在同一张桌子上加入两次的最佳方式是啥?

Posted

技术标签:

【中文标题】在同一张桌子上加入两次的最佳方式是啥?【英文标题】:What's the best way to join on the same table twice?在同一张桌子上加入两次的最佳方式是什么? 【发布时间】:2011-05-15 03:40:46 【问题描述】:

这有点复杂,但我有 2 张桌子。假设结构是这样的:

*Table1*
ID
PhoneNumber1
PhoneNumber2

*Table2*
PhoneNumber
SomeOtherField

可以根据 Table1.PhoneNumber1 -> Table2.PhoneNumber 或 Table1.PhoneNumber2 -> Table2.PhoneNumber 连接表。

现在,我想获得一个结果集,其中包含 PhoneNumber1、SomeOtherField 对应于 PhoneNumber1、PhoneNumber2 和 SomeOtherField 对应于 PhoneNumber2。

我想到了 2 种方法来做到这一点 - 要么在表上加入两次,要么在 ON 子句中使用 OR 加入一次。

方法一

SELECT t1.PhoneNumber1, t1.PhoneNumber2, 
   t2.SomeOtherFieldForPhone1, t3.someOtherFieldForPhone2
FROM Table1 t1
INNER JOIN Table2 t2
   ON t2.PhoneNumber = t1.PhoneNumber1
INNER JOIN Table2 t3
   ON t3.PhoneNumber = t1.PhoneNumber2

这似乎行得通。

方法二

以某种方式有一个看起来有点像这样的查询 -

SELECT ...
FROM Table1
INNER JOIN Table2 
   ON Table1.PhoneNumber1 = Table2.PhoneNumber OR
      Table1.PhoneNumber2 = Table2.PhoneNumber

我还没有让这个工作,我不确定是否有办法做到这一点。

完成此任务的最佳方法是什么?这两种方法似乎都不简单或直观......有没有更直接的方法来做到这一点?这个要求一般是如何实现的?

【问题讨论】:

【参考方案1】:

首先,我会尝试重构这些表,以避免将电话号码用作自然键。我不喜欢自然键,这是一个很好的例子。自然键,尤其是电话号码之类的东西,可以经常更改。当更改发生时更新数据库将是一个巨大的、容易出错的头痛。 *

方法 1 正如你所描述的那样,它是你最好的选择。由于命名方案和短别名,它看起来有点简洁,但是......当涉及多次加入同一个表或使用子查询等时,别名是你的朋友。

我会稍微清理一下:

SELECT t.PhoneNumber1, t.PhoneNumber2, 
   t1.SomeOtherFieldForPhone1, t2.someOtherFieldForPhone2
FROM Table1 t
JOIN Table2 t1 ON t1.PhoneNumber = t.PhoneNumber1
JOIN Table2 t2 ON t2.PhoneNumber = t.PhoneNumber2

我做了什么:

无需指定 INNER - 这是因为您没有指定 LEFT 或 RIGHT 的事实暗示 不要为主查找表添加 n 后缀 为您将多次使用的表别名添加 N 后缀以使其明显

*DBA 避免更新自然键的麻烦的一种方法是不指定主键和外键约束,这进一步加剧了不良数据库设计的问题。我实际上经常看到这种情况。

【讨论】:

我刚刚使用这个解决方案来解决我自己的问题。它有很大帮助。然而,在看到这个之前,我在任何可以看到需要相互连接的表的地方应用了主键和外键。为什么这是个坏主意? @volumeone - 我想你可能误解了我回答的最后一部分。主键和外键 是个好主意。避免它们是糟糕的做法、糟糕的设计,而且简直就是糟糕透顶。 完美..但为什么在这种情况下必须使用别名? 有没有办法做到这一点,而无需两次加入同一个表?也许在 where 子句中使用条件...【参考方案2】:

第一个很好,除非 Phone1 或(更有可能)phone2 可以为空。在这种情况下,您想使用左连接而不是内连接。

当您有一个包含两个电话号码字段的表格时,这通常是一个不好的迹象。通常这意味着您的数据库设计存在缺陷。

【讨论】:

好点!以后这会让我很头疼...谢谢!【参考方案3】:

您可以使用UNION 组合两个连接:

SELECT Table1.PhoneNumber1 as PhoneNumber, Table2.SomeOtherField as OtherField
  FROM Table1
  JOIN Table2
    ON Table1.PhoneNumber1 = Table2.PhoneNumber
 UNION
SELECT Table1.PhoneNumber2 as PhoneNumber, Table2.SomeOtherField as OtherField
  FROM Table1
  JOIN Table2
    ON Table1.PhoneNumber2 = Table2.PhoneNumber

【讨论】:

我想到了这个,但我需要将它作为单个非规范化记录返回... 哦,好吧,我假设几乎相反。如果是这种情况,那么我会使用类似于您的第一种方法的方法。我将编辑我的答案。【参考方案4】:

第一种方法是正确的方法,可以满足您的需要。但是,使用内部连接,如果Table2 中存在两个电话号码,您将只从Table1 中选择行。您可能想要执行LEFT JOIN 以便选择Table1 中的所有行。如果电话号码不匹配,则SomeOtherFields 将为空。如果你想确保至少有一个匹配的电话号码,你可以这样做WHERE t2.PhoneNumber IS NOT NULL OR t3.PhoneNumber IS NOT NULL

第二种方法可能会出现问题:如果Table2 同时具有PhoneNumber1PhoneNumber2 会发生什么?将选择哪一行?根据您的数据、外键等,这可能是也可能不是问题。

【讨论】:

【参考方案5】:

我的问题是即使不存在或只有一个电话号码也显示记录(完整的通讯录)。因此,我使用了一个 LEFT JOIN,它从左侧获取所有记录,即使右侧没有对应的记录。对我来说,这适用于 Microsoft Access SQL(它们需要括号!)

SELECT t.PhoneNumber1, t.PhoneNumber2, t.PhoneNumber3
   t1.SomeOtherFieldForPhone1, t2.someOtherFieldForPhone2, t3.someOtherFieldForPhone3
FROM 
(
 (
  Table1 AS t LEFT JOIN Table2 AS t3 ON t.PhoneNumber3 = t3.PhoneNumber
 )
 LEFT JOIN Table2 AS t2 ON t.PhoneNumber2 = t2.PhoneNumber
)
LEFT JOIN Table2 AS t1 ON t.PhoneNumber1 = t1.PhoneNumber;

【讨论】:

【参考方案6】:
SELECT
    T1.ID
    T1.PhoneNumber1,
    T1.PhoneNumber2
    T2A.SomeOtherField AS "SomeOtherField of PhoneNumber1",
    T2B.SomeOtherField AS "SomeOtherField of PhoneNumber2"  
FROM
    Table1 T1
    LEFT JOIN Table2 T2A ON T1.PhoneNumber1 = T2A.PhoneNumber
    LEFT JOIN Table2 T2B ON T1.PhoneNumber2 = T2B.PhoneNumber
WHERE
    T1.ID = 'FOO';

LEFT JOINJOIN 也返回相同的结果。使用 PostgreSQL 13.1.1 测试成功。

【讨论】:

以上是关于在同一张桌子上加入两次的最佳方式是啥?的主要内容,如果未能解决你的问题,请参考以下文章

Sequelize:两次加入同一张表

选择同一列两次,但条件不同

有条件加入同一张表两次

Laravel Eloquent Eager Loading:加入同一张表两次

SQL 查询优化:在事实表中两次使用相同指标的最佳方法是啥?

在一个 GPU 上加载 TRT 引擎以进行两次推理的最佳方法是啥?