如何在 SQL 中创建矩阵/如何使用大量行进行透视

Posted

技术标签:

【中文标题】如何在 SQL 中创建矩阵/如何使用大量行进行透视【英文标题】:How to create a Matrix in SQL / How to Pivot with a lot of rows 【发布时间】:2019-09-27 18:43:16 【问题描述】:

我有一个简单的发票表,其中包含每件售出的商品及其售出日期。

我想创建一个矩阵,左侧是从上到下的商品编号列表,顶部是从 1 到 52 周的列表,中间是该商品的销售量那一周。

这是我目前所拥有的。它只是一个星期,项目编号,以及它在那一周卖出了多少作为一个简单的列表。

这里是查询:

SELECT 
*
FROM (
    SELECT 
    Week
    ,Item_Number
    ,Color_Code
    ,Count(1) Touches
    FROM (

        SELECT

         DATEPART (year, I.Date_Invoiced) Year
        ,DATEPART (month, I.Date_Invoiced) Month
        ,DATEPART (week, I.Date_Invoiced) Week
        ,DATEPART (day, I.Date_Invoiced) Day
        ,I.Invoice_Number
        ,I.Customer_Number
        ,I.Warehouse_Code
        ,S.Pack_Type
        ,S.Quantity_Per_Carton
        ,S.Inner_Pack_Quantity
        ,ID.Item_Number
        ,ID.Color_Code
        ,ID.Quantity
        ,case when s.Pack_Type='carton' then id.Quantity/s.Quantity_Per_Carton when  s.Pack_Type='Inner Poly'  then id.Quantity/s.Inner_Pack_Quantity end  qty
        ,ID.Line_Number

        FROM    Invoices I
                LEFT JOIN Invoices_Detail ID on I.Company_Code = ID.Company_Code and I.Division_Code = ID.Division_Code and I.Invoice_Number = ID.Invoice_Number
                LEFT JOIN Style S on I.Company_Code = S.Company_Code and I.Division_Code = S.Division_Code and ID.Item_Number = S.Item_Number and ID.Color_Code = S.Color_Code

        WHERE   1=1
                AND (I.Company_Code = @LocalCompanyCode OR @LocalCompanyCode IS NULL)  
                AND (I.Division_Code = @LocalDivisionCode OR @LocalDivisionCode IS NULL)
                AND (I.Warehouse_Code = @LocalWarehouse OR @LocalWarehouse IS NULL)
                AND (S.Pack_Type = @LocalPackType OR @LocalPackType IS NULL)
                AND (I.Customer_Number = @LocalCustomerNumber OR @LocalCustomerNumber IS NULL)
                AND (I.Date_Invoiced Between @LocalFromDate And @LocalToDate)
                AND Inner_Pack_Quantity > 1
    ) T
    GROUP BY Week, Item_Number, Color_Code

) TT
ORDER BY Week, Item_Number

还有一些示例数据:

+------+-----------------+------------+---------+
| Week |   Item_Number   | Color_Code | Touches |
+------+-----------------+------------+---------+
|    1 | 11073900LRGMO   |      02000 |       7 |
|    1 | 11073900MEDMO   |      02000 |       9 |
|    2 | 1114900011BMO   |      38301 |      62 |
|    2 | 1114910012BMO   |      21701 |     147 |
|    2 | 1114910012BMO   |      38301 |     147 |
|    2 | 1114910012BMO   |      46260 |     147 |
|    3 | 13MK430R03R     |      00101 |       2 |
|    3 | 13MK430R03R     |      10001 |       2 |
|    3 | 13MK430R03R     |      65004 |       8 |
|    3 | 13MK430R03S     |      00101 |       2 |
|    3 | 13MK430R03S     |      10001 |       2 |
+------+-----------------+------------+---------+

我正在查看一些关于如何进行 Pivot 的指南,但他们似乎都说您需要指定每个列名,这在我的情况下会非常困难,因为我有数百个项目而且我不知道它们都是什么会提前。

【问题讨论】:

【参考方案1】:

您可以尝试使用动态查询来完成这项工作,这样您就不必提前知道报告中包含多少周。我使用您的示例数据使用 SQL Server 2014 演示了这一点,我还添加了一些额外的记录以实现效果。如果您不知道数据透视表是如何工作的,那么这将很难理解,但是使用这个示例,您很快就会得到它。第一个动态语句通过游标为您的数据透视构建列列表,然后将它们与数据透视所需的其余查询连接在一起。最后整个事情都被执行了。

-- I created a temp table out of your sample data to show how the dynamic query will work,
-- also added some extra data to demonstrate that you don't need to know the number of weeks in advance.
-- You should replace the the temp table with your query as a table or store the results in a global temp.
-- Notice that the temp table is global this is because the dynaimic queries will run on thier own 
-- session and not have access to the temp if you declare it as #dynPivot instead use ##dynPivot 
IF OBJECT_ID('tempdb..##dynPivot') IS NOT NULL
    BEGIN
        DROP TABLE ##dynPivot
    END 
