【问题标题】:SQL How to create output with sub totalsSQL 如何使用小计创建输出
【发布时间】:2014-02-27 21:04:27
【问题描述】:

我是 T-SQL 新手,需要帮助将 Excel 报表转换为 SQL 上的运行。我有一个 SQL 表,记录了每个库房的所有日常库存交易(进/出)。我需要创建一个报告,列出每个位置每个产品的当前库存水平和每个位置的数量,如下所示。换句话说,每个地方的当前库存水平。

我还需要有关如何将首选输出报告(如下)作为视图插入 SQL Server 的帮助,以便我可以每个月一遍又一遍地运行它。

提前致谢!

库存日志表:

PubID   QTY LocationID  Transaction
1       10  1           Add
1       20  2           Add
1       30  3           Add
1       5   1           Sold
1       10  2           Sold
1       5   3           Sold
2       10  1           Add
2       10  2           Add
2       5   2           Sold
2       8   2           Sold
1       20  1           Add
1       20  2           Add
2       2   2           Sold

首选输出表:

PubID   Local_1 Local_2 Local_3 Total
1       25      30      25      80
2       5       0       0       5
Total   30      30      25      85

我在这里看到了很多相近的例子,但大多数只是添加价值,而我需要从已添加库存中减去已售库存以获得我在每列中的总数。

右侧和底部的行总计和列总计是加号,但如果没有它更容易,则不需要。

谢谢!

【问题讨论】:

  • 我认为这个问题的不同足以保证一个新的职位。我看到了很多相近的例子,但没有一个能做到这一点。如果您看到更多询问我是谁的帖子,我愿意接受其他帖子。谢谢

标签: sql-server pivot-table subtotal


【解决方案1】:

如果这是关于没有旋转的聚合,您可以使用 CASE 表达式,如下所示:

SELECT
  ...
  Local_1 = SUM(CASE [Transaction] WHEN 'Add' THEN QTY ELSE -QTY END),
  ...
FROM ...
GROUP BY ...

但是,在 PIVOT 子句中,聚合函数的参数必须只是列引用,而不是表达式。您可以通过转换原始数据集来解决此问题,以便 QTY 是正数或负数,具体取决于 Transaction

SELECT
  PubID,
  QTY = CASE [Transaction] WHEN 'Add' THEN QTY ELSE -QTY END,
  LocationID
FROM dbo.InventoryLog

上面的查询会给你一个这样的结果集:

PubID  QTY  LocationID
-----  ---  ----------
1      10   1
1      20   2
1      30   3
1      -5   1
1      -10  2
1      -5   3
2      10   1
2      10   2
2      -5   2
2      -8   2
1      20   1
1      20   2
2      -2   2

现在很容易调整:

WITH prepared AS (
  SELECT
    PubID,
    QTY = CASE [Transaction] WHEN 'Add' THEN QTY ELSE -QTY END,
    LocationID
  FROM dbo.InventoryLog
)
SELECT
  PubID,
  Local_1 = [1],
  Local_2 = [2],
  Local_3 = [3]
FROM prepared
PIVOT
(
  SUM(QTY)
  FOR LocationID IN ([1], [2], [3])
) AS p
;

请注意,您实际上可以事先准备名称Local_1Local_2Local_3,并避免在主 SELECT 中重命名它们。假设它们是通过将 LocationID 值附加到字符串 Local_ 来形成的,这是我的意思的一个示例:

WITH prepared AS (
  SELECT
    PubID,
    QTY  = CASE [Transaction] WHEN 'Add' THEN QTY ELSE -QTY END,
    Name = 'Local_' + CAST(LocationID AS varchar(10))
  FROM dbo.InventoryLog
)
SELECT
  PubID,
  Local_1,
  Local_2,
  Local_3
FROM prepared
PIVOT
(
  SUM(QTY)
  FOR Name IN (Local_1, Local_2, Local_3)
) AS p
;

但是,您会看到,在此解决方案中,无论如何都需要重命名,因此我将在进一步的解释中使用以前的版本。

现在,将总计添加到您想要的输出中的数据透视结果可能看起来有点棘手。显然,该列可以简单地计算为所有 Local_* 列的总和,这对于少量位置实际上可能并不算太糟糕:

WITH prepared AS (
  SELECT
    PubID,
    QTY  = CASE [Transaction] WHEN 'Add' THEN QTY ELSE -QTY END,
    LocationID
  FROM dbo.InventoryLog
)
SELECT
  PubID,
  Local_1 = [1],
  Local_2 = [2],
  Local_3 = [3]
  Total   = COALESCE([1], 0)
          + COALESCE([2], 0)
          + COALESCE([3], 0)
FROM prepared
PIVOT
(
  SUM(QTY)
  FOR LocationID IN ([1], [2], [3])
) AS p
;

(需要 COALESCE,因为某些结果可能为 NULL。)

