Anti Joins (Left, Right, Full)
Find rows that have NO match in the other table — orphan players, teams without players, and all leftovers.
Some of the most valuable questions are about what's missing: "Which players point to a team that doesn't exist?" or "Which franchises have no players yet?" SQL Server has no ANTI JOIN keyword — you build one from an outer join plus a WHERE ... IS NULL filter.
Definition
An ANTI JOIN returns only the rows of one table that have no matching row in the other table. In SQL Server it is written as an outer join followed by
WHERE <other table's key> IS NULL.
| Anti join | Venn picture in words | Built as |
|---|---|---|
| LEFT ANTI JOIN | circle A minus the overlap | A LEFT JOIN B … WHERE b.key IS NULL |
| RIGHT ANTI JOIN | circle B minus the overlap | A RIGHT JOIN B … WHERE a.key IS NULL (or swap + LEFT) |
| FULL ANTI JOIN | both circles minus the overlap (the leftovers) | A FULL JOIN B … WHERE a.key IS NULL OR b.key IS NULL |
Why IS NULL works: an outer join fills the other side with NULLs exactly when no match was found. Filtering for those NULLs keeps only the unmatched rows.
The data for these examples
In the seed data every player's team exists, so a left anti join would return nothing. The playground setup therefore adds two "orphan" players:
sql
INSERT INTO players (id, name, team, country, runs) VALUES
(7, 'Shubman Gill', 'GT2', 'India', 600), -- typo: should be GT
(8, 'Ishan Kishan', NULL, 'India', 450); -- team not assignedOn the other side, KKR and GT exist in teams but have no players.
LEFT ANTI JOIN — players without a team
sql
SELECT p.name, p.team, t.team AS t_team, t.team_name
FROM players AS p
LEFT JOIN teams AS t
ON p.team = t.team
WHERE t.team IS NULL;| name | team | t_team | team_name |
|---|---|---|---|
| Shubman Gill | GT2 | NULL | NULL |
| Ishan Kishan | NULL | NULL | NULL |
How it works: the LEFT JOIN keeps all 8 players; only Gill and Kishan got NULL team columns. WHERE t.team IS NULL keeps just those two. Great for data-quality checks — "GT2" is clearly a typo to fix.
RIGHT ANTI JOIN — teams without players
sql
SELECT p.name, p.team, t.team AS t_team, t.team_name
FROM players AS p
RIGHT JOIN teams AS t
ON p.team = t.team
WHERE p.team IS NULL;| name | team | t_team | team_name |
|---|---|---|---|
| NULL | NULL | KKR | Kolkata Knight Riders |
| NULL | NULL | GT | Gujarat Titans |
Alternative — swap the tables and use LEFT JOIN (same result):
sql
SELECT t.team, t.team_name, t.city
FROM teams AS t
LEFT JOIN players AS p
ON p.team = t.team
WHERE p.id IS NULL;| team | team_name | city |
|---|---|---|
| KKR | Kolkata Knight Riders | Kolkata |
| GT | Gujarat Titans | Ahmedabad |
FULL ANTI JOIN — only the unmatched rows from both sides
sql
SELECT p.name, p.team, t.team AS t_team, t.team_name
FROM players AS p
FULL JOIN teams AS t
ON p.team = t.team
WHERE p.id IS NULL
OR t.team IS NULL;| name | team | t_team | team_name |
|---|---|---|---|
| Shubman Gill | GT2 | NULL | NULL |
| Ishan Kishan | NULL | NULL | NULL |
| NULL | NULL | KKR | Kolkata Knight Riders |
| NULL | NULL | GT | Gujarat Titans |
These are the leftovers — every row that did not find a partner on either side.
Which column should you test for NULL?
Test a column of the other table that can never be NULL in a real row — ideally its primary key (t.team, p.id). Then NULL can only mean "no match".
Watch what happens if you test the wrong column in a left anti join:
sql
-- ❌ tests the LEFT table's own column
SELECT p.name, p.team, t.team AS t_team
FROM players AS p
LEFT JOIN teams AS t
ON p.team = t.team
WHERE p.team IS NULL;| name | team | t_team |
|---|---|---|
| Ishan Kishan | NULL | NULL |
Shubman Gill (team 'GT2') is missed, because his own team is not NULL. Always check the other table's key.
Other ways to write an anti join
| Method | Code | Note |
|---|---|---|
| LEFT JOIN + IS NULL | shown above | the course's main pattern |
NOT EXISTS | WHERE NOT EXISTS (SELECT 1 FROM teams t WHERE t.team = p.team) | returns Gill and Kishan; often the clearest |
NOT IN | WHERE p.team NOT IN (SELECT team FROM teams) | ⚠️ returns only Gill — NULL NOT IN (…) is unknown, so Kishan is silently skipped |
Common mistakes
- ❌ Putting
IS NULLin theONclause instead ofWHERE. ✅ The NULLs only exist after the join, so filter inWHERE. - ❌ Testing a nullable column (
p.team) instead of the other table's key. ✅ Testt.team/p.id. - ❌ Using an INNER JOIN and expecting unmatched rows. ✅ Anti joins always start from an outer join.
Interview tip
Q: Find all teams that have no players.
A: SELECT t.* FROM teams t LEFT JOIN players p ON p.team = t.team WHERE p.id IS NULL; — or WHERE NOT EXISTS (SELECT 1 FROM players p WHERE p.team = t.team). Mention that NOT IN is risky when the subquery column can contain NULLs.
Key points to remember
- An anti join returns rows of one table that have NO match in the other; SQL Server builds it from an outer join + WHERE ... IS NULL.
- LEFT ANTI JOIN: A LEFT JOIN B WHERE b.key IS NULL → rows only in A.
- RIGHT ANTI JOIN: A RIGHT JOIN B WHERE a.key IS NULL — or swap the tables and use LEFT JOIN.
- FULL ANTI JOIN: A FULL JOIN B WHERE a.key IS NULL OR b.key IS NULL → all leftovers from both sides.
- Always test the OTHER table's primary key for NULL, never a column that can be NULL on its own.
- NOT EXISTS is a safe alternative; NOT IN silently fails when NULLs are involved.
- Anti joins are ideal for data-quality checks: orphans, typos, missing references.
Try it yourself
LEFT ANTI JOIN: players with no valid team
This example runs a small setup step first — click to see it
INSERT INTO players (id, name, team, country, runs) VALUES (7, 'Shubman Gill', 'GT2', 'India', 600), (8, 'Ishan Kishan', NULL, 'India', 450);
Runs on a fresh IPL practice database in your browser · edit the query and press Ctrl + Enter
RIGHT ANTI JOIN and its LEFT JOIN twin
Runs on a fresh IPL practice database in your browser · edit the query and press Ctrl + Enter
FULL ANTI JOIN: all the leftovers
This example runs a small setup step first — click to see it
INSERT INTO players (id, name, team, country, runs) VALUES (7, 'Shubman Gill', 'GT2', 'India', 600), (8, 'Ishan Kishan', NULL, 'India', 450);
Runs on a fresh IPL practice database in your browser · edit the query and press Ctrl + Enter
Practice: NOT EXISTS vs NOT IN
This example runs a small setup step first — click to see it
INSERT INTO players (id, name, team, country, runs) VALUES (7, 'Shubman Gill', 'GT2', 'India', 600), (8, 'Ishan Kishan', NULL, 'India', 450);
Runs on a fresh IPL practice database in your browser · edit the query and press Ctrl + Enter
Revision Flashcards
Tap a card to flip it and see the answer.
