将具有相同ID的多行合并为一行

12

如何将具有相同 ID 的多行合并为一行。

当第一行和第二行中相同列的值相同时,或者第一行中存在值且第二行为 NULL 时,我想要进行合并。 当第一行和第二行中相同列的值不同时,我不想进行合并。

我有一个表:

ID |A    |B    |C
1   NULL  31    NULL
1   412   NULL  1
2   567   38    4
2   567   NULL  NULL
3   2     NULL  NULL
3   5     NULL  NULL
4   6     1     NULL
4   8     NULL  5
4   NULL  NULL  5

我想获取表格:

ID |A    |B    |C
1   412   31    1
2   567   38    4
3   2     NULL  NULL
3   5     NULL  NULL
4   6     1     NULL
4   8     NULL  5
4   NULL  NULL  5

3
你尝试过什么?如果有两行及以上的数据不同怎么办?你如何选择其中一行的数据而不是另一行? - Kritner
为什么在你期望的结果中,你保留了最后一行(4 NULL NULL 5)?难道它不应该与前一行(4 8 NULL 5)合并吗? - Vladimir Baranov
@VladimirBaranov 不应该合并,因为行(4 6 1 NULL)的第二个值 6 与下一行中的 8 冲突。根据所给的解释,那么该ID的任何行都不应该被合并。 - ErikE
准确地说,ID = 4的任何行都不应该被合并! - Hemus San
3个回答

7
我认为上述答案存在一个更简单的解决方案(同时也是正确的)。基本上,它获取可以在CTE内合并的合并值,然后将其与无法合并的数据合并。
WITH CTE AS (
    SELECT
        ID,
        MAX(A) AS A,
        MAX(B) AS B,
        MAX(C) AS C
    FROM dbo.Records
    GROUP BY ID
    HAVING MAX(A) = MIN(A)
        AND MAX(B) = MIN(B)
        AND MAX(C) = MIN(C)
)
    SELECT *
    FROM CTE
    UNION ALL
    SELECT *
    FROM dbo.Records
    WHERE ID NOT IN (SELECT ID FROM CTE)

SQL Fiddle: http://www.sqlfiddle.com/#!6/29407/1/0

的意思是SQL演练场,可以在网站上进行SQL语句的编辑和执行。请点击链接进入网站。


干得好,看到了更简单的方法。就我个人而言,“NOT IN”让我感到不安。使用“NOT EXISTS”似乎更好。 :) - ErikE
只要“NOT IN”使用的字段不为空,性能就会相同。在这种情况下,在主键上执行此操作是安全的。优化器通过生成LEFT ANTI SEMI JOIN来执行两者。如果我知道该字段具有空值,并且在实践中,对于未索引或varchars的字段,我肯定会避免使用它。 - Jason W
我一再看到使用NOT IN会导致开发人员采用与JOINEXISTS不同的思维方式,从长远来看,这种思维方式并不那么灵活,并且不能产生结构良好的查询(如果引擎没有将其转换为连接,则最终可能会出现性能问题)。 - ErikE

3
WITH Collapsed AS (
   SELECT
      ID,
      A = Min(A),
      B = Min(B),
      C = Min(C)
   FROM
      dbo.MyTable
   GROUP BY
      ID
   HAVING
      EXISTS (
         SELECT Min(A), Min(B), Min(C)
         INTERSECT
         SELECT Max(A), Max(B), Max(C)
      )
)
SELECT
   *
FROM
   Collapsed
UNION ALL
SELECT
   *
FROM
   dbo.MyTable T
WHERE
   NOT EXISTS (
      SELECT *
      FROM Collapsed C
      WHERE T.ID = C.ID
);

在 SQL Fiddle 中查看此示例的工作方式

该方法的实现方式是通过使用MinMax创建所有可合并的行--对于每个ID中的每一列,它们应该相同,并且有用地排除了NULL值--然后将无法合并的行附加到该列表中。对于当一个列对于一个ID具有全部的NULL值(因此 MinMax 都为 NULL 无法相等) 的情况,特殊技巧是使用EXISTS ... INTERSECT来实现, 它将功能视为 Min(A) = Max(A) AND Min(B) = Max(B) AND Min(C) = Max(C) ,但允许将NULL比较为相等。

