I am fairly new with advanced MySQL commands, I have database tables with multiple relationships. I have an advanced search feature that must match Keywords to a few fields (most being in the Assets table but a couple in the User table). When I execute the following Query for desk it returns the same row multiple times, while it should do it once.
SELECT
a.id, a.asset_id, a.name, a.serial_number, a.category_id, a.status_id, a.user_id, a.location_id
FROM
assets a, users u
WHERE
(a.asset_ID LIKE '%desk%' OR a.name LIKE '%desk%' OR (u.first_name LIKE '%desk%' OR u.last_name LIKE '%desk%')) AND
a.serial_number LIKE '%%' AND
a.category_id='2'
LIMIT 25 OFFSET 5450
You have a cartesian product in your query. You should JOIN assets and users, e.g. with
WHERE a.user_id = u.id
or like this
FROM assets a JOIN users u ON a.user_id = u.id
UPDATE: Your comment shows that you actually want to "left outer join" the users table. This means, that all assets are in the result set regardless if there exists a matching user:
FROM assets a LEFT OUTER JOIN users u ON a.user_id = u.id
Read more about joining tables here: http://dev.mysql.com/doc/refman/5.5/en/join.html
You could use a SELECT DISTINCT clause, though the actual problem looks like the one made by Lukas. It's good practice to use explicit joins like so:
FROM assets a
JOIN users u ON a.user_id=u.id
You need to actually join the two tables (Assets and Users) together. As you have it, every row in each table is matched with every row in the other. This is known as a Cartesian Product and is usually a bad thing.
I would also suggest that you start using proper JOIN syntax:
SELECT
a.id,
a.asset_id,
a.name,
a.serial_number,
a.category_id,
a.status_id,
a.user_id,
a.location_id
FROM
Assets A
INNER JOIN Users U ON A.user_id = U.user_id
WHERE
(
a.asset_ID LIKE '%desk%' OR
a.name LIKE '%desk%' OR
(
u.first_name LIKE '%desk%' OR
u.last_name LIKE '%desk%'
)
) AND
a.serial_number LIKE '%%' AND
a.category_id='2'
LIMIT 25
OFFSET 5450
You are missing the join between the two tables.
Add something like
AND a.user_id = u.user_id
Related
SELECT DISTINCT u.id AS userId,u.type AS userType
FROM User AS u,Personal AS p,Company AS c
WHERE (p.realName LIKE '%adf%' AND u.type=1 AND u.id=p.userId)
OR (c.name LIKE '%grge%' AND u.id=c.userId)
LIMIT 0 , 10000
You can write your query as:
SELECT DISTINCT u.id AS userId,u.type AS userType
FROM User AS u inner join Personal AS p on u.id=p.userId
inner join Company AS c on u.id=c.userId
where p.realName LIKE '%adf%' or c.name LIKE '%grge%'
LIMIT 0 , 10000
Try to avoid comma seperated JOINS
You appear to be doing a quite hideous cross join, and then selectively narrowing down the records in the WHERE clause.
It is probably better to do 2 queries and union the results together. Each query can do one proper join. It is still going to have to access one column using the LIKE, and with a leading wild card that is not going to be quick (it can't use indexes).
SELECT u.id AS userId,
u.type AS userType
FROM User AS u
INNER JOIN Personal AS p
ON u.id = p.userId
WHERE p.realName LIKE '%adf%'
AND u.type = 1
UNION
SELECT u.id AS userId,
u.type AS userType
FROM User AS u
INNER JOIN Company AS c
ON u.id=c.userId
WHERE c.name LIKE '%grge%'
LIMIT 0 , 10000
I have 3 tables :
user(id, name);
advert(id,name,category_id,user_id);
category(id,category_name);
What i want to do is show for a certain user how many adverts he has in each category.
I have to use a sub-query.I've tried something like this but is not working at all.
What I've tried looks like this:
SELECT c.id,c.name count(advert) number_of_adverts
FROM category c
GROUP BY c.name
HAVING count(advert)=(SELECT count(a.id)
FROM advert a
INNER JOIN user ON a.user_id=u.id
WHERE u.id="1"
HAVING a.category_id=c.id);
I know this is wrong but i dont quite understand how should i do it to make it work.
Your looking for the GROUP BY clause.
SELECT c.id, c.name, COUNT(a.id) AS number_of_adverts
FROM user u
JOIN advert a ON (u.id = a.user_id)
JOIN category c ON (c.id = a.category_id)
WHERE u.user_id = '999'
GROUP BY c.id, c.name
You can't use a sub-query as, there could be multiple categories and multiple adverts for each category.
I usually go with the join approach but in this case I am a bit confused. I am not even sure that it is possible at all. I wonder if the following query can be converted to a left join query instead of the multiple select in used:
select
users.id, users.first_name, users.last_name, users.description, users.email
from users
where id in (
select assigned.id_user from assigned where id_project in (
select assigned.id_project from assigned where id_user = 1
)
)
or id in (
select projects.id_user from projects where projects.id in (
select assigned.id_project from assigned where id_user = 1
)
)
This query returns the correct result set. However, I guess the repetition of the query that selects assigned.id_project is a waste.
You could start with the project assignments of user 1 a1. Then find all assignments of other people to those projects a2, and the user in the project table p. The users you are looking for are then in either a2 or p. I added distinct to remove users who can be reached in both ways.
select distinct u.*
from assigned a1
left join
assigned a2
on a1.id_project = a2.id_project
left join
project p
on a1.id_project = p.id
join user u
on u.id = a2.id_user
or u.id = p.id_user
where a1.id_user = 1
Since both subqueries have a condition where assigned.id_user = 1, I start with that query. Let's call that assignment(s) the 'leading assignment'.
Then join the rest, using left joins for the 'optional' tables.
Use an inner join on user that matches either users of assignments linked to the leading assignment or users of projects linked to the leading project.
I use distinct, because I assumen you'd want each user once, event if they have an assignment and a project (or multiple projects).
select distinct
u.id, u.first_name, u.last_name, u.description, u.email
from
assigned a
left join assigned ap on ap.id_project = a.id_project
left join projects p on p.id = a.id_project
inner join users u on u.id = ap.id_user or u.id = p.id_user
where
a.id_user = 1
Here's an alternative way to get rid of the repetition:
SELECT
users.id,
users.first_name,
users.last_name,
users.description,
users.email
FROM users
WHERE id IN (
SELECT up.id_user
FROM (
SELECT id_user, id_project FROM assigned
UNION ALL
SELECT id_user, id FROM projects
) up
INNER JOIN assigned a
ON a.id_project = up.id_project
WHERE a.id_user = 1
)
;
That is, the assigned table's pairs of id_user, id_project are UNIONed with those of projects. The resulting set is then joined with the user_id = 1 projects to obtain the list of all users who share the projects with the ID 1 user. And now it only remains to retrieve the details for those users, which in this case is done in the same way as in your query, i.e. using an IN clause.
I'm sorry to say that I don't have MySQL to thoroughly test the performance of this query and so cannot be quite sure if it is in any way better or worse than your original query or than the one suggested both by #GolezTrol and by #Andomar. Generally I tend to agree with #GolezTrol's comment that a query with simple (semi- or whatever-) joins and repetitive parts might turn out more efficient than an equivalent sophisticated query that doesn't have repetitions. In the end, however, it is testing that must reveal the final answer for you.
The following query does what I want. It returns all the resuls in the users table and then if there is a match in the details tble, returns the relevant data
users
id|username
details
id|userid|firstname|lastname
$sql = "SELECT u.*, d.*
FROM `users` u
LEFT JOIN `details` d on
u.id = d.userid
ORDER BY $strorder";
However, when I try to join an additonal table where I want to do the same thing--return all the results of the users table and if there is a match in the third table, return the relevant data (total followers of this user)--it only returns one record.
3rd table
follow
id|followerid|followedid
$sql = "SELECT u.*, d.*, COUNT(f.id)
FROM `users` u
LEFT JOIN `details` d on
u.id = d.userid
LEFT JOIN `follow` f on
u.id = f.followedid
ORDER BY $strorder";
Can anyone see what I am doing wrong? Thanks in advance for any suggestions.
Many thanks.
Try to avoid * to select fields, it will be clearer to group your datas (even if mysql is quite permissive with groupings).
When you have an aggregate function (like COUNT, SUM), the other "non aggregated" requested fields should be in a GROUP BY clause.
Mysql don't force you to GROUP BY all the fields, but... I think it's quite a good habit to be "as ANSI as possible" (usefull when you use another DBMS)
SELECT u.id, u.username, d.firstname, d.lastname, count(*) as numberfollowers
FROM user u
LEFT JOIN details d on u.id = d.userid
LEFT JOIN follow f on u.id = f.followedid
GROUP BY u.id, u.username, d.firstname, d.lastname --or just GROUP BY u.id with Mysql
ORDER BY count(*) desc
COUNT being an aggregate function, when selected with other columns, requires you to group your results by those other columns in the select list.
You should rewrite your query with columns that you want to select from users and details and group by those columns.
See, I've got this bulletin board. This query here works just fine.
SELECT bulletin.date,
bulletin.title,
bulletin.content,
bulletin.id,
bulletin.made_by,
users.ID,
users.name
FROM bulletin, users
WHERE bulletin.id = '12345'
AND bulletin.made_by = users.ID
LIMIT 1
Now, I'm running into troubles because when a user deletes his account there is no information about said person left, he is deleted from the users table. I know there may be other ways to deal with this but I can't fix that now.
So I ask: How can I make that query return bulletin.date, bulletin.title and bulletin.content where bulletin.id is equal to 12345 even though I've got "AND bulletin.made_by = users.ID"? You see, I need to have that for all the other posts where the user still exists.
Any suggestions?
Use a LEFT OUTER JOIN
SELECT b.date,
b.title,
b.content,
b.id,
b.made_by,
b.ID,
b.name
FROM bulletin b
LEFT OUTER JOIN users u on b.made_by = u.ID
WHERE b.id = '12345'
LIMIT 1
See this great explanation of joins
Currently you are using INNER JOIN, Use LEFT JOIN in order to solve your problem.
SELECT a.date,
a.title,
a.content,
a.id,
a.made_by,
a.ID,
a.name
FROM bulletin a
LEFT JOIN users c
ON a.made_by = c.ID
WHERE a.id = '12345'
LIMIT 1
LEFT JOIN basically fetches all the records from the left table whether it has a matching row in the second table or not.