但还有一种替代方法,您不必再一次明确地列出所有位置。您可以使用SUM() OVER (...) 返回每​​个PubID 的总数以及prepared 数据集中的详细信息,如下所示:

WITH prepared AS (
  SELECT
    PubID,
    QTY   = CASE [Transaction] WHEN 'Add' THEN QTY ELSE -QTY END,
    LocationID,
    Total = SUM(CASE [Transaction] WHEN 'Add' THEN QTY ELSE -QTY END)
            OVER (PARTITION BY PubID)
  FROM dbo.InventoryLog
)
…

或者像这样,如果你想避免 CASE 表达式的重复:

WITH prepared AS (
  SELECT
    t.PubID,
    QTY   = x.AdjustedQTY,
    t.LocationID,
    Total = SUM(x.AdjustedQTY) OVER (PARTITION BY t.PubID)
  FROM dbo.InventoryLog AS t
  CROSS APPLY (
    SELECT CASE t.[Transaction] WHEN 'Add' THEN t.QTY ELSE -t.QTY END
  ) AS x (AdjustedQTY)
)
…

然后您只需将Total 列与转置结果和PubID 一起包含到主SELECT 子句中:

…
SELECT
  PubID,
  Local_1,
  Local_2,
  Local_3,
  Total
FROM prepared
PIVOT
(
  SUM(QTY)
  FOR LocationID IN ([1], [2], [3])
) AS p
;

这将是您的总。至于行,熟悉ROLLUP()分组功能后其实很容易添加:

…
SELECT
  PubID,
  Local_1 = SUM([1]),
  Local_2 = SUM([2]),
  Local_3 = SUM([3]),
  Total   = SUM(Total)
FROM prepared
PIVOT
(
  SUM(QTY)
  FOR LocationID IN ([1], [2], [3])
) AS p
GROUP BY ROLLUP(PubID)
;

PubID 列中的总行将具有 NULL,因此您将再次需要 COALESCE 将单词 Total 改为(仅当您想在 SQL 中返回它;或者您可以在调用中替换它申请):

…
PubID = COALESCE(CAST(PubID AS varchar(10)), 'Total'),
…

仅此而已。总结一下,这里是complete query

WITH prepared AS (
  SELECT
    PubID,
    QTY   = x.AdjustedQTY,
    t.LocationID,
    Total = SUM(x.AdjustedQTY) OVER (PARTITION BY t.PubID)
  FROM dbo.InventoryLog AS t
  CROSS APPLY (
    SELECT CASE t.[Transaction] WHEN 'Add' THEN t.QTY ELSE -t.QTY END
  ) AS x (AdjustedQTY)
)
SELECT
  PubID   = COALESCE(CAST(PubID AS varchar(10)), 'Total'),
  Local_1 = SUM([1]),
  Local_2 = SUM([2]),
  Local_3 = SUM([3]),
  Total   = SUM(Total)
FROM prepared
PIVOT
(
  SUM(QTY)
  FOR LocationID IN ([1], [2], [3])
) AS p
GROUP BY ROLLUP(PubID)
;

最后,您可能还想将 COALESCE 应用于 SUM,以避免在数据中返回 NULL(如果有必要)。

