I'm having a bit of a problem with a SELECT query in MySQL and I'd be thankful for some pointers. Please feel free to point me towards an existing answer (if there is one and I missed it).
The query is currently as follows:
SELECT e.*, ie.aaa, ue.bbb, ue.ccc
FROM ie
LEFT JOIN e ON ie.e_id = e.e_id
LEFT JOIN ue ON ie.e_id = ue.e_id
WHERE ie.other_id = ? AND ue.unrelated_id = ?
ORDER BY ...
There are three tables: ie, e and ue.
Tables ie and ue are relationships of e, and therefore contain foreign keys to it (e_id). ? represents an input parameter.
The problem is the ue.unrelated_id = ? part. What I'm really trying to do here is:
To return ue.ccc if and only if there is a ue relationship for unrelated_id = ?. If it doesn't exist, I want this field to be null.
Even if the ue relationship for unrelated_id = ? doesn't exist, this query should always return the remaining fields (ie is guaranteed to exist for other_id = ?).
Unfortunately, if I remove this where clause, I get ue.ccc for a "random" unrelated_id. But if I keep it, the query won't return any results at all if ue doesn't exist for this unrelated_id! I also tried adding OR ue.unrelated_id IS NOT NULL, but this makes the query return no results if the ue table is empty.
Any ideas? Please drop a comment if you need further clarification. I should answer quickly in the next few hours.
You could do one of two things:
SELECT e.*, ie.aaa, ue.bbb, ue.ccc
FROM ie
LEFT JOIN e ON ie.e_id = e.e_id
LEFT JOIN ue ON ie.e_id = ue.e_id AND ue.unrelated_id = ?
WHERE ie.other_id = ?
ORDER BY ...
Or
SELECT e.*, ie.aaa, ue.bbb, ue.ccc
FROM ie
LEFT JOIN e ON ie.e_id = e.e_id
LEFT JOIN ue ON ie.e_id = ue.e_id
WHERE ie.other_id = ? AND (ue.unrelated_id IS NULL OR ue.unrelated_id = ?)
ORDER BY ...
I would go with the first query, however.
EDIT: Please note that the 2nd query is only approperiate if ue.unrelated_id is not a nullable column.
Related
Below is an sql query that im using to get a list of users who doest have contact details added. The below query seems to work fine. But the only problem is that the image value always returns NULL
I tried to use a sub query to image the image link.Everything works except for the image link.
SELECT a.id,a.name,a.address,a.image_id,(select url
from meta_details b
where b.p_id = 'a.image_id'
and b.meta_val='profile_pic') as image
FROM users
WHERE a.user_id NOT IN (SELECT user_id FROM details where contact_id != '$cid')
Im not sure if this is the correct way, is it possible to make it work to get the image url?
You should remove the quote around a.image_id ( otherwise instead of a join condition on field you have a join on literal value 'a.image_id' that fails because don't match )
SELECT
a.id
,a.name
,a.address
,a.image_id
,(select url
from meta_details b
where b.p_id = a.image_id and b.meta_val='profile_pic' ) as image
FROM users a
WHERE a.user_id NOT IN (SELECT user_id FROM details where contact_id != '$cid')
This type of query can also be expressed with join operations:
select u.*, md.url as image
from users u left join
meta_details md
on md.p_id = u.image_id and
md.meta_val = 'profile_pic' left join
details d
on d.user_id = u.user_id and d.contact_id <> '$cid'
where d.user_id is null;
The advantage to this approach is that the SQL optimizer has a chance of producing a better optimization plan. Also, NOT IN is dangerous because if any rows from the subquery return NULL, then no rows at all will be returned from the query.
Suppose I have two table Gardners table and Plantings table.
Suppose my query is:
SELECT gid, first_name, last_name, pid, gardener_id, plant_name
FROM Gardners
INNER JOIN Plantings
ON gid = gardener_id
I want to know how exactly it works internally?
As per my understanding in every join condition:
Each row from Gardner Table will be compared with each row of Plantings Table. If the condition is matched then it will print out. Is my understanding correct?
In terms of program if you think:
for i in [Gardners Table]:
for j in [Plantings Table]:
if i.id == j.garderner id:
print <>
Now suppose if you query is something like:
User(uid,uname,ucity,utimedat)
Follows(uid,followerid) // uid and followerid are pointing to `uid` of `User`.
SELECT U.uid, U.uname FROM User U1 JOIN Follows F,User U2 WHERE
F.followerid = U2.uiddate AND U2.ucity = 'D'
How the join condition will work internally here? Is it equivalent to:
for i in [USER]:
for j in [Follows]:
for k in [USER]:
if condition:
print <>
Your example with Gardners table and Plantings table is correct. But example with users not so obvious.
I think that what you want to get is user followers from some city.
Assuming correct query is:
SELECT U1.uid, U2.uname
FROM User U1
JOIN Follows F ON U1.uid = F.uid
JOIN User U2 ON F.followerid = U2.uid
WHERE U2.ucity = 'D'
Then in pseudo code it'll look like this:
for i in [User Table]:
for j in [Follows Table]:
if i.uid = j.uid:
for k in [User Table]:
if j.followerid = k.uid and k.city = 'D':
print <>
SQL Fiddle for this: http://sqlfiddle.com/#!9/caeb1e/5
There is a very good picture of how joins actually works can be found here: http://www.codeproject.com/Articles/33052/Visual-Representation-of-SQL-Joins
In your second query, it's not clear what you're trying to do exactly as the syntax is erroneous; but if I were to guess, it seems like your intention is to join User U1 with a sub query of (implicit) join between Followers F and User U2.
If my guess is correct, the query would properly look more like this:
SELECT U1.uid, U1.uname
FROM User U1 JOIN
(SELECT U2.uid
FROM Followers F,User U2
WHERE F.followerid = U2.uiddate AND U2.ucity = 'D') T
WHERE u1.uid = T.uid
Which is not a 'best practice' way of writing the query either (you should use explicit joins, there's no need for a sub-query but you can just join three times, and so on)
But I wrote it this way to keep it closest to your original query.
And if my guess is correct, then your pseudo code would be more like:
for u2 in [User 2 where condition]:
for f in [Follows]:
if f.uid == u2.uid
SELECT uid AS T
for u1 in [User 1]:
if u1.uid == T.uid:
print <>
However, it's not a fully explained interpretation, because one key to understanding SQL is to think more in 'set' of data being filtered, rather than sequential selection of objects of data, because SQL does operations based on the set of data, which one might not be used to.
So a number of the above steps will be executed in one go, instead of sequential. But other than that, you should look towards the answer given by Yuri Tkachenko above, in how to view joins - and then the internals will come second when writing correct joins.
Yes you're understanding is correct if you are only talking on a join not on the other join eg: Inner, Outer like in SQL
What is the simplest way to make part of a WHERE clause dependent on the result of a JOIN? I realize that is an extremely ambiguous and confusing question, so allow me to simply show you what I am trying to achieve:
SELECT m.member_id, m.first_name, m.last_name
FROM cal_form_answers a
INNER JOIN cal_form_elements e
USING(element_id)
INNER JOIN cal_forms f
USING(form_id)
LEFT JOIN members m
USING(member_id)
WHERE f.org_id = ?
AND m.org_id = ?
AND e.form_id = ?
GROUP BY a.member_id
ORDER BY a.member_id
First, note that the question marks are not invalid syntax—I am using Codeigniter, which uses them as bound parameters.
Line 10 (AND m.org_id = ?) is dependent on whether or not the LEFT JOIN actually finds something. If there is no match in the members table, then Line 10 becomes unnecessary. In fact, it becomes a problem. I would like to limit results by Line 10 unless there is no match in the members table. In that event, I would simply like to ignore that part of the WHERE clause.
I believe this can be achieved using subqueries, though I am admittedly unsure how to work out the syntax. Is there any other way? If yes, how else can this result be achieved? In the event there is no other way, can somebody demonstrate an implementation of a subquery for this situation, and explain how it works?
Thank you!
If a LEFT JOIN does not match a record, then those LEFT JOINed fields are null. Why not just check if that field IS NULL, and when it is not then check it.)
SELECT m.member_id, m.first_name, m.last_name
FROM cal_form_answers a
INNER JOIN cal_form_elements e
USING(element_id)
INNER JOIN cal_forms f
USING(form_id)
LEFT JOIN members m
USING(member_id)
WHERE f.org_id = ?
AND (m.org_id = ? OR m.org_id IS NULL)
AND e.form_id = ?
GROUP BY a.member_id
ORDER BY a.member_id
Im writing this complex query to return a large dataset, which is about 100,000 records. The query runs fine until i add in this OR statement to the WHERE clause:
AND (responses.StrategyFk = strategies.Id Or responses.StrategyFk IS
Null)
Now i understand that by putting the or statement in there it adds a lot of overhead.
Without that statement and just:
AND responses.StrategyFk = strategies.Id
The query runs within 15 seconds, but doesn't return any records that didn't have a fk linking a strategie.
Although i would like these records as well. Is there an easier way to find both records with a simple where statement? I can't just add another AND statement for null records because that will break the previous statement. Kind of unsure of where to go from here.
Heres the lower half of my query.
FROM
responses, subtestinstances, students, schools, items,
strategies, subtests
WHERE
subtestinstances.Id = responses.SubtestInstanceFk
AND subtestinstances.StudentFk = students.Id
AND students.SchoolFk = schools.Id
AND responses.ItemFk = items.Id
AND (responses.StrategyFk = strategies.Id Or responses.StrategyFk IS Null)
AND subtests.Id = subtestinstances.SubtestFk
try:
SELECT ... FROM
responses
JOIN subtestinstances ON subtestinstances.Id = responses.SubtestInstanceFk
JOIN students ON subtestinstances.StudentFk = students.Id
JOIN schools ON students.SchoolFk = schools.Id
JOIN items ON responses.ItemFk = items.Id
JOIN subtests ON subtests.Id = subtestinstances.SubtestFk
LEFT JOIN strategies ON responses.StrategyFk = strategies.Id
That's it. No OR condition is really needed, because that's what a LEFT JOIN does in this case. Anywhere responses.StrategyFk IS NULL will result in no match to the strategies table, and it wil return a row for that.
See this link for a simple explanation of joins: http://www.codinghorror.com/blog/2007/10/a-visual-explanation-of-sql-joins.html
After that, if you're still having performance issues then you can start looking at the EXPLAIN SELECT ... ; output and looking for indexes that may need to be added. Optimizing Queries With Explain -- MySQL Manual
Try using explicit JOINs:
...
FROM responses a
INNER JOIN subtestinstances b
ON b.id = a.subtestinstancefk
INNER JOIN students c
ON c.id = b.studentfk
INNER JOIN schools d
ON d.id = c.schoolfk
INNER JOIN items e
ON e.id = a.itemfk
INNER JOIN subtests f
ON f.id = b.subtestfk
LEFT JOIN strategies g
ON g.id = a.strategyfk
Im looping through a feedback type comment system on a users page, finding the latest 6 posts.
To display the post how I want, I have to grab data from 3 tables.
I figure it'd be less db intensive if it was all pulled in one query, but I'm not sure how to write it, being suck at JOINs and things.
This is my existing code
$sql_result = mysql_query("SELECT * FROM feedback WHERE user='1' ORDER BY date desc LIMIT 6", $db);
while($rs = mysql_fetch_array($sql_result)) {
$sql_result2 = mysql_query("SELECT * FROM members WHERE id= '$rs[author]'", $db);
$rs2 = mysql_fetch_array($sql_result2);
if ($rs2[clan] != 0) {
$sql_result3 = mysql_query("SELECT * FROM clans WHERE id= '$rs2[clan]' LIMIT 1", $db);
$rs3 = mysql_fetch_array($sql_result3);
// write comment
Can someone give me a clue?
This should do it:
select * from feedback
left join members on feedback.author = members.id
left join clans on members.clan = clans.id
where feedback.user = 1
left join means if the table on the right has no matching row, a record with all null values is returned, so you still get the data from the table on the left.
I am no expert in Sql myself, but I have picked up a few tricks here and there :-)
A typical LEFT JOIN that works in Firebird is :
select A.*,B.*,C.*
from FEEDBACK A left join MEMBERS B
on A.USER = B.ID left join CLANS C
ON C.ID = A.USER
where A.USER=1
The logic behind the join is that All rows that now share the same value,
A.USER = B.ID = C.ID will now be visible.
The letters A B and C is just used for simplicity.
F, M and C will work the same way.
This Left Join will pick out all and every column in tables. This is done with A.*,B.*,C.*
Maybe you want only a few columns in each table.
That can be accomplished by naming the columns in the same manner.
Example:
A.USER,A.FIRSTNAME,A.SURNAME,B.COLNAME1,B.COLNAME2,C.COLNAME1,C.COLNAME2
When you need to adress the columns later, remember the Prefix of A. or B. or C. before the actual column-name you address.
Good luck and best regards.
Morten, Norway