我是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
我在这里看到了很多接近的示例,但大多数只是添加了值,而我需要从已添加的库存中减去已售库存以获取每列中的总计。
右侧和底部的行总计和列总数是加号,但如果没有则更容易。
谢谢!
答案 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_1
,Local_2
,Local_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
答案 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)
提供总计,还可以分别为PubID
和LocationID
提供总计,以及总计。这是您在问题中查询示例的结果:
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
的定义中将COALESCE应用于SUM的结果。