Stefan Steiger Stefan Steiger - 6 days ago 5
SQL Question

SQL: When and why are two on conditions allowed?

Question:

I recently had an interesting SQL problem.

I had to get the leasing contract for a leasing object.

The problem was, there could be multiple leasing contracts per room, and multiple leasing object per room.

However, because of bad db tinkering, leasing contracts are assigned to the room, not the leasing object. So I had to take the contract number, and compare it to the leasing object number, in order to get the right results.

I thought this would do:

SELECT *
FROM T_Room

LEFT JOIN T_MAP_Room_LeasingObject
ON MAP_RMLOBJ_RM_UID = T_Room.RM_UID

LEFT JON T_LeasingObject
ON LOBJ_UID = MAP_RMLOBJ_LOBJ_UID

LEFT JOIN T_MAP_Room_LeasingContract
ON T_MAP_Room_LeasingContract.MAP_RMCTR_RM_UID = T_Room.RM_UID

LEFT JOIN T_Contracts
ON T_Contracts.CTR_UID = T_MAP_Room_LeasingContract.MAP_RMCTR_CTR_UID
AND T_Contracts.CTR_No LIKE ( ISNULL(T_LeasingObject.LOBJ_No, '') + '.%' )

WHERE ...


However, because the mapping table gets joined before I have the contract number, and I cannot get the contract number without having the mapping table, i have doubled entries.

The problem is a little more complicated, as rooms having no leasing contract needed also to show up, so I couldn't just use an inner join.

With a little bit experimenting, I found that this works as expected:

SELECT *
FROM T_Room

LEFT JOIN T_MAP_Room_LeasingObject
ON MAP_RMLOBJ_RM_UID = T_Room.RM_UID

LEFT JON T_LeasingObject
ON LOBJ_UID = MAP_RMLOBJ_LOBJ_UID

LEFT JOIN T_MAP_Room_LeasingContract

LEFT JOIN T_Contracts
ON T_Contracts.CTR_UID = T_MAP_Room_LeasingContract.MAP_RMCTR_CTR_UID
ON T_MAP_Room_LeasingContract.MAP_RMCTR_RM_UID = T_Room.RM_UID
AND T_Contracts.CTR_No LIKE ( ISNULL(T_LeasingObject.LOBJ_No, '') + '.%' )

WHERE ...


I now see why the two on conditions in one join, which usually are courtesy of query designer, can be useful, and what difference it makes.

I was wondering whether this is a MS-SQL/T-SQL specific thing, or whether this is standard sql.

So I tried in PostgreSQL with another 3 tables.

So I wrote this query on 3 other tables:

SELECT *
FROM t_dms_navigation

LEFT JOIN t_dms_document
ON NAV_DOC_UID = DOC_UID

LEFT JOIN t_dms_project
ON PJ_UID = NAV_PJ_UID


and tried to turn it into one with two on conditions

SELECT *
FROM t_dms_navigation

LEFT JOIN t_dms_document

LEFT JOIN t_dms_project
ON PJ_UID = NAV_PJ_UID
ON NAV_DOC_UID = DOC_UID


So I thought it's t-sql specific, but quickly tried in MS-SQL too, just to find to my surprise that it doesn't work there either.

I thought it might be because of missing foreign keys, so i removed them on all tables in my room query, but it still did not work.

So here my question:

Why are 2 on conditions even legal, does this have a name, and why does it not work on my second example ?

Answer

It's standard SQL. Each JOIN has to have a corresponding ON clause. All you're doing is shifting around the order that the joins happen in1 - it's a bit like changing the bracketing of an expression to get around precedence rules.

A JOIN B ON <cond1> JOIN C ON <cond2>

First joins A and B based on cond1. It then takes that combined rowset and joins it to C based on cond2.

A JOIN B JOIN C ON <cond1> ON <cond2>

First joins B and C based on cond1. It then takes A and joins it to the previous combined rowset, based on cond2.


It should work in PostgreSQL - here's the relevant part of the documentation of the SELECT statement:

where from_item can be one of:
[ ONLY ] table_name [ * ] [ [ AS ] alias [ ( column_alias [, ...] ) ] ]
( select ) [ AS ] alias [ ( column_alias [, ...] ) ]
with_query_name [ [ AS ] alias [ ( column_alias [, ...] ) ] ]
function_name ( [ argument [, ...] ] ) [ AS ] alias [ ( column_alias [, ...] | column_definition [, ...] ) ]
function_name ( [ argument [, ...] ] ) AS ( column_definition [, ...] )
from_item [ NATURAL ] join_type from_item [ ON join_condition | USING ( join_column [, ...] ) ]

It's that last line that's relevant. Notice that it's a recursive definition - what can be to the left and right of a join can be anything - including more joins.


1As always with SQL, this is the logical processing order - the system is free to perform physical processing in whatever sequence it feels will work best, provided the result is consistent.