在同一张桌子上加入两次的最佳方式是啥?
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
中的所有行。如果电话号码不匹配,则SomeOtherField
s 将为空。如果你想确保至少有一个匹配的电话号码,你可以这样做WHERE t2.PhoneNumber IS NOT NULL OR t3.PhoneNumber IS NOT NULL
第二种方法可能会出现问题:如果Table2
同时具有PhoneNumber1
和PhoneNumber2
会发生什么?将选择哪一行?根据您的数据、外键等,这可能是也可能不是问题。
【讨论】:
【参考方案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 JOIN
或 JOIN
也返回相同的结果。使用 PostgreSQL 13.1.1 测试成功。
【讨论】:
以上是关于在同一张桌子上加入两次的最佳方式是啥?的主要内容,如果未能解决你的问题,请参考以下文章
Laravel Eloquent Eager Loading:加入同一张表两次