【问题标题】:Ambiguity in the SQL querySQL 查询中的歧义
【发布时间】: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 引擎如何知道您更喜欢哪个(公共)列,而无需对单个表进行任何寻址?

标签: sql postgresql


【解决方案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 指的是哪个表。

          【讨论】:

            猜你喜欢
            • 2011-05-10
            • 1970-01-01
            • 2010-10-05
            • 2013-09-12
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多