Quantcast
Channel: Erik Darling – Brent Ozar Unlimited®
Viewing all articles
Browse latest Browse all 370

Forcing Join Order Without Hints

$
0
0

Brent buys lunch for the ladies

The purpose of this post is to show a bit of syntax that often gets overlooked in favor of using query hints to force joins to occur in a particular order. We’ll start by creating three tables. One for employees, one for orders, and one for items in the order.

/*
An employees table! How novel!
*/
CREATE TABLE #Ozars (OzarID INT IDENTITY(1,1) NOT NULL, OzarName VARCHAR(30) NOT NULL)
INSERT INTO #Ozars (OzarName) VALUES ('Brent'), ('Jeremiah'), ('Kendra'), ('Doug'), ('Jessica'), ('Erik')
ALTER TABLE #Ozars ADD CONSTRAINT [PK_Ozars] PRIMARY KEY CLUSTERED (OzarID, OzarName)
/*
Luuuuuuuunch
*/
CREATE TABLE #Lunch (LunchID INT IDENTITY(1,1) NOT NULL, OzarID INT NOT NULL)
INSERT INTO #Lunch (OzarID) VALUES (1),(1),(1),(3),(5)
ALTER TABLE #Lunch ADD CONSTRAINT [PK_Lunch] PRIMARY KEY CLUSTERED (LunchID, OzarID)
/*
Brent called it in, so it's all under his ID. Because that's how restaurants work. By ID. Yep.
*/
CREATE TABLE #LunchOrders (LunchOrderID INT IDENTITY(1,1) NOT NULL, LunchID INT NOT NULL, Lunch VARCHAR(20))
INSERT INTO #LunchOrders (LunchID, Lunch) VALUES (1, 'Just Churros'), (1, 'Box of Wine'), (1, 'Kaled Kale')
ALTER TABLE #LunchOrders ADD CONSTRAINT [PK_LunchOrders] PRIMARY KEY CLUSTERED (LunchOrderID, LunchID)

A SQL celebrity gossip blog got a tip that someone from BOU ordered take-out. Not exactly an earth-shattering event, but querying minds want to know!

So they write a query, and then they look at the plan.

SELECT o.*
, l.*
, lo.*
FROM #Ozars o
LEFT JOIN #Lunch l ON l.OzarID = o.OzarID
INNER JOIN #LunchOrders lo ON lo.LunchID = l.LunchID

And that’s way harsh. SQL went and changed our LEFT JOIN into an INNER JOIN. What was it thinking? Now we don’t know who Brent is having lunch with.

2015-04-28_14-20-22

How in the why in the heck did that LEFT JOIN turn into an INNER JOIN? SQL is full of it today.

Who ordered the KALE?

Okay, we thought about it some. No more INNER JOIN.
We’ll get this done with another LEFT JOIN.

SELECT o.*
,l.*
,lo.*
FROM #Ozars o
LEFT JOIN #Lunch l ON l.OzarID = o.OzarID
LEFT JOIN #LunchOrders lo ON lo.LunchID = l.LunchID

Unless you’re Ernie, that’s wayyyyy too many Brents.

Too many Brents on the dance floor.

Too many Brents on the dance floor.

First of all, ew. But yeah, you can do this, and it will come back with the right results.

SELECT o.*
, lol.*
FROM #Ozars o
LEFT JOIN (
       SELECT l.*, lo.LunchOrderID, lo.Lunch
       FROM #Lunch l 
       INNER JOIN #LunchOrders lo ON lo.LunchID = l.LunchID
) lol
ON o.OzarID = lol.OzarID
2015-04-28_15-04-31

WHAT SCALARS ARE YOU COMPUTING? WHAT FRESH HELL DID YOU SPAWN FROM?

We can even try an OUTER APPLY. That’s a little nicer looking as a query…

SELECT o.*
, lol.*
FROM #Ozars o
OUTER APPLY (
       SELECT l.LunchID ,
              l.OzarID, 
              lo.LunchOrderID, 
              lo.Lunch
       FROM #Lunch l 
       INNER JOIN #LunchOrders lo ON lo.LunchID = l.LunchID
       WHERE o.OzarID = l.OzarID
) lol

… But same yucky plan.

Hi, I’m a cool trick.

SELECT o.*
, l.*
, lo.*
FROM #Ozars o
LEFT JOIN #Lunch l --They see me LEFT JOIN...
INNER JOIN #LunchOrders lo  --Then INNER JOIN...
       ON lo.LunchID = l.LunchID --Then write both my
       ON o.OzarID = l.OzarID --ON clauses

Nicer plan and a little less CPU than the others, on average.

Pleased to meetcha!

Pleased to meetcha!

This is an interesting concept to play with. With more than a couple JOINs, you can start using parentheses to group them together, like so:

SELECT o.*
, l.*
, lo.*
FROM #Ozars o
LEFT JOIN (#Lunch l 
INNER JOIN #LunchOrders lo 
       ON lo.LunchID = l.LunchID)
       ON o.OzarID = l.OzarID

If you switch the order of the ON clauses in the second to last query, you’ll get an error. Take a guess why in the comments!

/*
Clean me up, buttercup.
*/
--DROP TABLE #Ozars;
--DROP TABLE #Lunch;
--DROP TABLE #LunchOrders;

Brent says: Okay, first off, I don’t normally drink an entire box of wine for lunch, but I had to wash down all that kale. Second off, judging by these execution plans, SQL Server intercepted my wine delivery.

Kendra says: I’ve found parentheses join hints twice in production code in SQL Server. In both cases, nobody knew why they were there, what would happen if they rewrote the query, or even if they’d been used on purpose or if it was just an accident. If you use this technique, document your code heavily as to what you’re doing and why, or you’ll be that person everyone grumbles about.


Viewing all articles
Browse latest Browse all 370

Trending Articles