【问题标题】:Duplicates on Self Left Join自左连接上的重复项
【发布时间】:2016-04-16 04:45:36
【问题描述】:

我正在尝试将存储在垂直模型中的数据表转换为更水平的、类似于 SQL Server 表的模型。不幸的是,由于数据的性质,我不能在这里使用真实数据,所以我设计了一个遵循相同模型的通用示例。

表共有三列,一个 ID、列 ID 和值,其中 ID 和列 ID 构成主键。此外,不需要任何数据(即 ID 可以缺少列 ID = 3 而不会破坏任何内容)

PetID | ColumnID | Value
---------------------------
1     | 1        | Gilda
1     | 2        | Cat
2     | 1        | Sonny
2     | 2        | Cat
2     | 3        | Black

由于主键是两列的组合,我不能使用内置的 PIVOT 功能,所以我尝试做一个自我 LEFT JOIN:

SELECT T1.PetID
    ,T2.Value AS [Name]
    ,T3.Value AS [Type]
    ,T4.Value AS [Color]
FROM @Temp AS T1
LEFT JOIN @Temp AS T2 ON T1.PetID = T2.PetID
    AND T2.ColumnID = 1
LEFT JOIN @Temp AS T3 ON T1.PetID = T3.PetID
    AND T3.ColumnID = 2
LEFT JOIN @Temp AS T4 ON T1.PetID = T4.PetID
    AND T4.ColumnID = 3;

我想从 T1 获取 ID,然后执行自 LEFT JOIN 以通过 ColumnID 获取每个值。但是我在数据中得到了重复:

PetID | Name  | Type | Color
------------------------------
1     | Gilda | Cat  | NULL
1     | Gilda | Cat  | NULL
2     | Sonny | Cat  | Black
2     | Sonny | Cat  | Black
2     | Sonny | Cat  | Black

我可以使用 DISTINCT 删除这些重复项,但数据集相当大,因此所需的排序操作极大地减慢了查询速度。有没有更好的方法来实现这一点,还是我只是遇到了一个缓慢的查询?

【问题讨论】:

  • 很可能有更好的方法来完成连接部分,但我相信 GROUP BY 比 DISTINCT 更快,您可以使用它来删除重复项。
  • @GendoIkari,是的,但 GROUP BY 仍将在后台使用排序并减慢查询速度。我真的在寻找一种更有效的方式来进行连接,但感谢您的建议。
  • 加入会带来更多的行,但你有一些有趣的答案可以绕过加入
  • 你真的在使用表变量吗?如果是这样,请尝试使用#temp。您可以使用表变量获得糟糕的执行计划。表变量通常不是很多行。
  • @Frisbee,真实数据使用SQL Server中的一个表。为简单起见,该示例仅使用表变量。

标签: sql-server duplicates left-join distinct self-join


【解决方案1】:

您可以使用CASE 语句并完全避免连接。

SELECT
    PetID,
    MAX(CASE WHEN ColumnID = 1 THEN Value ELSE NULL END) AS Name,
    MAX(CASE WHEN ColumnID = 2 THEN Value ELSE NULL END) AS Type,
    MAX(CASE WHEN ColumnID = 3 THEN Value ELSE NULL END) AS Color
FROM @Temp
GROUP BY PetId

PetID, ColumnID 必须是您的主键才能正常工作。否则同一个ColumnID多次使用同一个PetID会出问题

【讨论】:

  • 我会试试这个。谢谢你的建议!
  • 我尝试了提供的几个答案,这个提供了真实数据中最快的执行时间。
【解决方案2】:

如果您愿意,可以使用 pivot..

SELECT  *
FROM    (SELECT PetID,
                (CASE ColumnID
                   WHEN 1 THEN 'Name'
                   WHEN 2 THEN 'Type'
                   WHEN 3 THEN 'Color'
                 END) ValueType,
                VALUE
         FROM   @Temp
        ) t 
PIVOT
(   MAX(Value) 
    FOR ValueType IN ([Name],[Type],[Color]) 
) p

没有 Sub 查询的另一种方法是..

SELECT  PetID,
        [1] [Name],
        [2] [Type],
        [3] [Color]
FROM    @Temp
PIVOT
(   MAX(Value) 
    FOR ColumnID IN ([1],[2],[3]) 
) p

【讨论】:

  • 非常有趣!我会试试这个。谢谢你的建议。
  • 我用真实数据尝试了这个,另一个答案最终更快,但这是在未来查询中使用的好信息。
【解决方案3】:

我不明白您对排序的担忧。你有一个主键,所以你也有一个索引。这是正确的做法:

select
    PetID,
    min(case when ColumnID = 1 then Value end) as Name,
    min(case when ColumnID = 2 then Value end) as Type,
    min(case when ColumnID = 3 then Value end) as Color
from @Temp
group by PetID

修复您的重复内容很简单,并且可能还会提高性能:

FROM (select distinct PetID from @Temp) AS T1

【讨论】:

  • 排序的问题是它对 SQL Server 来说代价高昂,并且会减慢查询的执行速度,尤其是对于大型数据集。我的示例只是一个小样本,因为我的完整数据集非常大。除非绝对需要,否则通常认为最好不要进行排序。
  • 您的数据已经排序,因为您已经定义了主键。无论如何,四向连接如何比单次扫描更快?
【解决方案4】:
SELECT T1.PetID
    ,T1.Value AS [Name]
    ,T2.Value AS [Type]
    ,T3.Value AS [Color]
    --select *
FROM #Temp AS T1
LEFT JOIN #Temp AS T2 ON T1.PetID = T2.PetID
    AND T2.ColumnID = 2
LEFT JOIN #Temp AS T3 ON T1.PetID = T3.PetID
    AND T3.ColumnID = 3
where t1.ColumnID = 1

您的问题是您要加入具有多行的主表。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2020-02-12
    • 2011-05-13
    • 2015-08-02
    • 1970-01-01
    • 2011-12-22
    • 2013-03-02
    相关资源
    最近更新 更多