【问题标题】:How to replace a functional (many) OUTER APPLY (SELECT * FROM)如何替换功能性(许多)OUTER APPLY(SELECT * FROM)
【发布时间】:2013-03-12 15:30:54
【问题描述】:

适用于 Microsoft SQL Server 2008 R2。

问题是

如果我们有几十个Outer Apply (30),那么它们的工作就会非常缓慢。在Outer Apply 的中间,我有一些比简单的选择更复杂的东西,一个视图。

详情

我正在编写一种分配给表(在数据库中)的属性。通常,一些表包含对属性表(键、值)的引用。

伪结构如下所示:

DECLARE @Lot TABLE (
LotId INT PRIMARY KEY IDENTITY, 
SomeText VARCHAR(8))

INSERT INTO @Lot
OUTPUT INSERTED.*
VALUES ('Hello'), ('World')

DECLARE @Attribute TABLE(
AttributeId INT PRIMARY KEY IDENTITY, 
LotId INT, 
Val VARCHAR(8),
Kind VARCHAR(8))

INSERT INTO @Attribute
OUTPUT INSERTED.* VALUES 
(1, 'Foo1', 'Kind1'), (1, 'Foo2', 'Kind2'), 
(2, 'Bar1', 'Kind1'), (2, 'Bar2', 'Kind2'), (2, 'Bar3', 'Kind3')

LotId       SomeText
----------- --------
1           Hello
2           World

AttributeId LotId       Val      Kind
----------- ----------- -------- --------
1           1           Foo1     Kind1
2           1           Foo2     Kind2
3           2           Bar1     Kind1
4           2           Bar2     Kind2
5           2           Bar3     Kind3

我现在可以运行如下查询:

SELECT 
[l].[LotId]
  , [SomeText]
  , [Oa1].[AttributeId]
  , [Oa1].[LotId]
  , 'Kind1Val' = [Oa1].[Val]
  , [Oa1].[Kind]
  , [Oa2].[AttributeId]
  , [Oa2].[LotId]
  , 'Kind2Val' = [Oa2].[Val]
  , [Oa2].[Kind]
  , [Oa3].[AttributeId]
  , [Oa3].[LotId]
  , 'Kind3Val' = [Oa3].[Val]
  , [Oa3].[Kind]  
FROM @Lot AS l
OUTER APPLY(SELECT * FROM @Attribute AS la WHERE la.[LotId] = l.[LotId] AND la.[Kind] = 'Kind1') AS Oa1
OUTER APPLY(SELECT * FROM @Attribute AS la WHERE la.[LotId] = l.[LotId] AND la.[Kind] = 'Kind2') AS Oa2
OUTER APPLY(SELECT * FROM @Attribute AS la WHERE la.[LotId] = l.[LotId] AND la.[Kind] = 'Kind3') AS Oa3


LotId       SomeText AttributeId LotId       Kind1Val Kind     AttributeId LotId       Kind2Val Kind     AttributeId LotId       Kind3Val Kind
----------- -------- ----------- ----------- -------- -------- ----------- ----------- -------- -------- ----------- ----------- -------- --------
1           Hello    1           1           Foo1     Kind1    2           1           Foo2     Kind2    NULL        NULL        NULL     NULL
2           World    3           2           Bar1     Kind1    4           2           Bar2     Kind2    5           2           Bar3     Kind3

简单的方法来获取属性值的数据透视表以及不具有此类属性的Lot行的结果Kind3。 我知道微软PIVOT,它并不简单,不适合这里。

最后,什么会更快并得到相同的结果?