【讨论】:

    【解决方案2】:

    下面的查询可以满足您的需求。我可能有一个额外的组可以合并为 1,但你明白了。

    DECLARE  @InventoryLog TABLE
    (
        PubId INT,
        Qty INT,
        LocationId INT,
        [Transaction] Varchar(4)
    
    )
    
    DECLARE  @LocationTable TABLE
    (
        Id INT,
        Name VarChar(10)
    
    )
    
    INSERT INTO @LocationTable
    VALUES
    (1, 'LOC_1'),
    (2, 'LOC_2'),
    (3, 'LOC_3')
    
    INSERT INTO @InventoryLog
    VALUES 
    (1 ,      10,  1 ,          'Add'),
    (1 ,      20,  2 ,          'Add'),
    (1 ,      30,  3 ,          'Add'),
    (1 ,      5 ,  1 ,          'Sold'),
    (1 ,      10,  2 ,          'Sold'),
    (1 ,      5 ,  3 ,          'Sold'),
    (2 ,      10,  1 ,          'Add'),
    (2 ,      10,  2 ,          'Add'),
    (2 ,      5 ,  2 ,          'Sold'),
    (2 ,      8 ,  2 ,          'Sold'),
    (1 ,      20,  1 ,          'Add'),
    (1 ,      20,  2 ,          'Add'),
    (2 ,      2 ,  2 ,          'Sold')
    
    SELECT PubId, 
           lT.Name LocationName, 
           CASE
                WHEN [Transaction] ='Add' Then Qty
                WHEN [Transaction] ='Sold' Then -Qty
           END as Quantity
    INTO   #TempInventoryTable
    FROM @InventoryLog iL
    INNER JOIN @LocationTable  lT on iL.LocationId = lT.Id
    
    SELECT * INTO #AlmostThere
    FROM
    (
    SELECT PubId, 
           ISNULL(LOC_1,0) LOC_1,
           ISNULL(LOC_2,0) LOC_2, 
           ISNULL(LOC_3,0) LOC_3,
           SUM(ISNULL(LOC_1,0) + ISNULL(LOC_2,0) + ISNULL(LOC_3,0)) AS TOTAL
    FROM #TempInventoryTable s
    PIVOT 
    (
        SUM(Quantity)
        FOR LocationName in (LOC_1,LOC_2,LOC_3)
    ) as b
    GROUP BY PubId, LOC_1, LOC_2, LOC_3
    ) b
    
    SELECT CAST(PubId as VARCHAR(10))PubId,
           LOC_1,
           LOC_2,
           LOC_3,
           TOTAL
    FROM #AlmostThere
    UNION
    SELECT ISNULL(CAST(PubId AS VARCHAR(10)),'TOTAL')  PubId, 
           [LOC_1]= SUM(LOC_1),
           [LOC_2]= SUM(LOC_2),
           [LOC_3]= SUM(LOC_3),
           [TOTAL]= SUM(TOTAL)
    FROM #AlmostThere
    GROUP BY ROLLUP(PubId)
    
    
    
    
    DROP TABLE #TempInventoryTable
    DROP TABLE #AlmostThere
    
    
    
     PubId  LOC_1   LOC_2   LOC_3   TOTAL 
    
       1     25  30  25  80 
    
       2     10  -5   0   5
    
     TOTAL   35  25  25  85
    

    Sql Fiddle

    【讨论】:

      【解决方案3】:

      这是另一种方法:在透视之前聚合数据,然后透视聚合结果。

      与我的其他建议相比,这种方法在语法上要简单得多,这也可能更容易理解和维护。

      所有的聚合都是在CUBE() 分组函数的帮助下完成的。基本查询是这样的:

      SELECT
        PubID,
        LocationID,
        QTY = SUM(CASE [Transaction] WHEN 'Add' THEN QTY ELSE -QTY END)
      FROM dbo.InventoryLog
      GROUP BY CUBE(PubID, LocationID)
      

      你可以看到和我其他答案一样的 CASE 表达式,只是这次可以直接用作 SUM 的参数。

      使用 CUBE 的聚合不仅可以得到 (PubID, LocationID) 的总数,还可以分别得到 PubIDLocationID 的总数,以及总计。这是您问题中示例的查询结果:

      PubID  LocationID  QTY
      -----  ----------  ---
      1      1           35
      2      1           10
      NULL   1           45
      1      2           50
      2      2           25
      NULL   2           75
      1      3           35
      NULL   3           35
      NULL   NULL        155
      1      NULL        120
      2      NULL        35
      

      LocationID 中有 NULL 的行是最终结果集中的行总计,PubID 中有 NULL 的行是列总计。两列中都有 NULL 的行是总计。

      在继续进行透视之前,我们需要为透视结果准备列名。如果名称应该从 LocationID 的值派生,则以下声明将替换原始查询的 SELECT 子句中的 LocationID

      Location = COALESCE('Local_' + CAST(LocationID AS varchar(10)), 'Total')
      

      我们还可以在同一阶段用'Total' 替换PubID 中的NULL,因此这将替换SELECT 子句中的PubID

      PubID = COALESCE(CAST(PubID AS varchar(10)), 'Total')
      

      现在结果将如下所示:

      PubID  LocationID  QTY
      -----  ----------  ---
      1      Local_1     35
      2      Local_1     10
      Total  Local_1     45
      1      Local_2     50
      2      Local_2     25
      Total  Local_2     75
      1      Local_3     35
      Total  Local_3     35
      Total  Total       155
      1      Total       120
      2      Total       35
      

      此时一切都已准备好应用 PIVOT。 This query 根据需要的格式对上述结果集进行转换:

      WITH aggregated AS (
        SELECT
          PubID    = COALESCE(CAST(PubID AS varchar(10)), 'Total'),
          Location = COALESCE('Local_' + CAST(LocationID AS varchar(10)), 'Total'),
          QTY      = SUM(CASE [Transaction] WHEN 'Add' THEN QTY ELSE -QTY END)
        FROM dbo.InventoryLog
        GROUP BY CUBE(PubID, LocationID)
      )
      SELECT
        PubID,
        Local_1,
        Local_2,
        Local_3,
        Total
      FROM aggregated
      PIVOT (
        MAX(QTY)
        FOR Location IN (Local_1, Local_2, Local_3, Total)
      ) AS p
      ;
      

      此查询将返回 NULL 以缺少 (PubID, LocationID) 的组合。如果您想返回 0,请将 COALESCE 应用于 aggregated 定义中的 SUM 结果。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2018-10-03
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2022-12-31
        • 1970-01-01
        相关资源
        最近更新 更多