Self joins and CROSS JOIN
Join a table to itself to compare its rows or walk a hierarchy, and use CROSS JOIN to pair every row with every row on purpose. Plus how to list each pair only once.
Comparing rows of one table
c1 and c2, and match on City. Without more conditions you also get everyone paired with themself, and every pair twice. c2.CustomerId > c1.CustomerId keeps each pair once:
SELECT c1.FirstName AS Customer, c2.FirstName AS SameCityAs, c1.CityFROM dbo.Customers AS c1JOIN dbo.Customers AS c2 ON c2.City = c1.City AND c2.CustomerId > c1.CustomerIdORDER BY c1.City, c1.CustomerId, c2.CustomerId;Customer SameCityAs City -------- ---------- ----- Asha Chloe Leeds Asha Grace Leeds Chloe Grace Leeds Ben Farid York
SELECT a.Name, b.Name AS SamePriceAs, a.PriceFROM dbo.Products AS aJOIN dbo.Products AS b ON b.Price = a.Price AND b.ProductId > a.ProductId;Name SamePriceAs Price ---------------- -------------- ----- Timber 2x4 2.4 m Safety goggles 6.80
Hierarchies: a foreign key to the same table
CREATE TABLE dbo.Staff ( StaffId int NOT NULL PRIMARY KEY, Name nvarchar(40) NOT NULL, JobTitle nvarchar(40) NOT NULL, ManagerId int NULL REFERENCES dbo.Staff (StaffId));INSERT INTO dbo.Staff (StaffId, Name, JobTitle, ManagerId) VALUES (1, N'Mira', N'Owner', NULL), (2, N'Tom', N'Store manager', 1), (3, N'Priya', N'Yard supervisor', 2), (4, N'Sam', N'Sales assistant', 2), (5, N'Leo', N'Yard hand', 3), (6, N'Nina', N'Bookkeeper', 1); SELECT s.Name AS Employee, s.JobTitle, m.Name AS ManagerFROM dbo.Staff AS sLEFT JOIN dbo.Staff AS m ON m.StaffId = s.ManagerIdORDER BY s.StaffId;Employee JobTitle Manager -------- --------------- ------- Mira Owner NULL Tom Store manager Mira Priya Yard supervisor Tom Sam Sales assistant Tom Leo Yard hand Priya Nina Bookkeeper Mira
s is the employee and m is the same table playing the manager. It is a LEFT JOIN so that Mira, who has no manager, is not lost. Each extra level up needs one more join; for a whole tree of any depth there are recursive CTEs, in the next module.
CROSS JOIN: every combination
VALUES list in FROM makes a small table on the spot:
SELECT p.Name, q.Qty, p.Price * q.Qty AS TotalFROM dbo.Products AS pCROSS JOIN (VALUES (1), (10), (50)) AS q (Qty)WHERE p.CategoryId = 2ORDER BY p.Name, q.Qty;Name Qty Total ------------ --- ------ Cement 25 kg 1 7.20 Cement 25 kg 10 72.00 Cement 25 kg 50 360.00 Sand 25 kg 1 4.10 Sand 25 kg 10 41.00 Sand 25 kg 50 205.00
Show a hint
Show the solution
SELECT a.Name, b.Name AS AndAlso, a.Price + b.Price AS TogetherFROM dbo.Products AS aJOIN dbo.Products AS b ON b.CategoryId = a.CategoryId AND b.ProductId > a.ProductIdWHERE a.Price + b.Price < 20;Name AndAlso Together ----------------- ---------------- -------- Cement 25 kg Sand 25 kg 11.30 Wood screws (200) Wall plugs (100) 16.40
Remember
A self join uses one table twice under two aliases. To list pairs once, require one key to be greater than the other. Hierarchies use a foreign key to the same table; LEFT JOIN keeps the top of the tree. CROSS JOIN returns every combination: m × n rows. A VALUES list in FROM makes a small table inside the query.