下面是我早些时候提供的稍微不同的解决方案,可能具有不同的性能特征,而且更复杂,我不太喜欢,但是由于它是一个单一连续的查询(没有UNION),所以我更喜欢。

WITH Collapsible AS (
   SELECT
      ID
   FROM
      dbo.MyTable
   GROUP BY
      ID
   HAVING
      EXISTS (
         SELECT Min(A), Min(B), Min(C)
         INTERSECT
         SELECT Max(A), Max(B), Max(C)
      )
), Calc AS (
   SELECT
      T.*,
      Grp = Coalesce(C.ID, Row_Number() OVER (PARTITION BY T.ID ORDER BY (SELECT 1)))
   FROM
      dbo.MyTable T
      LEFT JOIN Collapsible C
         ON T.ID = C.ID
)
SELECT
   ID,
   A = Min(A),
   B = Min(B),
   C = Min(C)
FROM
   Calc
GROUP BY
   ID,
   Grp
;

以下内容也在上面的SQL Fiddle中。

这个查询与第一个查询使用类似的逻辑来计算一个组是否应该合并,然后使用此逻辑创建一个分组键,该键对于ID内的所有行都相同,或者对于ID内的所有行都不同。通过最终的MinMax同样有效),共享分组键的行将被合并,因为它们具有相同的分组键,而具有不同分组键的行不会被合并,因为它们在ID上具有不同的分组键。

根据您的数据集、索引、表大小和其他性能因素,这两个查询中的任何一个都可能表现更好,尽管第二个查询需要做一些工作才能追赶上第一个查询,因为它需要进行两次排序而非一次。


还没有完全查看,但是对于 HAVING Min(A) = Max(A) AND Min(B) = Max(B) AND Min(C) = Max(C),我可能会用 HAVING EXISTS (SELECT Min(A), Min(B), Min(C) INTERSECT SELECT Max(A), Max(B), Max(C)) 替换它,因为存在像 1, 2, NULL; 1, NULL, NULL 这样的情况(即在所有可折叠行中至少有一列为空)。 - Andriy M
你说得对,针对一个ID的全NULL列是存在的。现在我的更新是否更好了(请参见SQL fiddle)?我还考虑了NOT EXISTS (SELECT Min(A), Min(B), Min(C) EXCEPT SELECT Max(A), Max(B), Max(C))--您对此有什么评论吗(我很好奇是否有我忽略的情况导致它不能正常工作)。 - ErikE
现在一切看起来都很好。我正在考虑避免对“Collapsible”进行潜在重复评估的方法,并尝试替换您查询中不可折叠的部分,但最终我的想法需要使用UNION而不是UNION ALL。噢,好吧,我想这已经足够好了。 :) 无论如何,干得好。 - Andriy M
1
dba.se的人有时会嘲笑SO上的SQL问题,但其中一些确实提供了有趣的难题,就像这个。 :) - ErikE

0
你可以尝试这样做:
select 
isnull(t1.A, t2.A) as A,
isnull(t1.B, t2.B) as B,
isnull(t1.C, t2.C) as C
from
table_name t1
join table_name t2 on t1.ID = t2.ID and ..... 

您提到了“第一”和“第二”的概念。您如何定义这个顺序?请在这里放置定义条件:.....

此外,我假设每个ID值恰好有2行。


1
在这里使用COALESCE比ISNULL更好。 - Codeman
@Pheonixblade9 可能是的,是的。不过我怀疑这并不是这个问题的主要事情/重点。 - peter.petrov
没错,这就是为什么我留下了评论而不是添加自己的答案 - 你说得对,我只是认为 COALESCE 更好。 - Codeman
请重新阅读问题。行的顺序并不重要,因为如果任何一对行具有冲突值,则不会合并/折叠。我认为对问题的一个好答案将提供一个查询,可以处理两行以上,并且针对示例数据正确运行,而不是使用模糊的“在这里添加一些连接条件,使其全部正常工作”。这让我想起了然后是M.O. - ErikE

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接