SQL:何时以及为什么允许两个ON条件?

3

问题:

最近我遇到了一个有趣的SQL问题。
我需要获取租赁物品的租赁合同。

问题在于,一个房间可能有多个租赁合同,而一个租赁物品也可能对应多个租赁合同。

然而,由于数据库设计不当,租赁合同是分配到房间而不是租赁物品上的。因此我需要将合同编号与租赁物品编号进行比较,以获得正确的结果。

我认为这样做就可以:

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 ...

然而,由于映射表在我获得合同编号之前被连接,而我又不能没有映射表就获取合同编号,所以出现了重复条目。
问题有点复杂,因为需要显示没有租赁合同的房间,所以我不能只使用内连接。
经过一些实验,我发现这个方法可以正常工作:
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 ...

我现在明白了为什么一个联接中的两个条件(通常是查询设计器提供的)会很有用,以及它会带来什么不同。

我想知道这是 MS-SQL/T-SQL 特有的还是标准 SQL。

所以我在 PostgreSQL 中尝试了另外 3 张表。

于是我在另外 3 张表上编写了这个查询:

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 

并尝试将其转换为具有两个条件的一个

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 

我以为这是t-sql特有的问题,但很快在MS-SQL中试了一下,惊讶地发现它也不起作用。

我认为可能是由于缺少外键,所以我在我的查询中删除了所有表上的外键,但它仍然无法正常工作。

所以我的问题是:
为什么两个on条件是合法的,这有个名字吗?为什么它在我的第二个示例中不起作用?


1
“不起作用” - 你是否收到错误信息?它们可能会提供很有价值的信息。 - Damien_The_Unbeliever
错误信息:无效的对象名称“t_dms_document”。但是名称是正确的(正确的数据库目录也是如此),如果我执行select * from t_dms_document,我会得到结果。 - Stefan Steiger
1个回答

5

这是标准的SQL语言。每个JOIN操作都必须有一个对应的ON子句。你所做的只是改变连接发生的顺序1 - 这有点像改变表达式的括号顺序以规避优先级规则。

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

首先根据cond1AB进行连接。然后将这个组合的行集根据cond2C进行连接。

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

根据cond1BC连接起来,然后基于cond2A与之前组合的行集合并。


在PostgreSQL中应该可以工作-以下是SELECT语句的相关部分文档:

from_item可以是以下之一:
     [ONLY]table_name[*][[AS]alias[(column_alias[,…])]]
     (选择)[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[,...])]

最后一行是相关的。请注意,这是一个递归定义 - 可以是任何内容(包括更多的连接)在连接的左边和右边。


1始终如SQL所示,这是逻辑处理顺序 - 系统可以按照其认为最佳的顺序执行物理处理,只要结果一致。


啊,我明白了。在我的第二个例子中,表(b和c)之间没有连接条件,只有(a和b)以及(a和c)之间的连接条件,这是不可行的,因此出现了错误,这是正确的。 - Stefan Steiger

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接