During my work, I get a chance to review lots of T-SQL Procedures and Views and I often see that the SQL joins are misused in them. When I ask developers regarding this, it’s evident that most of the time it has been the case that they don’t have proper understanding of what each JOIN
exactly does or how it behaves, ultimately causing the SQL Procedure or the View to return an unexpected result set. Therefore, I thought of writing this blog post.
When we require fetching details from multiple tables, the JOIN
clause is there to the rescue. But in SQL Server, there are various types of JOIN
s which will cater to our requirement in different ways. So it’s very important to have a good understanding of these types of JOIN
s and their usage.
In SQL Server, the following types of JOIN
s are available:
INNER JOIN
OUTER JOIN
LEFT OUTER JOIN
RIGHT OUTER JOIN
FULL OUTER JOIN
CROSS JOIN
CROSS APPLY
OUTER APPLY
We will look into the aforementioned JOIN
s more closely. The scope of this article is to give a high-level idea on the aforementioned JOIN
s and the APPLY
operator in SQL Server.
To illustrate the aforementioned JOIN
s, I will use the following sample tables:
SalesRep
SalesDetails
RepRating
Settings
We consider a case where we have 5 Sales Reps and the details will be saved in ‘RepDetails
’ table and the sales transactions which they have done is recorded under ‘SalesDetails
’ table. In the SalesDetails
table, we have included few transactions which we don’t have a matching Sales Rep. Similarly, in the RepDetails
table, there are a couple of sales reps for which we don’t have any sales information.
CREATE TABLE RepDetails(
RepId INT
,RepName VARCHAR(30)
)
CREATE TABLE SalesDetails(
RepId INT
,SaleMonth VARCHAR(6)
,OrderNo VARCHAR(6)
,SaleValue MONEY
)
CREATE TABLE RepRating(
RepId INT
,Rate INT
,YearMonth VARCHAR(6)
)
CREATE TABLE Settings(
S_Id INT
,S_Desc VARCHAR(20)
,S_Value VARCHAR(20)
)
INSERT INTO RepDetails (
[RepId]
,[RepName]
) VALUES
(1,'Eugene Thomas')
,(2,'John Wheeler')
,(3,'Curtis Bailey')
,(4,'Jeffrey Garrett')
,(5,'Rosemarie Hubbard')
INSERT INTO SalesDetails (
[RepId]
,[SaleMonth]
,[OrderNo]
,[SaleValue]
)
VALUES
(7,'201607','XpyDy3',839)
,(1,'201607','NR0RTp',496)
,(4,'201607','4552T4',299)
,(6,'201607','GKhkyC',877)
,(4,'201606','iyK65Z',291)
,(6,'201606','NFCszW',446)
,(7,'201606','D238bN',135)
,(1,'201607','bERDXk',304)
,(7,'201608','nykZqB',935)
,(4,'201608','R7ea5v',352)
,(6,'201606','VVjIdo',407)
,(7,'201608','vtLT4z',977)
,(2,'201608','xnHTnO',416)
,(1,'201606','jFAJIm',674)
,(6,'201606','0Q011m',480)
INSERT INTO dbo.RepRating(
RepId
,Rate
,YearMonth
)
VALUES
(1,1,'201608')
,(3,2,'201608')
,(4,1,'201609')
,(2,2,'201609')
INSERT INTO dbo.Settings(
S_Id
,S_Desc
,S_Value
)
VALUES
(1,'LedgerMonth','201609')
,(2,'TaxRate','10%')
Note: During the illustration, I will refer to the table which is followed by the ‘FROM
’ clause as the ‘Left Table’ and the table which is followed by the JOIN
clause as the ‘Right Table’.
Inner Join / Join
When we join two or more tables using an INNER JOIN
, it will only return us the results when records can only be found on both left and right tables which will satisfy the condition we supply.

This can be illustrated using a Venn diagram as follows:

SELECT *
FROM
dbo.RepDetails AS RD
JOIN dbo.SalesDetails AS SD
ON SD.RepId = RD.RepId

Please note: We have sales reps having RepId
s 1,2,3,4, & 5. But in SalesDetails
table, we have sales details for RepId
s 1,2,4,6 &7. So when these tables are joined the RepId
s which reside on both tables, which are 1, 2, and 4 will return the details, ultimately giving us the aforementioned result set.
Left Outer Join / Left Join
In a LEFT OUTER JOIN
, unlike the INNER JOIN
, it will select all the records from the ‘Left’ table and based on the JOIN
condition, it will select any matching records from the ‘Right’ table and return us the results. If there are no matching details on the ‘Right’ table, columns on related to those rows will return as ‘NULL
’.

