【问题标题】:Odd FULL OUTER JOIN "ON" Clause?奇怪的 FULL OUTER JOIN "ON" 子句?
【发布时间】:2021-09-23 17:10:24
【问题描述】:

我遇到了一个有点令人困惑的问题。

简化查询:(假设ID是每个表的主键)

SELECT
A.ID,
A.Data,
B.Data,
C.Data
FROM
A FULL OUTER JOIN
B ON A.ID = B.ID FULL OUTER JOIN
C ON A.ID = C.ID

我使用FULL OUTER JOIN's 因为在我的情况下,不能保证任何ID 在所有三个表中,但如果它在多个表中,我想查看它的所有数据排成一排。

这是我很快意识到的问题:如果BC 两个表中都有一个ID(但不是A),那么您会遇到以下问题:

  1. ID 字段是 NULL,用于未出现在表 A 中的 ID。这是有道理的,因为查询选择了A.ID。我找到了一个非常简单的方法,那就是使用COALESCE(即COALESCE(A.ID,B.ID,C.ID))。

  2. 不在表A 中的ID 的数据在两个单独的行中返回。 (一行有NULL's for B's data,另一行有NULL's for C's data。)想了想,这也是有道理的,因为上面的查询方式是写的。 BC 两个表都基于表 A 连接,因此如果 ID 不在表 A 中,则查询与表 ID 中的 ID 连接没有关系BC。我也找到了解决这个问题的方法,即在ON 子句中明确指定与它之前的每个 表的关系,以OR 分隔。

因此进行以下更改将解决这两个问题:

SELECT
COALESCE(A.ID,B.ID,C.ID),
A.Data,
B.Data,
C.Data
FROM
A FULL OUTER JOIN
B ON A.ID = B.ID FULL OUTER JOIN
C ON A.ID = C.ID OR B.ID = C.ID

这很好用,但我花了一些时间才弄清楚这一点,以后遇到此查询的人员可能会很奇怪,因为在ON 子句中使用了COALESCE 和一堆OR乍一看似乎是多余的,但实际上两者都需要。

这对于较大的查询也可能会变得非常混乱,因为ON 子句的大小对于以这种方式连接的每个表来说都是复合的。

我的问题是:是否有其他内置方式或其他技巧来处理这种类型的 OUTER JOIN 已经考虑到这些您不需要为 INNER JOIN 考虑的额外条件s?

【问题讨论】:

  • 不,没有“内置方式”。不,我们不应该寻求“技巧”。您所描述的通常是外部联接的结果-而不仅仅是完全联接。 IME 使用 通常很少见,但我确实看到许多奇怪的或“冗长的”或“难以编写/解释”的查询,这些查询是架构受损的结果。在这里可以说你有 3 个不同的实体(表)代表同一个“事物”——一个规范化问题。
  • @SMor 谢谢,我不一定是在寻找“技巧”,我对当前的方式没意见,只是想知道是否还有其他方式,因为我发现这种策略特别不寻常。但是,是的,我同意这种类型的查询通常很少见,在这种情况下,三个表可能是同一个“东西”。不幸的是,在这种情况下,没有一个包含所有 ID 列表的“主”表,我意识到这通常是这种情况。这是我遇到的第一种情况并非如此。
  • 是的 - 有时模式会迫使我们在编写查询时手动生成有用的信息。但正如我所写,这通常是外连接的产物,是查询编写者需要理解和解决的问题。
  • 我相信另一种方法是通过每个表的键加入 A->B、A->C、B->C 组,并从每个表中选择 hte 最大值。将通过聚合消除每个键的重复项..我需要样本数据来测试它..或者可能基于键的数据来 PIVOT 数据....没有键,没有枢轴数据;但列仍会旋转...只要至少 1 个表有 1 条记录。
  • 是的,还有另一种方法 - UNION ALL 三个表。在 UNION ALL 之外,按键分组并使用各种聚合函数将 1-3 行归结为 1。仅当您感兴趣的列的完整列表在所有表中匹配时才有效

标签: sql-server sql-server-2008 outer-join


【解决方案1】:

这是另一种方式。它不一定或多或少复杂或高性能。你需要检查你的情况。如前所述,必须这样做可能表明存在建模问题。

SELECT ID, MAX(Data1), MAX(Data2), MAX(Data3)
FROM
(
SELECT A.ID, A.Data1, NULL, NULL
FROM A
UNION ALL
SELECT B.ID, NULL, B.Data2, NULL
FROM B
UNION ALL
SELECT C.ID, NULL, NULL, C.Data3
FROM C
) T
GROUP BY ID

【讨论】:

    【解决方案2】:

    想象一个交叉表查询,或者这样的枢轴:

    这是一个 excel 示例。

    这种方式不是联接,而是在 ID 列上进行旋转,并且数据是否存在于其他表中是无关紧要的;只要 1 个表有数据,该表中的值就会显示。

    这是我使用 Excel 的意思的可视化:

    SO ID 是您在每个表中的 ID。表是涉及的3个表。数据是与每个 ID 关联的值。通过旋转数据,您可以看到表 A 在所有 3 条记录中都有值,而 b 仅在记录 2 和 3 上具有值,而 c 仅在记录 1 和 2 上具有值。使用 SQL Server 中的动态数据透视表,我相信您可以完成一样。但是,这需要使用动态 SQL:示例:SQL Server dynamic PIVOT query?

    【讨论】:

      【解决方案3】:

      我认为这会做你想要的:

      FROM A FULL OUTER JOIN B 
        ON A.ID = B.ID 
      OUTER JOIN C ON A.ID = C.ID or B.ID = C.ID
      

      这将为您提供 A 和 B 中的所有行,以及 C 中与其中任何一个匹配的任何内容。

      如果它在多个表中,我想查看所有数据

      要排除ID 仅出现在一个表中的情况,请添加一堆指定允许组合的 WHERE 子句,

      where A.ID is not NULL and B.ID is not NULL 
         or B.ID is not NULL and C.ID is not NULL 
         or A.ID is not NULL and C.ID is not NULL 
           
      

      【讨论】:

        【解决方案4】:

        感谢@xQbert 提供数据。

        您可以使用 CTE(具有唯一 ID 列表)实现相同的目的,然后进行 CROSS APPLY。

        DECLARE @a table(id int, data int)
        DECLARE @b table(id int, data int)
        DECLARE @c table(id int, data int)
        
        insert into @a values(1, 9), (2, 8),(3,7)
        insert into @b values(2, 6), (3, 5)
        insert into @c values(1, 4), (2, 5)
        
        ;WITH CTE_IDs as
        (
        SELECT ID from @a
        UNION
        SELECT ID FROM @b
        UNION
        SELECT ID FROM @c
        )
        SELECT c.ID,t.* FROM CTE_IDs as c
        cross apply
        (
        VALUES((select data from @a where id = c.id),
        (select data from @b where id = c.id),
        (select data from @c where id = c.id)) 
        ) as t(a_data,b_data,c_data)
        
        ID a_data b_data c_data
        1 9 NULL 4
        2 8 6 5
        3 7 5 NULL

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2013-03-10
          • 2012-02-13
          • 2019-08-07
          • 2011-09-28
          • 2023-03-27
          • 2014-03-14
          • 2020-03-03
          相关资源
          最近更新 更多