MySQL - LEFT JOIN failing when WHERE clause added - mysql

I have 4 tables as follows; SCHEDULES, SCHEDULE_OVERRIDE, SCHEDULE_LOCATION_OVERRIDES and LOCATION
I need to return ALL rows from all tables so running this query works fine, adding NULL values for any values that are not present:
SELECT.....
FROM (schedule s LEFT JOIN schedule_override so ON so.schedule_id = s.id)
LEFT JOIN schedule_location_override slo ON slo.schedule_override_id = so.id
LEFT JOIN location l ON slo.location_id = l.id
ORDER BY s.id, so.id, slo.id, l.id
I then need to restict results on the schedule_override end_date field. My problem is, as soon as I do this, no results for the SCHEDULE table are returned at all. I need all schedules to be returned, even if the overrides end_date criteria is not met.
Heres what I am using:
SELECT.....
FROM (schedule s LEFT JOIN schedule_override so ON so.schedule_id = s.id)
LEFT JOIN schedule_location_override slo ON slo.schedule_override_id = so.id
LEFT JOIN location l ON slo.location_id = l.id
WHERE so.end_date > '2011-01-30' OR so.end_date IS NULL
ORDER BY s.id, so.id, slo.id, l.id
Appreciate any thoughts/comments.
Best regards, Ben.

Have you tried putting it in the ON clause?
SELECT.....
FROM (schedule s LEFT JOIN schedule_override so ON so.schedule_id = s.id AND (so.end_date > '2011-01-30' OR so.end_date IS NULL))
LEFT JOIN schedule_location_override slo ON slo.schedule_override_id = so.id
LEFT JOIN location l ON slo.location_id = l.id
ORDER BY s.id, so.id, slo.id, l.id

That's a quite common mistake with outer Joins.
You need to put everything that limits the Join into the "ON" part for that table, otherwise you are effectively transforming the join to an inner one.
So move the WHERE clause in this case into the ON-part of the schedule_override and you should be fine.

Yes, when you left join, it could be that a row is not found, and the field is NULL in the result. When you add a condition in the WHERE clause, the value must match that condition, which it won't if it's NULL.
That shouldn't be a problem, because you explicitly check for NULL, so I don't really know why this condition fails, unless it does return a date, but that date is befor 2011-01-30.
Anyway, you could try to move the condition to the join. It will eliminate the need to check for NULL, although it shouldn't make a difference really.
SELECT.....
FROM
schedule s
LEFT JOIN schedule_override so
ON so.schedule_id = s.id
AND so.end_date > '2011-01-30'
...

Related

What Would be the Correct SELECT Statement for This?

