【问题标题】:How to convert row to column basis with certain criteria in SQL Server?如何在 SQL Server 中使用特定条件将行转换为列?
【发布时间】:2017-04-08 12:57:09
【问题描述】:

我有一张表,每行记录都与其他行记录相关,有些行也与其他行无关。

我的记录是这样的

项目表格

ID          ItemLookupCode       UnitOfMeasure     ParentItem
--------------------------------------------------------------
111         100006C0005          CRT                 0
112         100006B0001          BAG                111       // this row is child of ID 111

221         100027C0002          CRT                 0
222         100027T0012          PCT                221
223         100027P0001          PC                 222

期待输出

我尝试了以下查询,它工作正常。有没有其他更好的性能解决方案:

SELECT DISTINCT 
    TOP (100) PERCENT dbo.Item.ID, 
    dbo.Item.ItemLookupCode, 
    dbo.Item.UnitOfMeasure, 
    Item_1.ID AS ChildID1, 
    Item_1.ItemLookupCode AS ChildItemLookupCode1, 
    Item_1.ParentItem AS ChildParentItem1, 
    Item_1.UnitOfMeasure AS ChildUOM1, 
    Item_2.ID AS ChildID2, 
    Item_2.ItemLookupCode AS ChildItemLookupCode2, 
    Item_2.UnitOfMeasure AS ChildUOM2, 
    Item_3.ID AS ChildID3, 
    Item_3.ItemLookupCode AS ChildItemLookupCode, 
    Item_3.UnitOfMeasure AS ChildUOM3
FROM           
    dbo.Item 
LEFT OUTER JOIN
    dbo.Item AS Item_1 ON dbo.Item.ID = Item_1.ParentItem 
LEFT OUTER JOIN
    dbo.Item AS Item_2 ON Item_1.ID = Item_2.ParentItem 
LEFT OUTER JOIN
    dbo.Item AS Item_3 ON Item_2.ID = Item_3.ParentItem 

【问题讨论】:

标签: sql sql-server join stored-procedures pivot


【解决方案1】:

我的猜测是 ParentItem 上没有索引。 并且在该字段上有 3 个连接,全表扫描使其变慢。
但是 ID 可能是主键,所以它被索引了。

如果不能在 ParentItem 上添加索引?
然后你可以通过一个在父级上有索引的临时表。

CREATE TABLE #tmpItem (ParentID int, ID int);

INSERT INTO #tmpItem (ParentID, ID)
SELECT ParentItem, ID
FROM dbo.Item;

CREATE CLUSTERED INDEX #IDX_C_tmpItem ON #tmpItem(ParentID);

SELECT --TOP (100) PERCENT 
Item_0.ID AS ID, 
Item_0.ItemLookupCode AS ItemLookupCode, 
Item_0.UnitOfMeasure AS UnitOfMeasure, 
Item_1.ID AS ChildID1, 
Item_1.ItemLookupCode AS ChildItemLookupCode1, 
Item_1.ParentItem AS ChildParentItem1, 
Item_1.UnitOfMeasure AS ChildUOM1, 
Item_2.ID AS ChildID2, 
Item_2.ItemLookupCode AS ChildItemLookupCode2,
Item_2.ParentItem AS ChildParentItem2, 
Item_2.UnitOfMeasure AS ChildUOM2, 
Item_3.ID AS ChildID3, 
Item_3.ItemLookupCode AS ChildItemLookupCode3,
Item_3.ParentItem AS ChildParentItem3, 
Item_3.UnitOfMeasure AS ChildUOM3
FROM (
    SELECT I0.ID as ID0, I1.ID as ID1, I2.ID as ID2, I3.ID as ID3
    FROM #tmpItem AS I0
    LEFT JOIN #tmpItem AS I1 ON (I0.ID  = I1.ParentID)
    LEFT JOIN #tmpItem AS I2 ON (I1.ID  = I2.ParentID)
    LEFT JOIN #tmpItem AS I3 ON (I2.ID  = I3.ParentID)
) Q
LEFT JOIN dbo.Item Item_0 ON Q.ID0 = Item_0.ID
LEFT JOIN dbo.Item Item_1 ON Q.ID1 = Item_1.ID
LEFT JOIN dbo.Item Item_2 ON Q.ID2 = Item_2.ID
LEFT JOIN dbo.Item Item_3 ON Q.ID3 = Item_3.ID;

