Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

SQL Server Left Join With 'Or' Operator

I have a four tables, TopLevelParent, two mid level tables MidParentA and MidParentB, and a Child table which can have a parent of MidParentA or MidParentB (One or the other midParent must be in place). Both mid level tables have a parent table of TopLevelParent.

The Top Level table look like this:

TopLevelId | Name -------------------------- 1          | name1    2          | name2    

The MidParent tables look like this:

MidParentAId | TopLevelParentId |           MidParentBId | TopLevelParentId | ------------------------------------       ------------------------------------ 1            |        1         |           1            |        1         | 2            |        1         |           2            |        1         | 

The Child table look like this:

ChildId | MidParentAId | MidParentBId -------------------------------- 1       |     1        |   NULL 2       |    NULL      |     2 

I have used the following left join in a larger stored procedure which is timing out, and it looks like the OR operator on the last left join is the culprit:

SELECT *     FROM TopLevelParent tlp LEFT JOIN MidParentA a ON tlp.TopLevelPatientId = a.TopLevelPatientId LEFT JOIN MidParentB a ON tlp.TopLevelPatientId = b.TopLevelPatientId LEFT JOIN Child c ON c.ParentAId = a.ParentAId OR c.ParentBId = b.ParentBId 

Is there a more performant way to do this join?

like image 345
Declan McNulty Avatar asked Nov 01 '13 10:11

Declan McNulty


People also ask

Can we use or condition in join?

If you have an OR condition in the JOIN - and there is no possibility that the values in the OR statement overlap...then you can convert it to a UNION ALL. If the values overlap it would require a UNION which may not improve performance over the JOIN.

What does (+) mean in SQL join?

The plus sign is Oracle syntax for an outer join. There isn't a minus operator for joins. An outer join means return all rows from one table. Also return the rows from the outer joined where there's a match on the join key. If there's no matching row, return null.

Can I use WHERE in left join?

Left join is applied to the tables Course and Student and the table below is the result set. The left table and its corresponding matching rows on the right table are displayed. If a user wants to display the rows only in the left table, where clause can be used in the query.

What is left join with example?

The Left Join in SQL basically returns all records from the left table and the matched records from the right tables. For example, let's say, we have two tables, Table A and Table B. When Left Join is applied on these two tables, all records from Table A and only the matched records from Table B will be displayed.


2 Answers

Given how little of the query is being exposed; a very rough rule of thumb is to replace an Or with a Union to avoid table scanning.

Select.. LEFT JOIN Child c ON c.ParentAId = a.ParentAId  union Select.. left Join Child c ON c.ParentBId = b.ParentBId 
like image 146
u07ch Avatar answered Sep 23 '22 21:09

u07ch


Here is what I did in the end, which got the execution time down from 52 secs to 4 secs.

SELECT *  FROM (     SELECT tpl.*, a.MidParentAId as 'MidParentId', 1 as 'IsMidParentA'      FROM TopLevelParent tpl      INNER JOIN MidParentA  a ON a.TopLevelParentId = tpl.TopLevelParentID UNION     SELECT tpl.*, b.MidParentBId as 'MidParentId', 0 as 'IsMidParentA'       FROM TopLevelParent tpl      INNER JOIN MidParentB b ON b.TopLevelParentId = tpl.TopLevelParentID UNION     SELECT tpl.*, 0 as 'MidParentId', 0 as 'IsMidParentA'       FROM TopLevelParent tpl      WHERE tpl.TopLevelParentID NOT IN (        SELECT pa.TopLevelParentID         FROM TopLevelParent tpl        INNER JOIN MidParentA  a ON a.TopLevelParentId = tpl.TopLevelParentID     UNION        SELECT pa.TopLevelParentID         FROM TopLevelParent tpl        INNER JOIN MidParentB b ON h.TopLevelParentId = tpl.TopLevelParentID     ) ) tpl LEFT JOIN MidParentA a ON a.TopLevelParentId = tpl.TopLevelParentID LEFT JOIN MidParentB b ON b.TopLevelParentId = tpl.TopLevelParentID LEFT JOIN  (         SELECT  [ChildId]                 ,[MidParentAId] as 'MidParentId'                 ,1 as 'IsMidParentA'         FROM Child c         WHERE c.MidParentAId IS NOT NULL    UNION         SELECT [ChildId]                ,[MidParentBId] as 'MidParentId'                ,0 as 'IsMidParentA'         FROM Child c         WHERE c.MidParentBId IS NOT NULL ) AS c ON c.MidParentId = tpl.MidParentId  AND c.IsMidParentA = tpl.IsMidParentA 

This eliminates the table scanning that was happening, as I have matched the top level record to its midlevel parent up front if it exists, and stamped it on that record.

I have also done the same with the child record meaning I can then just join the child record to the top level record on the MidParentId, and I use the IsMidParentA bit flag to differentiate where there are two identical MidParentIds (ie an Id of 1 for IsMidParentA and IsMidParentB).

Thanks to all who took the time to answer.

like image 36
Declan McNulty Avatar answered Sep 23 '22 21:09

Declan McNulty