Why does the order of join clauses affect the query plan in SQL Server?
join, optimization, sql-server, sql-server-2000, sql-server-2005
Solution
I have to disagree with all previous answers, and the reason is simple: if you change the order of your left join, your queries are logically different and as such they produce different result sets. See for yourself:
SELECT 1 AS a INTO #t1
UNION ALL SELECT 2
UNION ALL SELECT 3
UNION ALL SELECT 4;
SELECT 1 AS b INTO #t2
UNION ALL SELECT 2;
SELECT 1 AS c INTO #t3
UNION ALL SELECT 3;
SELECT a, b, c
FROM #t1 LEFT JOIN #t2 ON #t1.a=#t2.b
LEFT JOIN #t3 ON #t2.b=#t3.c
ORDER BY a;
SELECT a, b, c
FROM #t1 LEFT JOIN #t3 ON #t1.a=#t3.c
LEFT JOIN #t2 ON #t3.c=#t2.b
ORDER BY a;
a b c
----------- ----------- -----------
1 1 1
2 2 NULL
3 NULL NULL
4 NULL NULL
(4 row(s) affected)
a b c
----------- ----------- -----------
1 1 1
2 NULL NULL
3 NULL 3
4 NULL NULL
Problem
I am building a view in SQL Server 2000 (and 2005) and I've noticed that the order of the join statements greatly affects the execution plan and speed of the query. ``` select sr.WTSASessionRangeID, -- bunch of other columns from WTSAVW_UserSessionRange us inner join WTSA_SessionRange sr on sr.WTSASessionRangeID = us.WTSASessionRangeID left outer join WTSA_SessionRangeTutor srt on srt.WTSASessionRangeID = sr.WTSASessionRangeID left outer join WTSA_SessionRangeClass src on src.WTSASessionRangeID = sr.WTSASessionRangeID left outer join WTSA_SessionRangeStream srs on srs.WTSASessionRangeID = sr.WTSASessionRangeID --left outer join MO_Stream ms on ms.MOStreamID = srs.MOStreamID left outer join WTSA_SessionRangeEnrolmentPeriod srep on srep.WTSASessionRangeID = sr.WTSASessionRangeID left outer join WTSA_SessionRangeStudent stsd on stsd.WTSASessionRangeID = sr.WTSASessionRangeID left outer join WTSA_SessionSubrange ssr on ssr.WTSASessionRangeID = sr.WTSASessionRangeID left outer join WTSA_SessionSubrangeRoom ssrr on ssrr.WTSASessionSubrangeID = ssr.WTSASessionSubrangeID left outer join MO_Stream ms on ms.MOStreamID = srs.MOStreamID ``` On SQL Server 2000, the query above consistently generates a plan of cost 946. If I uncomment the MO_Stream join in the middle of the query and comment out the one at the bottom, the cost drops to 263. The execution speed drops accordingly. I always thought that the query optimizer would interpret the query appropriately without considering join order, but it seems that order matters. So since order does seem to matter, is there a join strategy I should be following for writing faster queries? (Incidentally, on SQL Server 2005, with almost identical data, the query plan costs were 0.675 and 0.631 respectively.) Edit: On SQL Server 2000, here are the profiled stats: - `946-cost query: 9094ms CPU, 5121 reads, 0 writes, 10123ms duration` - `263-cost query: 172ms CPU, 7477 reads, 0 writes, 170ms duration` Edit: Here is the logical structure of the tables. ``` SessionRange ---+--- SessionRangeTutor |--- SessionRangeClass |--- SessionRangeStream --- MO_Stream |--- SessionRangeEnrolmentPeriod |--- SessionRangeStudent +----SessionSubrange --- SessionSubrangeRoom ``` Edit: Thanks to Alex and gbn for pointing me in the right direction. I also found this question. Here's the new query: ``` select sr.WTSASessionRangeID // + lots of columns from WTSAVW_UserSessionRange us inner join WTSA_SessionRange sr on sr.WTSASessionRangeID = us.WTSASessionRangeID left outer join WTSA_SessionRangeTutor srt on srt.WTSASessionRangeID = sr.WTSASessionRangeID left outer join WTSA_SessionRangeClass src on src.WTSASessionRangeID = sr.WTSASessionRangeID left outer join WTSA_SessionRangeEnrolmentPeriod srep on srep.WTSASessionRangeID = sr.WTSASessionRangeID left outer join WTSA_SessionRangeStudent stsd on stsd.WTSASessionRangeID = sr.WTSASessionRangeID // SessionRangeStream is a many-to-many mapping table between SessionRange and MO_Stream left outer join ( WTSA_SessionRangeStream srs inner join MO_Stream ms on ms.MOStreamID = srs.MOStreamID ) on srs.WTSASessionRangeID = sr.WTSASessionRangeID // SessionRanges MAY have Subranges and Subranges MAY have Rooms left outer join ( WTSA_SessionSubrange ssr left outer join WTSA_SessionSubrangeRoom ssrr on ssrr.WTSASessionSubrangeID = ssr.WTSASessionSubrangeID ) on ssr.WTSASessionRangeID = sr.WTSASessionRangeID ``` SQLServer2000 cost: 24.9