下面只是一个使用递归查询的实验。
使用 ID 上的索引。
是的,我知道,它不会返回没有孩子的父母。请不要判断。

declare @Item table (ID int primary key, ItemLookupCode varchar(11), UnitOfMeasure varchar(3), ParentItem int);
insert into @Item (ID, ItemLookupCode, UnitOfMeasure, ParentItem) values 
(111,'100006C0005','CRT',0), (112,'100006B0001','BAG',111), 
(221,'100027C0002','CRT',0), (222,'100027T0012','PCT',221), (223,'100027P0001','PC',222), 
(224,'100027X0001','XX',223),(225,'100027Y0001','YY',223),
(226,'100027Z0001','ZZ',225);

WITH RCTE AS
(
   select ID as StartID, 0 as PrevID, 0 as Level, ID, ParentItem as ParentID, ItemLookupCode, UnitOfMeasure
   from @Item

   union all

   select RCTE.StartID, RCTE.ID, RCTE.Level + 1, t.ID, t.ParentItem, t.ItemLookupCode, t.UnitOfMeasure
   from RCTE
   join @Item t on (RCTE.ParentID = t.ID)
)
select 
max(case when ReverseLeveL = 0 then ID end) as ID0,
max(case when ReverseLeveL = 0 then ItemLookupCode end) as ItemLookupCode0,
max(case when ReverseLeveL = 0 then UnitOfMeasure end) as UnitOfMeasure0,
max(case when ReverseLeveL = 1 then ID end) as ID1,
max(case when ReverseLeveL = 1 then ItemLookupCode end) as ItemLookupCode1,
max(case when ReverseLeveL = 1 then UnitOfMeasure end) as UnitOfMeasure1,
max(case when ReverseLeveL = 2 then ID end) as ID2,
max(case when ReverseLeveL = 2 then ItemLookupCode end) as ItemLookupCode2,
max(case when ReverseLeveL = 2 then UnitOfMeasure end) as UnitOfMeasure2,
max(case when ReverseLeveL = 3 then ID end) as ID3,
max(case when ReverseLeveL = 3 then ItemLookupCode end) as ItemLookupCode3,
max(case when ReverseLeveL = 3 then UnitOfMeasure end) as UnitOfMeasure3
from (
   SELECT *, row_number() over (partition by StartID order by Level desc)-1 as ReverseLeveL
   from RCTE
   where Level <= 3
    ) Q
group by StartID
having max(case when ReverseLeveL = 1 then ID end) is not null;

【讨论】:

    【解决方案2】:

    您拥有的表格设计 - 父 ID 列 - 称为 邻接列表。它是分层数据。邻接列表是several ways 之一,用于表示 SQL 中的分层数据。

    关于你的问题...

    还有其他更好的性能解决方案吗?

    你的层次结构只有三层吗?伟大的!然后 3X SELF JOIN 就可以了。这是最直接且性能最好的方法。从 SQL 执行计划中添加任何missing indices 坚持这种方法。


    您的层次结构是否具有任意深度?为此,需要递归。您现在有几个选择。递归 CTE 是最简单的方法,我建议您使用它,除非性能太差。如果是,那么 Simple-Talk 做得很好here 比较了各种可用方法的性能:Recusive CTE、动态 SQL、While Loops 等......

    这有点深奥,但他们最终还是选择了一个异国情调的表现冠军。

    当遍历我们在八到八之间构建的层次结构时 十三级深,他基于集合的 WHILE 循环避免万圣节 使用 RECOMPILE 选项 (LAHPwR) 的保护通常是经过的 时间赢家。也许令人惊讶的是,非传统递归 功能似乎只比六点之间的 rCTE 好一点 和十二个级别。它与七点的基于集合的循环完全相关 级别。

    是的,有一些复杂的方法可以查询任意深度的邻接列表。他们中的一些人比其他人表现更好。

    代替奇异的查询方法,一种更简单的方法可能是仅以不同的分层格式保存数据的副本。可以更快地查询的一种。嵌套集已显示为orders of magnitudes faster,而不是邻接列表进行查询。 this article 的底部有一个 SP,它显示了如何将邻接列表转换为嵌套集。

    祝你好运!

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2022-08-04
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-12-04
      • 1970-01-01
      相关资源
      最近更新 更多