SELECT *
FROM notifications
INNER JOIN COMMENT
ON COMMENT.id = notifications.source_id
WHERE idblog IN (SELECT blogs_id
FROM blogs
WHERE STATUS = "active")
INNER JOIN reportmsg
ON reportmsg.msgid = notifications.source_id
WHERE uid =: uid
ORDER BY notificationid DESC
LIMIT 20;
Here I am INNER JOINing notifications with comment and reportmsg; then filtering content with WHERE.
But my problem is that for the first INNER JOIN [i.e, with comment], before joining notifications with comment, I want to match notifications.idblog with blogs.blogs_id and SELECT only those rows where blogs.status = "active".
For better understanding of the code above:
Here, for INNER JOIN, with comment I want to SELECT only those rows in notifications whose idblog matches blogs.blogs_id and has status = "active".
The second INNER JOIN with reportmsg needs not to be altered. I.e, it only filters through uid.
As you can see from the image below, you can just need to merge other tables to notifications table using LEFT JOIN like that:
SELECT n.notificationid, n.uid, n.idblog, n.source_id,
b.blogs_id, b.status,
c.id,
r.msgid
-- ... and the other columns you want
FROM notifications n
LEFT JOIN blogs b ON b.blogs_id = n.idblog AND b.STATUS = "active" AND n.uid =: uid
LEFT JOIN comment c ON c.id = n.source_id
LEFT JOIN reportmsg r ON r.msgid = n.source_id
ORDER BY n.notificationid DESC
LIMIT 20;
There's no need/reason to filter before the second join because you only use inner joins and then the order of joins and WHERE-conditions don't matter:
SELECT n.*, c.*, r.*
FROM notifications AS n
JOIN COMMENT as c
ON n.source_id = c.id
LEFT JOIN blogs as b
ON n.idblogs = b.blogs_id
AND B.STATUS = 'active'
JOIN reportmsg AS R
ON n.source_id = r.msgid
WHERE uid =: uid
ORDER BY notificationid DESC
LIMIT 20
You can switch the order of joins, you can move B.STATUS = 'active' into the join-condition, but all queries will return the same result. (After the edit it's a LEFT JOIN, of course now the result differs)
And of course you shouldn't use *, better list only the columns you actually need.
if query optimizer does its work, it does not matter where you put filtering statement in INNER JOIN case but in the LEFT JOIN it has effects. Putting filtering statement in LEFT JOIN conditions cause table filtered at first and joined after while putting filtering statement in WHERE clause will filter results of join. Hence, if you want to use LEFT JOIN your query must look like:
SELECT nt.*
FROM notifications nt
LEFT JOIN Blogs bg on nt.blogs_id = bg.blogs_id and bg.STATUS = "active"
LEFT JOIN COMMENT cm ON cm.id = nt.source_id
LEFT JOIN reportmsg rm ON rm.msgid = nt.source_id
WHERE uid =: uid
ORDER BY nt.notificationid DESC
LIMIT 20;
It's very unclear what you are after here.. while your table diagram is useful, you should really supply some sample data and an expected result even if it is just a couple of dummy rows for each table.
Queries work row by row, both INNER JOINs are applied to the same notification row and non-matching rows are discarded.
Any filter applies to both JOIN and any returned rows must have a match in BOTH comment and reportmsg.
Perhaps you want two LEFT JOINs that can apply different filters and guessing from the table names perhaps it could look like this:
SELECT *
FROM notifications n
LEFT JOIN blogs b
ON n.blogId = b.blogs_id
LEFT JOIN comment c
ON c.id = n.source_id
AND b.status = "Active"
LEFT JOIN reportmsg rm
ON rm.msgid = n.source_id
WHERE n.uid =: uid
AND (c.id IS NOT NULL OR rm.msgid IS NOT NULL)
ORDER BY n.notificationid DESC
LIMIT 20
You also should work on your naming convention:
notifications, comment -> pick either plural or singular table names
notifications.notificationid, comment.id -> pick adding table name to id
notificationid, source_id -> pick underscore or no separation
idblog, notificationid -> pick prepending or appending id
Currently you pretty much have to look up every id field every time you want to use one.
You should change your query to this:
SELECT *
FROM notifications
INNER JOIN comment ON comment.id = notifications.source_id
INNER JOIN reportmsg ON reportmsg.msgid=notifications.source_id
LEFT JOIN blogs ON notifications.idblog = blogs.blogs_id
WHERE blogs.status = 'active'
ORDER BY notificationid DESC
LIMIT 20;

LEFT JOIN on a nullable column. What is the behaviour?

It is not clear to me what is the behaviour of a LEFT JOIN if you try to JOIN on a column that may be NULL.
E.g.
SELECT columns
FROM
EmployeePayment ep JOIN EmployeePaymentGroup g ON g.group_id = ep.group_id AND g.subsidiary_id = ep.subsidiary_id
LEFT JOIN EmployeeSalaryDetails esd USING (department_id)
LEFT JOIN Employee e ON e.id = esd.emp_id
What happens if the INNER JOIN of EmployeePayment and EmployeePaymentGroup return 1 record and then the second LEFT JOIN on EmployeeSalaryDetails retains this record but this record has as esd.emp_id a NULL value and try to LEFT JOIN on Employee on that NULL value.
I know that NULLs are tricky so I was wondering how does the LEFT JOIN cope with NULLs
Note:
I opened a question earlier about JOINs but the comment of Abhik Chakraborty make me realise that there was a problem with a NULL value in a condition
If you have a LEFT JOIN and the right table returns nothing (NULL) all the fields belonging to the right table in the projection are simply NULL, but you still get your result from the left table. RIGHT JOIN has the opposite behavior and INNER JOIN will not return anything.
SELECT * FROM `left_table` LEFT JOIN `right_table`
NULL = NULL evaluates to UNKNOWN (which means “no, don’t join because I have no clue if we are allowed to.”) and the projection will only contain the results from the left table.
Of course there are ways to go around this problem:
SELECT *
FROM `left_table` AS `l`
LEFT JOIN `right_table` AS `r`
ON `r`.`id` <=> `l`.`id`
Now checks against NULL will work normally as you are used to (NULL <=> NULL is 1 and 'value' <=> NULL is 0). Also see the documentation for the equal to operator.
You can use isnull(cast()) function to convert the null record in a type and give it a value that is also in the employee column in order to join them based on that value.
LEFT JOIN EmployeeSalaryDetails esd on isnull((esd.department_id, "input here value of id") = "input here the value of the column"
Here is a example:
Inner join FORECAST_Claims b on (isnull(cast(a.Durg_Key as varchar(20)),'UNKNOWN') = isnull(cast(b.Durg_Key as varchar(20)),'UNKNOWN')

if condition for join query

SELECT tm.MAGAZINE_ID, tm.publisher_id, tu.begin_date, tu.report_from,
units `MAGAZINE_NAME`
FROM `tbl_itunes_report` tu
LEFT JOIN tbl_magazine_subscription_dtl tsd
ON tsd.subscription_key = tu.sku_key
AND ((tu.begin_date >= tsd.start_date
AND tu.begin_date < tsd.end_date) OR (tu.begin_date >= tsd.start_date
AND tsd.end_date = '0000-00-00'))
LEFT JOIN tbl_magazine_subscription ts
ON ts.magazine_subscription_id = tsd.subs_id
LEFT JOIN tbl_magazine_issue ti
ON ti.PurchaseKey = tu.sku_key AND ti.OS_SELECT = 0
LEFT JOIN tbl_magazine tm
ON tm.magazine_id = ts.magazine_id OR tm.magazine_id = ti.magazine_id
WHERE `product_type_identifier` LIKE 'IA%'
AND ( tsd.subscription_key IS NOT NULL OR ti.PurchaseKey IS NOT NULL )
GROUP BY tm.MAGAZINE_ID,tsd.no_of_issues
ORDER BY tm.magazine_name, tsd.no_of_issues
Using this query I will generate my report. There are 4 joins in it.
In the case tbl_magazine_subscription_dtl, tbl_magazine_issue uses the sku_key field for joining. The sku_key value is present in only one table at a time, but the joining will lead to decreased query speed. Is it possible to check if sku_key is present or not before the join is performed?
I don't completely understand your question, do you want only the rows where sku_key is not null and present in both tbl_magazine_issue and tbl_magazine_subscription_dtl? In that case, you should do an inner join instead of a left outer join on tbl_magazine_issue, and add a AND tu.sku_key IS NOT NULL to the WHERE clause. The query engine should be smart enough to apply this condition before the join.
You should also make sure that sku_key and PurchaseKey are indexed if you're concerned about performance of this join.

Mysql Query Left Join Condition Problem

I have a litte problem with a mysql query.
I use 5 tables:
user_has_data (uid, dataid); users (uid, uname); user_in_group(uid, groupid, data); groups(groupid, data, packageid); packages(packageid, name)
all ids are PK. I want to build a sql query that finds a user, which belongs to a specified dataid, by its uname and checks if the user is in a group (relation in table user_in_group) belonging to a specified package (a group is assigned to one package). if so data from users, package and group should be fetched, otherwise only the user data should be fetched. Therefore I use left joins, so I can also get the users with no group:
SELECT `uac`.`uid`, `u`.`uid`, `uig`.`groupid`, `ag`.`packageid`
FROM `user_has_data` AS `uac`
INNER JOIN `users` AS `u` ON u.uid = uac.uid
LEFT JOIN `user_in_group` AS `uig` ON uig.uid = uac.uid
LEFT JOIN `groups` AS `ag` ON (ag.groupid = uig.groupid) AND (ag.packageid = 2)
WHERE (uac.dataid = '3') AND (u.uname LIKE 'test%')
GROUP BY `u`.`uid`
Unfortunately I get wrong results: I get groups that have a different packageid than stated in the join, if the user has another group assigned to him with a different packageid.
probably this is because the first left join has no restrictions to packageid and the second is a left join and so it has no restrictions on the result (packageid is NULL for all results, but should have values). If I change the second left join to a ordinary join, the group problem would be fixed but the query cant find users without group any more.
Any ideas how to fix this or even possible?
thanks in advance!
Are you saying that you are actually seeing the value ag.packageid = 2 in your query results?
If not, I think you might try something like:
SELECT `uac`.`uid`, `u`.`uid`, `g`.`groupid`, `g`.`packageid`
FROM `user_has_data` AS `uac`
INNER JOIN `users` AS `u` ON u.uid = uac.uid
LEFT JOIN (`user_in_group` AS `uig`
INNER JOIN `groups` AS `ag` ON (ag.groupid = uig.groupid) AND (ag.packageid = 2) )
AS `g` ON uac.uid = g.uid
WHERE (uac.dataid = '3') AND (u.uname LIKE 'test%')
GROUP BY `u`.`uid`
Because you are limiting your search to a specific group packageid of '2', why not just make both of your LEFT JOIN INNER JOINS and then throw in ag.packageid = 2 in your WHERE clause?
SELECT `uac`.`uid`, `u`.`uid`, `uig`.`groupid`, `ag`.`packageid`
FROM `user_has_data` AS `uac`
INNER JOIN `users` AS `u` ON u.uid = uac.uid
LEFT OUTER JOIN `user_in_group` AS `uig` ON uig.uid = uac.uid
LEFT OUTER JOIN `groups` AS `ag` ON ag.groupid = uig.groupid
WHERE (uac.dataid = '3') AND (u.uname LIKE 'test%')
AND (ag.packageid = 2 OR uig.uid IS NULL)
GROUP BY `u`.`uid`
I know LEFT JOIN and LEFT OUTER JOIN mean the same thing, but I like to be explicit. With the condition in your join, I bet you were getting groups with different packages, but weren't getting the packages?

Extra condition in ON clause is ignored

I have this query:
SELECT
TA.id,
T.duration,
DATE_FORMAT(TA.startTime,'%H:%i') AS startTime,
TEI.displayname,
TA.threatment_id,
TE.employeeid,
TTS.appointment_date
FROM
tblEmployee AS TE
INNER Join tblEmployeeInfo AS TEI ON TEI.employeeinfoid = TE.employeeinfoid
LEFT OUTER Join tblAppointment AS TA ON TE.employeeid = TA.employee_id
LEFT OUTER Join tblThreatment AS T ON TA.threatment_id = T.threatmentid
LEFT OUTER Join tblAppointments AS TTS ON TTS.id = TA.appointments_id
AND TTS.appointment_date = '2009-10-19'
LEFT OUTER Join tblCustomerCard AS TCC ON TCC.customercardid = TTS.customercard_id
WHERE
TE.employeeid = 1
What I try to accomplish is to select an employee, if available, all appointments at a given date. When there aren't any appointments, it should at least give the information about the employee.
But right now, it just gives all appointments related to an employee, and passes null when the date doesn't match. Whats going wrong here?
Because you are doing a left OUTER join, it will only join those records that match the On condition and will attach Null when the condition is not met.
You will still get records for which there is no Appointments on that date.
If you did an INNER join, then if the On condition is not met, no record will be output. So you will not get any records for which there are no appointments on that date.
Ok, not sure what database you are on, but this would work on SQL server :
select * from tblEmployee TA
...
left join
( select * from tblAppointments ed where ed.appointment_date = '10/01/2008' ) TTS
on ON TTS.id = TA.appointments_id
Thats the vibe anyway! You might need to tinker a bit.. Im at work and cant get the whole thing going for ya! :)