All articles

Types of SQL Server Joins with Examples

A join in SQL Server combines rows from two or more tables based on a related column. The type of join decides which rows survive: an INNER JOIN keeps only…

0 · log in to like, save & follow Share on LinkedIn Share on X

A join in SQL Server combines rows from two or more tables based on a related column. The type of join decides which rows survive: an INNER JOIN keeps only matching pairs, while the outer joins (LEFT, RIGHT, FULL) also keep rows that have no match on the other side. In this guide you will learn every join type in SQL Server 2022 with one running example — a Customers and Orders schema — plus self-joins, cross joins, the semi/anti-join patterns, how to find unmatched rows, joining on multiple columns, and a performance note. Every query uses the same seed data so you can copy them into SQL Server Management Studio or Azure Data Studio and see the exact result rows shown.

Types of SQL Server Joins with Examples

Set up the sample tables and seed data

Every query below runs against the same two tables. Create them once and seed a few rows, including a customer with no orders and an order tied to a customer we deliberately keep unmatched.

CREATE TABLE dbo.Customers
(
    CustomerId INT PRIMARY KEY,
    Name       NVARCHAR(60) NOT NULL,
    City       NVARCHAR(60) NOT NULL
);

CREATE TABLE dbo.Orders
(
    OrderId    INT PRIMARY KEY,
    CustomerId INT NULL,
    Amount     DECIMAL(10,2) NOT NULL
);

INSERT INTO dbo.Customers VALUES
    (1, N'Asha',  N'Pune'),
    (2, N'Ben',   N'London'),
    (3, N'Chen',  N'Singapore'); -- Chen has no orders

INSERT INTO dbo.Orders VALUES
    (100, 1, 250.00),
    (101, 1, 90.00),
    (102, 2, 400.00),
    (103, NULL, 60.00); -- order with no customer

INNER JOIN: keep only matching rows

INNER JOIN returns rows only where the join condition is true on both sides. Chen and the orphan order 103 disappear because neither has a partner.

SELECT c.Name, o.OrderId, o.Amount
FROM dbo.Customers AS c
INNER JOIN dbo.Orders AS o ON o.CustomerId = c.CustomerId;
Name  OrderId  Amount
----  -------  ------
Asha  100      250.00
Asha  101      90.00
Ben   102      400.00

LEFT (OUTER) JOIN: keep all left rows

LEFT OUTER JOIN (the OUTER keyword is optional) returns every row from the left table and fills in NULL where the right table has no match. Chen now appears with NULL order columns.

SELECT c.Name, o.OrderId, o.Amount
FROM dbo.Customers AS c
LEFT JOIN dbo.Orders AS o ON o.CustomerId = c.CustomerId;
Name  OrderId  Amount
----  -------  ------
Asha  100      250.00
Asha  101      90.00
Ben   102      400.00
Chen  NULL     NULL

RIGHT (OUTER) JOIN: keep all right rows

RIGHT OUTER JOIN is the mirror image: every row from the right table survives, with NULLs for missing left matches. Here order 103, whose CustomerId is NULL, appears with no customer name. In practice most teams standardize on LEFT JOIN and reorder the tables, because it reads more naturally.

SELECT c.Name, o.OrderId, o.Amount
FROM dbo.Customers AS c
RIGHT JOIN dbo.Orders AS o ON o.CustomerId = c.CustomerId;
Name  OrderId  Amount
----  -------  ------
Asha  100      250.00
Asha  101      90.00
Ben   102      400.00
NULL  103      60.00

FULL OUTER JOIN: keep unmatched rows from both sides

FULL OUTER JOIN combines the behaviour of LEFT and RIGHT: it returns matched rows once, plus unmatched rows from both tables padded with NULLs. You see Chen (customer without an order) and order 103 (order without a customer) in the same result set. This makes it the natural choice for reconciliation — comparing two data sets to find records that exist on one side but not the other.

SELECT c.Name, o.OrderId, o.Amount
FROM dbo.Customers AS c
FULL OUTER JOIN dbo.Orders AS o ON o.CustomerId = c.CustomerId;
Name  OrderId  Amount
----  -------  ------
Asha  100      250.00
Asha  101      90.00
Ben   102      400.00
Chen  NULL     NULL
NULL  103      60.00

SQL Server INNER JOIN and LEFT JOIN example queries against Customers and Orders

CROSS JOIN: every combination

A CROSS JOIN produces the Cartesian product — every left row paired with every right row — and takes no ON clause. With 3 customers and 4 orders it returns 12 rows (3 × 4). It is useful for generating combinations such as sizes × colors or dates × stores, and for building calendar or number tables. Guard against it appearing accidentally, though: an old-style comma join with a forgotten WHERE condition silently becomes a cross join and can explode row counts.

SELECT c.Name, o.OrderId
FROM dbo.Customers AS c
CROSS JOIN dbo.Orders AS o;   -- 3 x 4 = 12 rows

SELF JOIN: relate a table to itself

A self join joins a table to another instance of itself, using table aliases to tell the two copies apart. The classic case is an employee/manager hierarchy where ManagerId points back into the same Employees table. A LEFT JOIN keeps the top-level manager whose ManagerId is NULL, whereas an INNER JOIN would drop that root row.

