SQL 查询中的歧义
Posted
技术标签:
【中文标题】SQL 查询中的歧义【英文标题】:Ambiguity in the SQL query 【发布时间】:2018-12-09 19:18:09 【问题描述】:有两个表:表customer
包含有关客户的信息,表payment
包含有关付款的信息。 customer
表中的主键customer_id
是表payment_id
中的外键。以下两个查询返回相同的结果:
SELECT
payment.customer_id,
last name,
amount
FROM customer
INNER JOIN payment ON customer.customer_id = payment.customer_id
SELECT
customer.customer_id,
last_name,
amount
FROM customer
INNER JOIN payment ON customer.customer_id = payment.customer_id
查询之间的唯一区别在于SELECT
子句中的第一个参数:payment.customer_id
与customer.customer_id
。由于customer_id
是表连接所在的列,payment.customer_id
和customer.customer_id
之间的区别似乎没有意义。但是,如果我尝试在查询中省略该表:
SELECT
customer_id,
last_name,
amount
FROM customer
INNER JOIN payment ON customer.customer_id = payment.customer_id
我收到了
[42702] 错误:列引用“customer_id”不明确
您能否描述一下查询中的歧义在哪里?
【问题讨论】:
DBMS 引擎如何知道您更喜欢哪个(公共)列,而无需对单个表进行任何寻址? 【参考方案1】:错误表示有两列同名customer_id
,让数据库引擎不知道你要查询哪一列。
您需要明确告诉数据库引擎您要查询的列的名称。
表可能会在创建表后添加一个新列,如果您没有在选择中明确指定查询的SELECT
表列,则新列可能与旧列名称相同原始查询出错。
给你一些建议
您可以给查询表一个别名,让您的查询更清晰。
在选择表名时明确指定查询的SELECT
表列,因为表
如果payment
表中的last_name
列和customer
中的amount
列
你可以这样做。
SELECT
c.customer_id,
p.last_name,
c.amount
FROM customer c
INNER JOIN payment p ON c.customer_id = p.customer_id
【讨论】:
【参考方案2】:INNER JOIN
等旧版联接会创建重复的列。在查询中使用INNER JOIN
会生成两个名为customer_id
的列。 SQL 语言对此有一个解决方法:您必须在列前加上一个范围变量,正如其他人在此处所建议的那样(尽管使用了具有误导性的术语“表别名”)。
谢天谢地,SQL 语言也解决了这个问题:NATURAL JOIN
不会创建重复的列,因此您不需要消除它们的歧义:
SELECT
customer_id,
last_name,
amount
FROM customer
NATURAL JOIN payment
产生重复列的联接仍然存在,因为从未从 SQL 语言中删除任何内容(“兼容性桎梏”)。但是除了NATURAL JOIN
,你不需要任何加入。
这个想法是,您的数据元素名称在整个数据字典中的含义相同,例如amount
表示一件事(与付款有关)和仅一件事(没有与客户或任何其他类型有关的 amount
)。
有时您可能需要将不想参与的专栏“投射出去”NATURAL JOIN
,例如
WITH
C AS ( SELECT customer_id, last_name FROM customer ),
P AS ( SELECT customer_id, amount FROM payment )
SELECT
customer_id,
last_name,
amount
FROM C
NATURAL JOIN P
这也可以“保护”您的代码,例如万一有人在付款中添加last_name
属性。
【讨论】:
【参考方案3】:仅仅因为两列使用相等测试匹配,并不意味着它们具有相同的值。
这两列可以是不同的类型,例如整数和浮点数,或数字等。
或者他们可以是citext
which does case insensitive comparisons(一张桌子可以有'RedRum'
和其他'redruM'
)。
通常连接条件可能不是严格相等(例如网络范围比较或前缀匹配)
在所有这些情况下,您将哪个表用于结果列很重要。
如果你在做一个外连接,表名又很重要。
Postgresql 不知道=
什么时候可以隐含,什么时候不能隐含,它总是需要它。
经验法则,当连接表时,指定您在查询中使用的每一列的表。这样,如果有人向另一个表添加一些列,事情就不会中断。
【讨论】:
【参考方案4】:您能否描述一下查询中的歧义在哪里?
从逻辑上讲,查询中没有歧义,因为两列必须具有相同的值。但是,当您使用LEFT JOIN
而不是INNER JOIN
时,可能会出现歧义,例如:
INSERT INTO customer (customer_id, last_name) VALUES
(1, 'Smith'),
(2, 'Jones');
INSERT INTO payment (customer_id, amount) VALUES
(1, 100);
SELECT
customer.customer_id,
payment.customer_id,
last_name,
amount
FROM customer
LEFT JOIN payment ON customer.customer_id = payment.customer_id
customer_id | customer_id | last_name | amount
-------------+-------------+-----------+--------
1 | 1 | Smith | 100
2 | | Jones |
(2 rows)
解析器只遵循一般规则,不会分析查询以找出潜在的歧义何时会暴露出来。
【讨论】:
【参考方案5】:最好始终在列前加上表/子查询别名。
但在您的情况下(两个表之间仅共享 PK/FK 名称)您也可以使用 USING
子句:
SELECT
customer_id,
last_name,
amount
FROM customer
JOIN payment USING(customer_id);
DBFiddle Demo
还有第三种可能的解决方案,但我强烈建议不要使用它:
SELECT
customer_id,
last_name,
amount
FROM customer
NATURAL JOIN payment
【讨论】:
我强烈推荐NATURAL JOIN
“解决方案”,因为我认为最好的做法是避免重复列,而不是使用范围变量来解决它们引起的问题。
@onedaywhen我更喜欢避免自然加入,here你可以找到一些原因。
@onedaywhen 随意使用任何最适合您的方法。在我看来,NATURAL JOIN
并不能解决任何问题,而且将来可能会导致更多问题。我的建议很简单:不要偷懒(为每一列使用表别名,从长远来看会有所回报)。
首先,所谓的“表别名”实际上是范围变量。这个想法是它们“覆盖”表中的行并代表一行,而不是表。因此,“表别名”是最不合适的。其次,范围变量是 1992 年以前的连接类型生成的重复列问题的解决方法。该问题在 1992 年通过 NATURAL JOIN
得到解决,这是一种替代旧连接类型的高级连接类型...
如果我选择使用 1992 年之前的连接类型,它会生成重复的列,然后我需要使用范围变量来解决问题。相反,我选择使用NATURAL JOIN
来完全避免这个问题。你对懒惰的指责太离谱了。【参考方案6】:
您通过省略 select 语句中的表格回答了您自己的问题。通过不指定,SQL 不知道customer_id
指的是哪个表。
【讨论】:
以上是关于SQL 查询中的歧义的主要内容,如果未能解决你的问题,请参考以下文章