Introduction to JOINs in SQL Server: INNER, OUTER and CROSS JOIN

This introduction to JOINs started with a request. When we launched the Gandhinagar SQL Server User Group, I was the key speaker, and the room was full of people eager to learn. Some members asked me to write a simple article on JOINs and their types. Here it is, with a real result for every JOIN.

All the JOIN types at a glance as small red Venn diagrams: INNER, LEFT, RIGHT, FULL, the three IS NULL forms, and CROSS JOIN as 5 times 6

What a JOIN Decides

A JOIN puts rows from two tables together by comparing a column in each. The rows that match are the easy part. Each type of JOIN is a different answer to one question: what happens to the rows with no partner?

The Two Sample Tables

Every example here uses the same two small tables. Run this in any test database. Every result in this post comes from SQL Server 2025.

CREATE TABLE Table1 (ID INT, Value VARCHAR(10))
INSERT INTO Table1 (ID, Value)
SELECT 1,'First'
UNION ALL
SELECT 2,'Second'
UNION ALL
SELECT 3,'Third'
UNION ALL
SELECT 4,'Fourth'
UNION ALL
SELECT 5,'Fifth'
GO

CREATE TABLE Table2 (ID INT, Value VARCHAR(10))
INSERT INTO Table2 (ID, Value)
SELECT 1,'First'
UNION ALL
SELECT 2,'Second'
UNION ALL
SELECT 3,'Third'
UNION ALL
SELECT 6,'Sixth'
UNION ALL
SELECT 7,'Seventh'
UNION ALL
SELECT 8,'Eighth'
GO

Table1 with IDs 1 to 5 and Table2 with IDs 1, 2, 3, 6, 7 and 8. IDs 1, 2 and 3 are highlighted because they are in both tables

INNER JOIN

This join returns rows when there is at least one match in both the tables. Anything without a match stays out of the result.

Venn diagram of INNER JOIN: only the overlap of Table1 and Table2 is shaded, with the query below it

SELECT t1.*, t2.*
FROM Table1 t1
INNER JOIN Table2 t2 ON t1.ID = t2.ID

INNER JOIN result: 3 rows, IDs 1, 2 and 3, the IDs found in both tables

LEFT OUTER JOIN

There are three different outer join methods, and this is the first. It returns all the rows from the left table along with the matching rows from the right table. If there’s no match in the right table, it returns NULL values.

Venn diagram of LEFT OUTER JOIN: all of Table1 is shaded, including the overlap, with the query below it

SELECT t1.*, t2.*
FROM Table1 t1
LEFT JOIN Table2 t2 ON t1.ID = t2.ID

LEFT JOIN result: 5 rows. IDs 1, 2 and 3 are matched, and IDs 4 and 5 show NULL on the right side

RIGHT OUTER JOIN

This one mirrors the left join. It returns all the rows from the right table along with the matching rows from the left table. If there’s no match in the left table, it returns NULL values.

Venn diagram of RIGHT OUTER JOIN: all of Table2 is shaded, including the overlap, with the query below it

SELECT t1.*, t2.*
FROM Table1 t1
RIGHT JOIN Table2 t2 ON t1.ID = t2.ID

RIGHT JOIN result: 6 rows. IDs 1, 2 and 3 are matched, and IDs 6, 7 and 8 show NULL on the left side

FULL OUTER JOIN

This join combines the left outer join and the right outer join. It returns the rows from both tables, and it returns NULL values wherever there’s no match.

Venn diagram of FULL OUTER JOIN: both circles are fully shaded, with the query below it

SELECT t1.*, t2.*
FROM Table1 t1
FULL OUTER JOIN Table2 t2 ON t1.ID = t2.ID

FULL OUTER JOIN result: 8 rows. IDs 1, 2 and 3 are matched, IDs 4 and 5 have NULL on the right, and IDs 6, 7 and 8 have NULL on the left

CROSS JOIN

This is a Cartesian join, and it doesn’t need any condition to join. The number of rows in the result is the row count of one table multiplied by the row count of the other. With 5 rows and 6 rows, we get 30.

Diagram of CROSS JOIN: Table1 with 5 rows and Table2 with 6 rows, with lines joining every row to every row. Row 1 is in red, pairing with all 6 rows, for 30 rows in all

SELECT t1.*, t2.*
FROM Table1 t1
CROSS JOIN Table2 t2

CROSS JOIN result: 5 times 6 equals 30 rows. The first 6 rows show ID 1 paired with each of the 6 rows of Table2

Finding the Rows With No Match

Outer joins are useful for more than keeping rows. You’ll see many developers write a query like this to find rows that are missing from another table:

SELECT t1.*
FROM Table1 t1
WHERE t1.ID NOT IN (SELECT t2.ID FROM Table2 t2)

An outer join with a WHERE clause gives the same result here, and it’s the form I recommend. You’ll see why in the first trap below.

SELECT t1.*, t2.*
FROM Table1 t1
LEFT JOIN Table2 t2 ON t1.ID = t2.ID
WHERE t2.ID IS NULL

Venn diagram of LEFT OUTER JOIN with WHERE NULL: only the part of Table1 outside the overlap is shaded

You can write the same idea with a right outer join:

SELECT t1.*, t2.*
FROM Table1 t1
RIGHT JOIN Table2 t2 ON t1.ID = t2.ID
WHERE t1.ID IS NULL

Venn diagram of RIGHT OUTER JOIN with WHERE NULL: only the part of Table2 outside the overlap is shaded