GO
-- Create temp
CREATE TABLE ##dynPivot( 
    [Week] INT,
    Item_Number VARCHAR(50),
    Color_Code VARCHAR(50),
    Touches INT
)
-- populate temp with sample data
INSERT INTO ##dynPivot([Week], Item_Number, Color_Code, Touches)
VALUES (1, '11073900LRGMO','02000',7), (1, '11073900MEDMO', '02000',9), (2, 
'1114900011BMO', '38301',62), (2, '1114910012BMO', '21701',147), (2, '1114910012BMO', '38301',147), (2, '1114910012BMO', '46260',147), 
(3, '13MK430R03R', '00101',2), (3, '13MK430R03R', '10001',2), (3, '13MK430R03R', '65004',8), 
(3, '13MK430R03S', '00101',2), (3, '13MK430R03S', '10001',2), (4, '13MK430R03S', '10001',2), (5, '13MK430R03S', '10001',6),
(9, '13MK430R03S', '10001',2),(10, '13MK430R03S', '10001',1),(6, '13MK430R03S', '10001',1),(3, '13MK430R03S', '10001',9),
(4, '13MK430R03R', '00101',2), (8, '13MK430R03R', '10001',126), (7, '13MK430R03R', '65004',8),
(10, '1114910012ACB', '21701',147), (20, '1114910012XYZ', '38301',17), (12, '1114910012BMO', '46260',14)

-- Build the two dynaimc queries
DECLARE @sql VARCHAR(MAX) = ''
SET @sql = 'DECLARE @innerSql VARCHAR(MAX) = '''' 
DECLARE @week VARCHAR(50)
DECLARE @count INT
DECLARE @selectList VARCHAR(500) = ''''
DECLARE @counter INT
SET @counter = (SELECT COUNT(DISTINCT[week]) FROM ##dynPivot)
DECLARE C CURSOR FOR SELECT [week] FROM ##dynPivot GROUP BY [week] ORDER BY [week]
OPEN c 
FETCH NEXT FROM C INTO @week
WHILE @@FETCH_STATUS = 0
    BEGIN
        IF @counter > 1
            BEGIN
                SET @selectList = @selectList + ''['' + @week + ''], ''
            END
        ELSE
            BEGIN
                SET @selectList = @selectList + ''['' + @week + '']''
            END
        SET @counter = @counter - 1
        FETCH NEXT FROM C INTO @week
    END
CLOSE C
DEALLOCATE C
SET @innerSql = @innerSql + ''SELECT Item_Number, '' + @selectList + '' FROM (SELECT [Week], Item_Number, Touches FROM ##dynPivot) AS DataSorce'' + 
'' PIVOT (SUM(Touches) FOR [week] IN ('' + @selectList + '')) AS PT''
EXEC(@innerSql)'
--Execute them
EXEC(@sql)

-- You should get the following results
-- Item_Number      1       2       3       4       5       6       7       8       9       10      12      20
-- 11073900LRGMO    7       NULL    NULL    NULL    NULL    NULL    NULL    NULL    NULL    NULL    NULL    NULL
-- 11073900MEDMO    9       NULL    NULL    NULL    NULL    NULL    NULL    NULL    NULL    NULL    NULL    NULL
-- 1114900011BMO    NULL    62      NULL    NULL    NULL    NULL    NULL    NULL    NULL    NULL    NULL    NULL
-- 1114910012ACB    NULL    NULL    NULL    NULL    NULL    NULL    NULL    NULL    NULL    147     NULL    NULL
-- 1114910012BMO    NULL    441     NULL    NULL    NULL    NULL    NULL    NULL    NULL    NULL    14      NULL
-- 1114910012XYZ    NULL    NULL    NULL    NULL    NULL    NULL    NULL    NULL    NULL    NULL    NULL    17
-- 13MK430R03R      NULL    NULL    12      2       NULL    NULL    8       126     NULL    NULL    NULL    NULL
-- 13MK430R03S      NULL    NULL    13      2       6       1       NULL    NULL    2       1       NULL    NULL

【讨论】:

以上是关于如何在 SQL 中创建矩阵/如何使用大量行进行透视的主要内容,如果未能解决你的问题,请参考以下文章

如何在矩阵表 PHP 中创建总计行

SQL 数据透视表动态

在 LibreOffice Base 中创建等效数据透视表的 SQL (HSQLDB) 查询

在 1 行中创建相同列的数据透视

在 sql 中创建一个带联接的数据透视表

如何从 Scala 中的 DataFrame 在 Spark 中创建分布式稀疏矩阵