SQL查询中的歧义

时间:2018-07-01 07:36:19

标签: sql postgresql

有两个表:表customer包含有关客户的信息,表payment包含有关付款的信息。 customer_id表中的主键customer是表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”不明确

能否请您描述一下查询中的歧义?

6 个答案:

答案 0 :(得分:2)

您通过省略select语句中的表来回答自己的问题。通过不指定它,SQL不知道customer_id引用哪个表。

答案 1 :(得分:2)

  

能否请您描述一下查询中的歧义?

从逻辑上讲,查询中没有歧义,因为两列必须具有相同的值。但是,当您使用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)

解析器仅遵循一般规则,并且不分析查询来找出何时可能出现歧义。

答案 2 :(得分:2)

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

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

或者它们可以是citext which does case insensitive comparisons(一个表可以包含'RedRum',另外一个'redruM')。

通常加入条件可能不是严格相等的(例如,网络范围比较或前缀匹配)

在所有这些情况下,您用于结果列的哪个表都很重要。

如果您正在执行外部联接表,则名称再次有意义。

Postgresql不知道何时=意味着可以隐含该表,何时不可以隐含该表,则始终需要它。

经验法则,在联接表时,请指定您在查询中使用的每一列的表。这样,如果有人在其他表中添加一些列,事情就不会中断。

答案 3 :(得分:1)

该错误表示有两列具有相同的名称customer_id,让数据库引擎不知道您要查询哪一列。

您需要明确告知数据库引擎要查询的列的名称。

在创建表之后,可以在表中添加一个新列,如果未在选择遗嘱中明确指定查询的SELECT个表列,则新列可能与旧列名相同。是原始查询的错误。

这是给你的一些建议

  • 您可以给查询表一个别名,让查询更清晰。

  • 由于表的原因,在表名的选择中明确指定查询的SELECT个表列

如果last_name表中的payment列和amount中的customer

您可以这样做。

SELECT
  c.customer_id,
  p.last_name,
  c.amount
FROM customer c
INNER JOIN payment p ON c.customer_id = p.customer_id

答案 4 :(得分:1)

一个好习惯是始终为表/子查询别名添加列前缀。

但是在您的情况下(两个表之间仅共享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

答案 5 :(得分:1)

旧式联接(例如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属性,则可能性很小。