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_idcustomer.customer_id。由于customer_id 是表连接所在的列,payment.customer_idcustomer.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】:

仅仅因为两列使用相等测试匹配,并不意味着它们具有相同的值。

这两列可以是不同的类型,例如整数和浮点数,或数字等。

或者他们可以是citextwhich 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 查询中的歧义的主要内容,如果未能解决你的问题,请参考以下文章

如何解决情感分析中的歧义?

字符串 - PHP 中的数组歧义

如何修复程序中的“歧义”where子句

消除多重继承中的类成员歧义

kotlin 中的重载分辨率歧义错误

隐式运算符 != 解决运算符中的歧义 ==