Remember, the term Not Inner Join doesn’t exist in database terminology. But a full outer join with a WHERE condition gives the exact opposite of an inner join. It returns every row that an inner join would leave out.

SELECT t1.*, t2.*
FROM Table1 t1
FULL OUTER JOIN Table2 t2 ON t1.ID = t2.ID
WHERE t1.ID IS NULL OR t2.ID IS NULL

Venn diagram of FULL OUTER JOIN with WHERE NULL: both circles are shaded except the overlap

Results of the three IS NULL queries: LEFT JOIN returns IDs 4 and 5, RIGHT JOIN returns IDs 6, 7 and 8, and FULL JOIN returns all five rows

Three Traps Worth Knowing

These three come up again and again, and readers have asked about them in the comments here. I ran each one on the same tables.

Trap 1: NOT IN and a single NULL. Add one row with a NULL ID to Table2, and NOT IN returns nothing at all, not even IDs 4 and 5. The left join form still returns both rows. NOT EXISTS also still returns both.

INSERT INTO Table2 (ID, Value) VALUES (NULL, 'Unknown')

SELECT t1.*
FROM Table1 t1
WHERE t1.ID NOT IN (SELECT t2.ID FROM Table2 t2)

SELECT t1.*, t2.*
FROM Table1 t1
LEFT JOIN Table2 t2 ON t1.ID = t2.ID
WHERE t2.ID IS NULL

DELETE FROM Table2 WHERE ID IS NULL

Trap 2: A WHERE filter can undo your LEFT JOIN. Put a filter on the right table in the ON clause, and you keep all 5 left rows. Move the same filter to WHERE, and you get 1 row, because NULL fails the test. I go deeper in A WHERE Filter That Turns Your LEFT JOIN Into an INNER JOIN.

SELECT t1.*, t2.*
FROM Table1 t1
LEFT JOIN Table2 t2 ON t1.ID = t2.ID AND t2.Value = 'First'

SELECT t1.*, t2.*
FROM Table1 t1
LEFT JOIN Table2 t2 ON t1.ID = t2.ID
WHERE t2.Value = 'First'

Trap 3: Duplicates multiply. A reader asked what happens when both tables hold the value 1 three times. Each row matches all three on the other side, so the inner join returns 9 rows, and so do the left and right joins. As another reader noted, Venn diagrams show which rows qualify, not how many come back.

CREATE TABLE Dup1 (ID INT)
CREATE TABLE Dup2 (ID INT)
INSERT INTO Dup1 VALUES (1),(1),(1)
INSERT INTO Dup2 VALUES (1),(1),(1)

SELECT a.ID, b.ID
FROM Dup1 a
INNER JOIN Dup2 b ON a.ID = b.ID

DROP TABLE Dup1
DROP TABLE Dup2

Quick reference card: Three JOIN Traps

Which JOIN Do I Need?

Start from the rows you want back, and the choice gets easy. Here’s the short version.

Decision guide. Rows in both tables: INNER JOIN. All rows from one table: LEFT JOIN. All rows from both: FULL JOIN. Rows missing from the other table: LEFT JOIN with WHERE IS NULL. Every pair: CROSS JOIN

Quick reference card: JOINs in One Card

Clean Up

When you finish, remove the two sample tables:

DROP TABLE Table1
DROP TABLE Table2

Where to Go Next

That’s the end of this introduction to JOINs. These posts go further:

I hope this article fulfills its purpose. Please tell me in the comments where you’d like me to take it next.

A JOIN is not a way to combine tables, it is a decision about the rows with no partner.

Published by Pinal Dave on SQLAuthority. More of my work at pinaldave.com.


Discover more from SQL Authority with Pinal Dave

Subscribe to get the latest posts sent to your email.

Best Practices, Database, SQL Joins, SQL Scripts, SQL User Group
Previous Post
System Configuration Checker WMI Error: Fixing SQL Server Setup
Next Post
SQL SERVER – Check if Current Login is Part of Server Role Member

Related Posts

401 Comments. Leave new

  • sir ,in full outer join can we use this query to get result as same as your’s ?
    select * from table t1 full outer join table t2 on t1.colt2.col

    Reply
  • SELF-join is when two instances of the same table or view is joined together.

    Reply
  • Hi Pinal,

    I have a requirement where I am not able to achieve it. Please help.
    I am having 2 tables, one with 1000 records (Left Table) and other with 100 records (Right Table).
    When we try performing a Left outer join based on certain conditions, the output has more number of rows compared to the input 1000 records of the Left Table.
    I can understand that a Left outer Join can produce more number of records than the input.
    But our requirement is,
    1. The output should contain exactly same number of rows as that of the left table provided, whether the condition satisfies or not. But the condition has to be checked for sure.

    Thanks
    Dasarathy D R

    Reply
  • yashwanth K S
    May 24, 2017 7:32 pm

    its very usefull

    Thank you

    Regards
    Yashwanth K S

    Reply
  • Hi Pinal,

    Following is my query. I need to understand the difference between below two queries. Just assignment of fields is reversed.

    Query 1:

    SELECT t1.*,t2.*
    FROM Table1 t1
    INNER JOIN Table2 t2 ON t1.ID = t2.ID

    Query 2:

    SELECT t1.*,t2.*
    FROM Table1 t1
    INNER JOIN Table2 t2 ON t2.ID = t1.ID

    Which one is correct and why ?. Is there any performance or any other issue if we use second one.

    Reply
  • that’s useful article.

    Reply

Leave a Reply

Your email address will not be published. Required fields are marked *

Fill out this field
Fill out this field
Please enter a valid email address.