This can be shown using a Venn diagram as follows:

SELECT *
FROM
dbo.RepDetails AS RD
LEFT JOIN dbo.SalesDetails AS SD
ON SD.RepId = RD.RepId

Right Outer Join / Right Join
In a RIGHT OUTER JOIN
, it will select all records from the ‘Right’ table and based on the JOIN
condition, it will select any matching records from the left table and return. If there aren’t any matching records on the left table, it will return a ‘NULL
’ value.

This can be shown using a Venn diagram as follows:

SELECT *
FROM
dbo.SalesDetails AS SD
RIGHT JOIN dbo.RepDetails AS RD
ON SD.RepId = RD.RepId
Full Outer Join / Full Join
FULL OUTER JOIN
is kind of a mix of both LEFT
& RIGHT OUTER JOIN
s. It will return all rows from both ‘Left’ and ‘Right’ tables based on the JOIN
condition. When the details aren’t matched, it will return a NULL
value in those respective columns.

This can be shown using a Venn diagram as follows:

SELECT *
FROM
dbo.RepDetails AS RD
FULL OUTER JOIN dbo.SalesDetails AS SD
ON SD.RepId = RD.RepId
Cross Join
CROSS JOIN
will return a result set with the number of rows equal to rows in ‘Left’ table multiplied by the number of rows in ‘Right’ table. Usually, this behaviour is present when there’s no condition provided in the WHERE
condition. So each row in the left table is joined to each row in the right table. Usually, this behaviour is called ‘Cartesian Product’.

SELECT *
FROM
dbo.RepDetails AS RD
CROSS JOIN dbo.Settings AS S

But when some condition is provided via the WHERE
clause CROSS JOIN
will behave like an INNER JOIN
:
SELECT *
FROM
dbo.RepDetails AS RD
CROSS JOIN dbo.Settings AS S
WHERE
RD.RepId = S.S_Id

Note: In a CROSS JOIN
, it’s not possible to refer to a value in the Left table along with the right table. Example: the following code will result in an error.
SELECT *
FROM
dbo.RepDetails AS RD
CROSS JOIN (SELECT * FROM dbo.Settings AS S WHERE S.S_Id = RD.RepId ) AS ST
CROSS APPLY
behaves like an INNER JOIN
and OUTER APPLY
behaves like an OUTER JOIN
. But the main difference in APPLY
compared to the JOIN
is that the right side of the APPLY
operator can reference columns in the table which is on the left side. This is not possible in a JOIN
.
For example, suppose we need to fetch sales rep details along with the maximum sale record which they have done. So the following query is not possible since it is returning an error due to the aforementioned reason.
SELECT
*
FROM
dbo.RepDetails AS RD
JOIN(
SELECT TOP 1 *
FROM
dbo.SalesDetails AS SD
WHERE
RD.RepId = SD.RepId
ORDER BY
SD.SaleValue DESC
) AS SData
ON 1=1
It will result in an error:
Msg 4104, Level 16, State 1, Line 78
The multi-part identifier "RD.RepId" could not be bound.
The way to achieve this is by using an APPLY
.
Cross Apply
Considering the above requirement, we can use a CROSS APPLY
in order to achieve the aforementioned.
SELECT
*
FROM
dbo.RepDetails AS RD
CROSS APPLY(
SELECT TOP 1 *
FROM
dbo.SalesDetails AS SD
WHERE
RD.RepId = SD.RepId
ORDER BY
SD.SaleValue DESC
) AS SData

Noticed the above sample, you can see that it returned three records. But if you inspect closely, the SalesRep
table consists with five Reps. But CROSS APPLY
has only returned the maximum sales value if there’s a matching record on the table right side to the APPLY
operator. (Similar to an INNER JOIN
)
Outer Apply
Using OUTER APPLY
, we can achieved a similar result like CROSS APPLY
, but the difference is that even though there aren’t any matching records in the table right side to the APPLY
operator, still it will return all the rows from the left side table, will NULL
values for the columns in the right side table. We will consider the same query that we used in the above example, but changing the APPLY
to an OUTER APPLY
.
SELECT
*
FROM
dbo.RepDetails AS RD
OUTER APPLY(
SELECT TOP 1 *
FROM
dbo.SalesDetails AS SD
WHERE
RD.RepId = SD.RepId
ORDER BY
SD.SaleValue DESC
) AS SData

There are other capabilities which are possible using the APPLY
. This article explains these capabilities really well.
Hope this will help you to understand the JOIN
and the APPLY
operator in SQL Server and where it can be used precisely.