【问题讨论】:

    标签: sql-server sql-server-2008 pivot unpivot


    【解决方案1】:

    为了得到结果,你可以unpivot然后pivot数据。

    有两种方法可以执行此操作。首先可以使用UNPIVOTPIVOT函数:

    select *
    from
    (
        select LotId,
            SomeText,
            col+'_'+CAST(rn as varchar(10)) col,
            value
        from
        (
            select l.LotId, 
                l.SomeText,
                cast(a.AttributeId as varchar(8)) attributeid,
                cast(a.LotId as varchar(8)) a_LotId,
                a.Val,
                a.Kind,
                ROW_NUMBER() over(partition by l.lotid order by a.attributeid) rn
            from @Lot l
            left join @Attribute a
                on l.LotId = a.LotId
        ) src
        unpivot
        (
            value
            for col in (attributeid, a_Lotid, val, kind)
        ) unpiv
    ) d
    pivot
    (
        max(value)
        for col in (attributeid_1, a_LotId_1, Val_1, Kind_1,
                    attributeid_2, a_LotId_2, Val_2, Kind_2,
                    attributeid_3, a_LotId_3, Val_3, Kind_3)
    ) piv
    

    SQL Fiddle with Demo

    或者从 SQL Server 2008+ 开始,您可以使用 CROSS APPLYVALUES 子句来取消透视数据:

    select *
    from
    (
        select LotId,
            SomeText,
            col+'_'+CAST(rn as varchar(10)) col,
            value
        from
        (
            select l.LotId, 
                l.SomeText,
                cast(a.AttributeId as varchar(8)) attributeid,
                cast(a.LotId as varchar(8)) a_LotId,
                a.Val,
                a.Kind,
                ROW_NUMBER() over(partition by l.lotid order by a.attributeid) rn
            from @Lot l
            left join @Attribute a
                on l.LotId = a.LotId
        ) src
        cross apply
        (
            values ('attributeid', attributeid),('LotId', a_LotId), ('Value', Val), ('Kind', Kind)
        ) c (col, value)
    ) d
    pivot
    (
        max(value)
        for col in (attributeid_1, LotId_1, Value_1, Kind_1,
                    attributeid_2, LotId_2, Value_2, Kind_2,
                    attributeid_3, LotId_3, Value_3, Kind_3)
    ) piv
    

    SQL Fiddle with Demo

    unpivot 过程为每个 LotIDSomeText 获取多个列,并将其转换为给出结果的行:

    | LOTID | SOMETEXT |           COL | VALUE |
    --------------------------------------------
    |     1 |    Hello | attributeid_1 |     1 |
    |     1 |    Hello |       LotId_1 |     1 |
    |     1 |    Hello |       Value_1 |  Foo1 |
    |     1 |    Hello |        Kind_1 | Kind1 |
    |     1 |    Hello | attributeid_2 |     2 |
    

    我在内部子查询中添加了一个row_number(),用于创建新的列名以进行透视。创建名称后,可以将枢轴应用于新列,给出最终结果

    这也可以使用动态 SQL 来完成:

    DECLARE @cols AS NVARCHAR(MAX),
        @query  AS NVARCHAR(MAX)
    
    select @cols = STUFF((SELECT ',' + QUOTENAME(col+'_'+rn) 
                        from 
                        (
                          select 
                            cast(ROW_NUMBER() over(partition by l.lotid order by a.attributeid) as varchar(10)) rn
                          from Lot l
                          left join Attribute a
                              on l.LotId = a.LotId
                        ) t
                        cross apply (values ('attributeid', 1),
                                     ('LotId', 2), 
                                     ('Value', 3), 
                                     ('Kind', 4)) c (col, so)
                        group by col, rn, so
                        order by rn, so
                FOR XML PATH(''), TYPE
                ).value('.', 'NVARCHAR(MAX)') 
            ,1,1,'')
    
    
    set @query = 'SELECT LotId,
                        SomeText,' + @cols + ' 
                 from 
                 (
                    select LotId,
                        SomeText,
                        col+''_''+CAST(rn as varchar(10)) col,
                        value
                    from
                    (
                        select l.LotId, 
                            l.SomeText,
                            cast(a.AttributeId as varchar(8)) attributeid,
                            cast(a.LotId as varchar(8)) a_LotId,
                            a.Val,
                            a.Kind,
                            ROW_NUMBER() over(partition by l.lotid order by a.attributeid) rn
                        from Lot l
                        left join Attribute a
                            on l.LotId = a.LotId
                    ) src
                    cross apply
                    (
                        values (''attributeid'', attributeid),(''LotId'', a_LotId), (''Value'', Val), (''Kind'', Kind)
                    ) c (col, value)
                ) x
                pivot 
                (
                    max(value)
                    for col in (' + @cols + ')
                ) p '
    
    execute(@query)
    

    SQL Fiddle with Demo

    所有三个版本都会给出相同的结果:

    | LOTID | SOMETEXT | ATTRIBUTEID_1 | LOTID_1 | VALUE_1 | KIND_1 | ATTRIBUTEID_2 | LOTID_2 | VALUE_2 | KIND_2 | ATTRIBUTEID_3 | LOTID_3 | VALUE_3 | KIND_3 |
    -----------------------------------------------------------------------------------------------------------------------------------------------------------
    |     1 |    Hello |             1 |       1 |    Foo1 |  Kind1 |             2 |       1 |    Foo2 |  Kind2 |        (null) |  (null) |  (null) | (null) |
    |     2 |    World |             3 |       2 |    Bar1 |  Kind1 |             4 |       2 |    Bar2 |  Kind2 |             5 |       2 |    Bar3 |  Kind3 |
    

    【讨论】:

    • 你是个关键极客! ('!-)
    • @Kaf 我更喜欢书呆子,但我知道。
    • +1 表示拒绝使用 geek 这个词,特别是考虑到一些更丰富多彩的历史用法(参见 oed.com/view/Entry/77307?rskey=fHnqwr&result=1#eid
    • SET STATISTICS IO ON 在您的解决方案中给出:扫描计数 3,逻辑读取 15 VS 扫描计数 1,逻辑读取 5。我现在将尝试使用真实数据。
    • @Kaf 我非常同意:D 我称她为枢轴女神
    猜你喜欢
    • 2013-12-05
    • 1970-01-01
    • 2014-02-14
    • 2018-01-15
    • 2014-02-05
    • 1970-01-01
    • 2018-02-04
    • 2019-02-11
    • 2022-12-01
    相关资源
    最近更新 更多