CREATE TABLE dbo.Employees
(
    EmployeeId INT PRIMARY KEY,
    Name       NVARCHAR(60) NOT NULL,
    ManagerId  INT NULL
);
INSERT INTO dbo.Employees VALUES
    (1, N'Devi', NULL),  -- CEO, no manager
    (2, N'Eli',  1),
    (3, N'Fay',  2);

SELECT e.Name AS Employee, m.Name AS Manager
FROM dbo.Employees AS e
LEFT JOIN dbo.Employees AS m ON m.EmployeeId = e.ManagerId;
Employee  Manager
--------  -------
Devi      NULL
Eli       Devi
Fay       Eli

Find unmatched rows with LEFT JOIN ... IS NULL

To find rows that have no match — customers who never ordered — do a LEFT JOIN and filter for NULL on the right side's key. This is the anti-join pattern.

SELECT c.Name
FROM dbo.Customers AS c
LEFT JOIN dbo.Orders AS o ON o.CustomerId = c.CustomerId
WHERE o.CustomerId IS NULL;   -- returns Chen

Semi-joins and anti-joins with EXISTS / NOT EXISTS

A semi-join returns left rows that have at least one match, without duplicating them or pulling right-side columns. In T-SQL you express it with EXISTS. Its opposite, the anti-join, uses NOT EXISTS.

-- Semi-join: customers who have placed an order
SELECT c.Name FROM dbo.Customers AS c
WHERE EXISTS (SELECT 1 FROM dbo.Orders AS o
              WHERE o.CustomerId = c.CustomerId);

-- Anti-join: customers who have not
SELECT c.Name FROM dbo.Customers AS c
WHERE NOT EXISTS (SELECT 1 FROM dbo.Orders AS o
                  WHERE o.CustomerId = c.CustomerId);

EXISTS is often clearer than INNER JOIN + DISTINCT when you only need existence, and NOT EXISTS handles NULLs more safely than NOT IN. The SQL Server 2022 optimizer frequently produces the same plan for EXISTS and an equivalent join, so choose whichever reads best.

Join on multiple columns

When the relationship spans more than one column, AND the conditions together in the ON clause. This is common with composite keys or tenant-scoped data.

SELECT *
FROM dbo.OrderLines AS ol
INNER JOIN dbo.Products AS p
    ON p.TenantId  = ol.TenantId
   AND p.ProductId = ol.ProductId;

Performance and indexing note

Joins run fastest when the columns in the ON clause are indexed. Make sure foreign-key columns like Orders.CustomerId have a supporting index; the primary key already covers Customers.CustomerId. Without one, SQL Server may resort to hash or nested-loop scans over the full table.

CREATE INDEX IX_Orders_CustomerId ON dbo.Orders(CustomerId);

Keep ON conditions on raw columns rather than wrapping them in functions (ON UPPER(a.Code) = b.Code defeats the index). Put filters that belong to an outer table's match in the ON clause, and filters that restrict the final result in WHERE — mixing them up can silently turn a LEFT JOIN back into an inner one, because a WHERE predicate on the right table's column rejects the NULL-padded rows the outer join just produced.

When a join feels slow, inspect the actual execution plan (SET STATISTICS IO ON, or the graphical plan in SSMS). SQL Server 2022 picks between nested-loop, merge, and hash join operators based on row estimates and available indexes; a missing index or stale statistics is the usual reason it chooses a costly scan. Adding the covering index above and running UPDATE STATISTICS often turns a hash join over full scans into a cheap index seek.

Key takeaways

  • INNER JOIN keeps only matching pairs; LEFT, RIGHT, and FULL OUTER also keep unmatched rows padded with NULL.
  • LEFT JOIN ... WHERE right.key IS NULL is the idiomatic way to find rows with no match.
  • CROSS JOIN returns the Cartesian product and takes no ON clause.
  • A SELF JOIN uses two aliases of one table for hierarchies like employee/manager.
  • EXISTS (semi-join) and NOT EXISTS (anti-join) test for matches without duplicating rows.
  • Index the columns in your ON clause and keep them function-free so SQL Server 2022 can seek.

Frequently asked questions

What is the difference between INNER JOIN and LEFT JOIN?

INNER JOIN returns only rows that match in both tables. LEFT JOIN returns all rows from the left table and fills right-side columns with NULL when there is no match.

Is JOIN the same as INNER JOIN in SQL Server?

Yes. Writing JOIN with no qualifier means INNER JOIN. Similarly LEFT JOIN, RIGHT JOIN, and FULL JOIN are shorthands for their OUTER forms.

How do I find rows in one table that have no match in another?

Use a LEFT JOIN and filter WHERE otherTable.key IS NULL, or write NOT EXISTS with a correlated subquery. Both express an anti-join.

When should I use EXISTS instead of a JOIN?

Use EXISTS when you only need to know whether a match exists and do not want columns from the other table or duplicate rows. It avoids the DISTINCT you would otherwise add after an INNER JOIN.

Enjoyed this article? Get the best GeeksArray articles in your inbox — once a week, no spam, unsubscribe anytime.

Comments (0)

Log in to join the conversation.

No comments yet — be the first to share your thoughts.