SQL如何使用子总计创建输出

时间:2014-02-27 21:04:28

标签: sql-server pivot-table subtotal

我是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

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

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

谢谢!

3 个答案:

答案 0 :(得分: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列包含在主SELECT子句中,同时包含透视结果和PubID

…
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(如果有必要)。

答案 1 :(得分:0)

以下查询可以满足您的需求。我可能有一个额外的组可以组合成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

答案 2 :(得分:0)

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

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

所有聚合都是在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
;

此查询将为(PubID, LocationID)的缺失组合返回NULL。如果您想要返回0,请在aggregated的定义中将C​​OALESCE应用于SUM的结果。