Types of SQL JOINs Explained With Examples
Relational databases like SQL Server store data across multiple tables linked by common key values. Therefore, it's often necessary to retrieve records from two or more related tables based on specific conditions. To accomplish this, SQL Server uses the JOIN clause.
A JOIN is an SQL clause that allows you to query and retrieve data from multiple tables based on logical relationships between them. In simple terms, it tells SQL Server how to use data from one table to find matching rows in another.
Master JOINs with dbForge SQL Complete
As you progress as a database user, you'll continually need to fetch and combine data from more than one table using SQL Server JOINs. At this point, dbForge SQL Complete comes to the aid. Its code completion works excellently even for complex JOIN statements.
dbForge SQL Complete suggests a full JOIN clause based on foreign keys or conditions based on column names. These suggestions are available after the JOIN and ON keywords.
Moreover, dbForge SQL Complete can prompt a complete SQL JOIN statement when you combine tables based on foreign keys. Additionally, you can select a JOIN statement from the prompt list manually for a specific JOIN operation.
Let's have a look at various JOIN types created using dbForge SQL Complete.
Basic SQL JOIN types
SQL Server supports several JOIN types, including INNER JOIN, SELF JOIN, CROSS JOIN, and OUTER JOIN.
OUTER JOINs can further be divided into LEFT OUTER JOINs, RIGHT OUTER JOINs, and FULL OUTER JOINs.
Each JOIN type defines the way two tables are related in a query.
- SQL INNER JOIN combines all rows that have matching values in two or more tables.
- SQL LEFT OUTER JOIN combines all rows from the left table and only the matching rows from the right table.
- SQL RIGHT OUTER JOIN combines all records from the right table and only the matching rows from the left table.
- SQL SELF JOIN joins the table to itself for comparing rows within the same table.
- SQL CROSS JOIN combines each row of the first table with each row of the second table.
INNER JOIN
An INNER JOIN returns only the records (or rows) that have matching values in both tables. It is used to retrieve data that exists in both related tables.
The simplest query example is:
SELECT
c.Name
,o.Product
FROM Customers c
INNER JOIN Orders o
ON c.CustomerID = o.CustomerID;
- When to use: Use SQL INNER JOIN only when you need rows that exist in both tables.
- Pitfalls: The query excludes unmatched rows, which can cause "missing data" issues. If you need to preserve unmatched rows from one table, use LEFT JOIN instead.
- Example scenario: Compile the list of customers who placed orders, including customer names and product names.
SELECT
p.FirstName + ' ' + p.LastName AS CustomerName,
pr.Name AS Product
FROM Sales.Customer c
INNER JOIN Person.Person p
ON c.PersonID = p.BusinessEntityID
INNER JOIN Sales.SalesOrderHeader soh
ON c.CustomerID = soh.CustomerID
INNER JOIN Sales.SalesOrderDetail sod
ON soh.SalesOrderID = sod.SalesOrderID
INNER JOIN Production.Product pr
ON sod.ProductID = pr.ProductID;
OUTER JOIN
An OUTER JOIN returns a result that includes both matching and unmatching rows from the left and right tables. If no matching rows exist for the row in the left table, the columns of the right table will contain NULLs. Correspondingly, the columns of the left table will contain NULLs if there are no matching rows for the row in the right table.
The simplest query example is:
SELECT c.Name, o.Product
FROM Customers c
FULL OUTER JOIN Orders o ON c.CustomerID = o.CustomerID;
- When to use: Use the FULL OUTER JOIN when you need a complete overview of all records, matched or not.
- Pitfalls: Large result sets filled with NULLs can make analysis harder and cause low performance. To avoid this, use the FULL JOIN query only when absolutely needed; otherwise, stick to LEFT JOIN or INNER JOIN for efficiency.
- Example scenario: Retrieve the list of all products in orders even if the customer information is missing.
SELECT
p.FirstName + ' ' + p.LastName AS CustomerName,
pr.Name AS Product
FROM Sales.Customer c
FULL OUTER JOIN Person.Person p
ON c.PersonID = p.BusinessEntityID
FULL OUTER JOIN Sales.SalesOrderHeader soh
ON c.CustomerID = soh.CustomerID
FULL OUTER JOIN Sales.SalesOrderDetail sod
ON soh.SalesOrderID = sod.SalesOrderID
FULL OUTER JOIN Production.Product pr
ON sod.ProductID = pr.ProductID;
LEFT OUTER JOIN
A LEFT OUTER JOIN returns all rows from the left table and the matching rows from the right table. If there are no matches in the right table, the result will still include the left table's rows, with NULL values for the columns from the right table.
The simplest query example is:
SELECT c.Name, o.Product
FROM Customers c
LEFT JOIN Orders o ON c.CustomerID = o.CustomerID;
- When to use: Use the SQL LEFT OUTER JOIN when you want to include all rows from the left table, even without matches.
- Pitfalls: This query may produce NULL values for missing matches. Reports or calculations may break if NULLs aren't handled. To avoid this, use COALESCE() or ISNULL() to replace NULLs with default values, e.g., COALESCE(o.Product, 'No Orders').
- Example scenario: Get the list of all customers, even if they have not placed any orders (in that case, the Product column for the customer will contain NULL).
SELECT
p.FirstName + ' ' + p.LastName AS CustomerName,
pr.Name AS Product
FROM Sales.Customer c
LEFT JOIN Person.Person p
ON c.PersonID = p.BusinessEntityID
LEFT JOIN Sales.SalesOrderHeader soh
ON c.CustomerID = soh.CustomerID
LEFT JOIN Sales.SalesOrderDetail sod
ON soh.SalesOrderID = sod.SalesOrderID
LEFT JOIN Production.Product pr
ON sod.ProductID = pr.ProductID;
RIGHT OUTER JOIN
A RIGHT OUTER JOIN operates on the same principle as the LEFT OUTER JOIN, but in reverse. It returns all records from the right table and the matching records from the left table. If a row in the right table has no corresponding match in the left table, the result will still include that row, but with NULL values.
The simplest query example is:
SELECT c.Name, o.Product
FROM Customers c
RIGHT JOIN Orders o ON c.CustomerID = o.CustomerID;
- When to use: SQL RIGHT OUTER JOINS are helpful in scenarios where you want to include all rows from the right table, even if there's no match.
- Pitfalls: This query is less intuitive than LEFT JOIN. Developers may confuse the two. It is advisable to standardize on LEFT JOIN in your team and reorder tables when needed—it makes queries easier to read.
- Example scenario: Return the list of all products that appear in orders. If the person information is missing, CustomerName will be NULL.
SELECT
p.FirstName + ' ' + p.LastName AS CustomerName,
pr.Name AS Product
FROM Person.Person p
RIGHT JOIN Sales.Customer c
ON p.BusinessEntityID = c.PersonID
RIGHT JOIN Sales.SalesOrderHeader soh
ON c.CustomerID = soh.CustomerID
RIGHT JOIN Sales.SalesOrderDetail sod
ON soh.SalesOrderID = sod.SalesOrderID
RIGHT JOIN Production.Product pr
ON sod.ProductID = pr.ProductID;
SELF JOIN
A SELF JOIN allows joining a table with itself. In this type of JOIN, each row is combined with other rows in the same table, as if the table were duplicated. However, it does not copy the table physically because SQL uses table aliases to create distinct references to the same table.
The simplest query example is:
SELECT e.Name AS Employee, m.Name AS Manager
FROM Employees e
LEFT JOIN Employees m ON e.ManagerID = m.EmpID;
- When to use: Use the SQL SELF JOIN when you want to compare or link rows in the same table.
- Pitfalls: With this JOIN type, it is easy to lose track of aliases (e vs m) and NULL rows may complicate reporting. To avoid this, always use clear aliases (Employee, Manager) and handle NULLs explicitly.
- Example scenario: Match each employee with the manager. If the employee has no manager (belongs to the top management), the column will contain NULLs.
SELECT
epp.FirstName + ' ' + epp.LastName AS Employee,
mpp.FirstName + ' ' + mpp.LastName AS Manager
FROM HumanResources.Employee e
LEFT JOIN HumanResources.Employee m
ON e.OrganizationNode.GetAncestor(1) = m.OrganizationNode
LEFT JOIN Person.Person epp
ON e.BusinessEntityID = epp.BusinessEntityID
LEFT JOIN Person.Person mpp
ON m.BusinessEntityID = mpp.BusinessEntityID;
CROSS JOIN
A CROSS JOIN in SQL, also known as a Cartesian join, combines each row from the first table with every row from the second table. The result is a set of all possible combinations between the two tables. Unlike other types, a CROSS JOIN does not require a joining condition.
The simplest query example is:
SELECT c.Color, s.Size
FROM Colors c
CROSS JOIN Sizes s;
- When to use: Use the SQL CROSS JOIN when you want to generate combinations of all values between two tables.
- Pitfalls: Result size grows quickly (rows = table1 × table2), which can crash queries if tables are large. Therefore, use it only for small lookup tables or with filters (WHERE clause) to control output size.
- Example scenario: Generate all possible combinations of color and size with no additional JOIN conditions.
SELECT
c.Color,
s.Size
FROM
(SELECT DISTINCT Color
FROM Production.Product
WHERE Color IS NOT NULL) AS c
CROSS JOIN
(SELECT DISTINCT Size
FROM Production.Product
WHERE Size IS NOT NULL) AS s;
SQL JOINs summary
| Type | Syntax | When to use | Returns | Example |
|---|---|---|---|---|
| INNER JOIN |
SELECT * FROM A INNER JOIN B ON A.id = B.id; |
When you need only rows that exist in both tables | Rows with matching values in both tables | Find customers who placed orders |
| LEFT JOIN |
SELECT * FROM A LEFT JOIN B ON A.id = B.id; |
When you want all rows from the left table, even without matches | All rows from the left plus matches from the right; unmatched right rows = NULL | List all employees and their departments, even if some employees are not assigned |
| RIGHT JOIN |
SELECT * FROM A RIGHT JOIN B ON A.id = B.id; |
When you want all rows from the right table, even without matches | All rows from right plus matches from left; unmatched left rows = NULL | List all departments and their employees, even if some departments have no employees |
| FULL OUTER JOIN |
SELECT * FROM A FULL OUTER JOIN B ON A.id = B.id; |
When you need a complete set from both tables | All rows from both tables; unmatched rows filled with NULLs | Combine customer and supplier lists, showing all contacts |
| CROSS JOIN |
SELECT * FROM A CROSS JOIN B; |
When you need every combination of two tables (Cartesian product) | All possible row combinations | Generate all possible product and category combinations |
| SELF JOIN | SELECT a.*, b.* FROM Employees a JOIN Employees b ON a.ManagerID = b.EmpID; | When comparing rows within the same table (hierarchies, relationships) | Matches rows from the same table | Find employees who report to the same manager |
Conclusion
SQL JOINs are essential tools for retrieving and combining data from multiple tables in a database. Mastering them is a fundamental skill for any database professional. This article provides a clear overview of the basic types of SQL JOINs, their characteristics, and practical use cases, helping you grasp these core concepts quickly and with ease.
In addition, dbForge SQL Complete is a powerful assistant for working with JOINs. This robust add-in for SSMS helps you write SQL queries more efficiently. With advanced code suggestions and AI-powered query generation based on plain-language prompts, it simplifies even the most complex JOIN operations. You can try dbForge SQL Complete right away by downloading its fully functional trial and testing it with your own workload.
FAQ
Joining in SQL means retrieving data from two or more tables based on a common field. In other words, JOINs combine data from multiple tables in a result table based on a related column between those tables.
The purpose of JOINs in SQL is to access data from multiple tables based on logical relationships between them. JOINs are used to fetch data from database tables and represent the result dataset as a separate table.
There are four main types of JOINs in SQL: INNER JOIN, OUTER JOIN, CROSS JOIN, and SELF JOIN. OUTER JOINs can be further divided into three subtypes: LEFT OUTER JOIN, RIGHT OUTER JOIN, and FULL OUTER JOIN.
1. Forgetting the ON clause
When you join two tables without an ON condition, every row from the first table is paired with every row from the second table. Let us consider the below Customers and Orders input tables.
| CustomerID | Name |
|---|---|
| 1 | Alice |
| 2 | Bob |
| OrderID | CustomerID | Product |
|---|---|---|
| 101 | 1 | Laptop |
| 102 | 2 | Phone |
Wrong query:
SELECT *
FROM Customers, Orders;
Output (Cartesian product):
| CustomerID | Name | OrderID | CustomerID | Product |
|---|---|---|---|---|
| 1 | Alice | 101 | 1 | Laptop |
| 1 | Alice | 102 | 2 | Phone |
| 2 | Bob | 101 | 1 | Laptop |
| 2 | Bob | 102 | 2 | Phone |
Problem: You get 4 rows instead of 2, with meaningless combinations.
Correct query:
SELECT c.Name, o.Product
FROM Customers c
JOIN Orders o ON c.CustomerID = o.CustomerID;
2. Wrong JOIN condition
If you run a JOIN query on unrelated columns, you'll either lose matches or create wrong ones.
Wrong query:
SELECT *
FROM Customers c
JOIN Orders o ON c.CustomerID = o.OrderID;
Why it's wrong:
- CustomerID values are 1, 2.
- OrderID values are 101, 102.
- Since these numbers don't overlap, no rows will match, and you'll get an empty result set.
Correct query:
SELECT c.Name, o.Product
FROM Customers c
JOIN Orders o ON c.CustomerID = o.CustomerID;
3. Not using aliases
When both tables have a column with the same name, the database won't know which one you mean if you don't prefix it.
Wrong query:
SELECT CustomerID, Name, Product
FROM Customers
JOIN Orders ON CustomerID = CustomerID;
This fails with "ambiguous column name" because CustomerID exists in both tables.
Correct query (using aliases):
SELECT c.CustomerID, c.Name, o.Product
FROM Customers c
JOIN Orders o ON c.CustomerID = o.CustomerID;
4. Joining on non-indexed columns
Sometimes your JOIN query might work but run very slowly on large datasets.
Example:
SELECT c.Name, o.Product
FROM Customers c
JOIN Orders o ON c.CustomerID = o.CustomerID;
On large tables, the database may scan the entire Orders table for each CustomerID, which is very slow.
To fix this, add an index:
CREATE INDEX idx_orders_customerid
ON Orders(CustomerID);
INNER JOIN
Suppose, we have two tables: Customers (with CustomerID, Name, and CountryID columns) and Orders (with OrderID, CustomerID, and OrderDate columns). Using the INNER JOIN we can create a result table containing the CustomerID, Name, OrderID, and OrderDate columns. In such a simple way, we will be able to see who and when created an order. The tables will be matched based on the common CustomerID column, and the unmatched row won't get into the result dataset.
LEFT OUTER JOIN
Suppose, we have the Products and Orders tables that have a common ProductId column. With the help of the LEFT OUTER JOIN we can combine the two tables in a result table that will contain all the rows from the Products table and in case no records match from the Orders table, the NULL values will be shown in the OrderID column. In such a simple way, we will be able to see which products have not been ordered yet.
RIGHT OUTER JOIN
The RIGHT OUTER JOIN works similarly to the LEFT OUTER JOIN, but in the opposite direction. Let's consider the Vendors and Orders tables. Using the RIGHT OUTER JOIN we can retrieve records for all vendors showing their purchase orders. In case a vendor hasn't placed any orders yet, NULLs will be displayed in the PurchaseOrderId column.
Both JOIN and UNION serve to combine data; however, their distinction lies in the manner in which they execute this operation. For a full explanation, refer to UNION vs. UNION ALL in SQL Server.
SQL Server supports up to 256 tables in a single query using JOINs. However, in practice, queries with more than 5–7 JOINs are usually complex and harder to maintain. Consider breaking them into smaller queries or using Common Table Expressions (CTEs) for readability and performance.
A LEFT JOIN keeps all rows from the left table and adds matching rows from the right table, filling unmatched rows with NULL.
A RIGHT JOIN does the opposite: it keeps all rows from the right table and brings in the matches from the left.
Rule of thumb: LEFT = keep left, RIGHT = keep right.
Yes. JOINs can significantly affect query performance, especially on large datasets.
Problems often occur when:
- Joining on non-indexed columns
- Using FULL JOIN or CROSS JOIN on large tables
- Joining too many tables without filters
To improve performance:
- Create indexes on join keys
- Use only the necessary columns in SELECT
- Filter data early with WHERE clauses