有没有办法使用窗口函数来简化这个查询?
Is there a way to use windowing functions to simplify this query?
我有一些数据,真实世界的例子。为了以特定方式显示数据,数据的存储方式使得我必须对其进行操作才能获得我想要的结果。从本质上讲,有两种不同类型的发票:预发票 (P),其中在处理订单之前开具账单;和标准发票 (I),其中剩余部分在发货后开具账单。在一般情况下,您会期望标准发票会减去发票前金额,例如,如果发票前金额为 2 美元而订单金额为 10 美元,则标准发票将为 8 美元。不过,标准发票存储为 10 美元。
下面是一个完全可操作的查询(该查询是可操作的!这是一个陷阱!),它既填充数据又 returns 我想要的结果。目标是获取发票金额,如果是预发票,则 return 该金额;但如果是标准发票,则 "use up" 预发票的价值和 return 新金额,最小为零。我已经包括了六种情况,因为预发票可以是任何金额,并且在技术上可能随时需要。
如有任何帮助,我们将不胜感激。我尝试了一些窗口函数,包括 UNBOUNDED PRECEDING 类型的东西,但似乎我总是需要递归并进入无限循环。因此,下面是我的蛮力方法。
DECLARE @PData TABLE (
CustNum INT
,TransNum INT
,InvType NVARCHAR(1)
,InvAmt DECIMAL(5,2)
,CRank INT
,TRank INT
,ModInvAmt DECIMAL(5,2)
)
INSERT INTO @PData (
CustNum
,TransNum
,InvType
,InvAmt
)
VALUES
(124, 1,'P',2)
,(124, 2,'I',10)
,(124, 3,'I',10)
,(153, 4,'I',10)
,(153, 5,'P',2)
,(153, 6,'I',10)
,(324, 7,'I',10)
,(324, 8,'I',10)
,(324, 9,'P',2)
,(441,10,'P',12)
,(441,11,'I',10)
,(441,12,'I',10)
,(455,13,'I',10)
,(455,14,'P',12)
,(455,15,'I',10)
,(667,16,'I',10)
,(667,17,'I',10)
,(667,18,'P',12)
UPDATE pd1
SET CRank = pd2.CDR
FROM @PData pd1
JOIN (SELECT CustNum, TransNum, DENSE_RANK() OVER (ORDER BY CustNum) AS CDR
FROM @PData) pd2
ON pd1.TransNum = pd2.TransNum
UPDATE pd1
SET TRank = pd2.TDR
FROM @PData pd1
JOIN (SELECT CustNum, TransNum, DENSE_RANK() OVER (PARTITION BY CustNum ORDER BY TransNum) AS TDR
FROM @PData) pd2
ON pd1.TransNum = pd2.TransNum
DECLARE @Counter1 INT
,@Counter2 INT
,@CBal DECIMAL(5,2)
,@TNum INT
,@IAmt DECIMAL(5,2)
SET @Counter1 = 0
WHILE @Counter1 < (SELECT MAX(CRank) FROM @PData)
BEGIN
SET @Counter1 += 1
SET @CBal = 0
SET @Counter2 = 0
WHILE @Counter2 < (SELECT MAX(TRank) FROM @PData WHERE CRank = @Counter1)
BEGIN
SET @Counter2 += 1
SET @TNum = (SELECT TransNum FROM @PData WHERE CRank = @Counter1 AND TRank = @Counter2)
SET @IAmt = (SELECT InvAmt FROM @PData WHERE TransNum = @TNum)
IF (SELECT InvType FROM @PData WHERE TransNum = @TNum) = 'P'
BEGIN
UPDATE @PData SET ModInvAmt = @IAmt WHERE TransNum = @TNum
SET @CBal += -@IAmt
END
ELSE
BEGIN
UPDATE @PData SET ModInvAmt = (ABS(@IAmt+@CBal)+(@IAmt+@CBal))/2 -- MINIMUM = 0
WHERE TransNum = @TNum
SET @CBal += (@IAmt - (ABS(@IAmt+@CBal)+(@IAmt+@CBal))/2)
END
END
END
SELECT CustNum
,TransNum
,InvType
,InvAmt
,ModInvAmt
FROM @PData
这是我得到的结果:
我通常不会报告原始发票金额——只报告新发票金额——但我将其包含在此处,以便更清楚地了解它是如何变化的。
CustNum TransNum InvType InvAmt ModInvAmt
124 1 P 2.00 2.00
124 2 I 10.00 8.00
124 3 I 10.00 10.00
153 4 I 10.00 10.00
153 5 P 2.00 2.00
153 6 I 10.00 8.00
324 7 I 10.00 10.00
324 8 I 10.00 10.00
324 9 P 2.00 2.00
441 10 P 12.00 12.00
441 11 I 10.00 0.00
441 12 I 10.00 8.00
455 13 I 10.00 10.00
455 14 P 12.00 12.00
455 15 I 10.00 0.00
667 16 I 10.00 10.00
667 17 I 10.00 10.00
667 18 P 12.00 12.00
我会用常见的 table 表达式递归地做。
此致
彼得
DECLARE @PData TABLE (
CustNum INT
,TransNum INT
,InvType NVARCHAR(1)
,InvAmt DECIMAL(5,2)
)
INSERT INTO @PData (
CustNum
,TransNum
,InvType
,InvAmt
)
VALUES
(124, 1,'P',2)
,(124, 2,'I',10)
,(124, 3,'I',10)
,(153, 4,'I',10)
,(153, 5,'P',2)
,(153, 6,'I',10)
,(324, 7,'I',10)
,(324, 8,'I',10)
,(324, 9,'P',2)
,(441,10,'P',12)
,(441,11,'I',10)
,(441,12,'I',10)
,(455,13,'I',10)
,(455,14,'P',12)
,(455,15,'I',10)
,(455,19,'I',10)
,(667,16,'I',10)
,(667,17,'I',10)
,(667,18,'P',12)
;WITH Data as (
SELECT
CustNum,
TransNum,
InvType,
InvAmt,
ROW_NUMBER() OVER (PARTITION BY custNum ORDER BY Transnum ASC) row,
CASE WHEN InvType = 'P' THEN cast(-1*InvAmt AS DECIMAL(5,2)) ELSE 0 END prepaidAmt
FROM
@PData
), modified as(
SELECT
CustNum,
TransNum,
InvType,
InvAmt,
prepaidAmt,
row,
InvAmt total
FROM Data d1
WHERE row = 1
UNION ALL
SELECT
d2.CustNum,
d2.TransNum,
d2.InvType,
d2.InvAmt,
CASE
WHEN
d2.InvAmt+m.prepaidAmt < 0
THEN
CAST (d2.InvAmt+m.prepaidAmt AS DECIMAL(5,2))
ELSE
CASE
WHEN
d2.invtype = 'P'
THEN
CAST(-1*d2.invamt AS DECIMAL(5,2))
ELSE
0
END
END ,
d2.row,
CASE
WHEN
d2.InvAmt+m.prepaidAmt <0
THEN
0
ELSE
CAST( d2.InvAmt+m.prepaidAmt AS DECIMAL(5,2))
END
FROM Data d2
JOIN modified m
ON
m.CustNum = d2.CustNum and
m.row = d2.row-1
)
SELECT
m.CustNum,
m.TransNum,
m.InvType,
m.InvAmt,
m.total
FROM modified m
ORDER BY
custnum,
transnum
我有一些数据,真实世界的例子。为了以特定方式显示数据,数据的存储方式使得我必须对其进行操作才能获得我想要的结果。从本质上讲,有两种不同类型的发票:预发票 (P),其中在处理订单之前开具账单;和标准发票 (I),其中剩余部分在发货后开具账单。在一般情况下,您会期望标准发票会减去发票前金额,例如,如果发票前金额为 2 美元而订单金额为 10 美元,则标准发票将为 8 美元。不过,标准发票存储为 10 美元。
下面是一个完全可操作的查询(该查询是可操作的!这是一个陷阱!),它既填充数据又 returns 我想要的结果。目标是获取发票金额,如果是预发票,则 return 该金额;但如果是标准发票,则 "use up" 预发票的价值和 return 新金额,最小为零。我已经包括了六种情况,因为预发票可以是任何金额,并且在技术上可能随时需要。
如有任何帮助,我们将不胜感激。我尝试了一些窗口函数,包括 UNBOUNDED PRECEDING 类型的东西,但似乎我总是需要递归并进入无限循环。因此,下面是我的蛮力方法。
DECLARE @PData TABLE (
CustNum INT
,TransNum INT
,InvType NVARCHAR(1)
,InvAmt DECIMAL(5,2)
,CRank INT
,TRank INT
,ModInvAmt DECIMAL(5,2)
)
INSERT INTO @PData (
CustNum
,TransNum
,InvType
,InvAmt
)
VALUES
(124, 1,'P',2)
,(124, 2,'I',10)
,(124, 3,'I',10)
,(153, 4,'I',10)
,(153, 5,'P',2)
,(153, 6,'I',10)
,(324, 7,'I',10)
,(324, 8,'I',10)
,(324, 9,'P',2)
,(441,10,'P',12)
,(441,11,'I',10)
,(441,12,'I',10)
,(455,13,'I',10)
,(455,14,'P',12)
,(455,15,'I',10)
,(667,16,'I',10)
,(667,17,'I',10)
,(667,18,'P',12)
UPDATE pd1
SET CRank = pd2.CDR
FROM @PData pd1
JOIN (SELECT CustNum, TransNum, DENSE_RANK() OVER (ORDER BY CustNum) AS CDR
FROM @PData) pd2
ON pd1.TransNum = pd2.TransNum
UPDATE pd1
SET TRank = pd2.TDR
FROM @PData pd1
JOIN (SELECT CustNum, TransNum, DENSE_RANK() OVER (PARTITION BY CustNum ORDER BY TransNum) AS TDR
FROM @PData) pd2
ON pd1.TransNum = pd2.TransNum
DECLARE @Counter1 INT
,@Counter2 INT
,@CBal DECIMAL(5,2)
,@TNum INT
,@IAmt DECIMAL(5,2)
SET @Counter1 = 0
WHILE @Counter1 < (SELECT MAX(CRank) FROM @PData)
BEGIN
SET @Counter1 += 1
SET @CBal = 0
SET @Counter2 = 0
WHILE @Counter2 < (SELECT MAX(TRank) FROM @PData WHERE CRank = @Counter1)
BEGIN
SET @Counter2 += 1
SET @TNum = (SELECT TransNum FROM @PData WHERE CRank = @Counter1 AND TRank = @Counter2)
SET @IAmt = (SELECT InvAmt FROM @PData WHERE TransNum = @TNum)
IF (SELECT InvType FROM @PData WHERE TransNum = @TNum) = 'P'
BEGIN
UPDATE @PData SET ModInvAmt = @IAmt WHERE TransNum = @TNum
SET @CBal += -@IAmt
END
ELSE
BEGIN
UPDATE @PData SET ModInvAmt = (ABS(@IAmt+@CBal)+(@IAmt+@CBal))/2 -- MINIMUM = 0
WHERE TransNum = @TNum
SET @CBal += (@IAmt - (ABS(@IAmt+@CBal)+(@IAmt+@CBal))/2)
END
END
END
SELECT CustNum
,TransNum
,InvType
,InvAmt
,ModInvAmt
FROM @PData
这是我得到的结果:
我通常不会报告原始发票金额——只报告新发票金额——但我将其包含在此处,以便更清楚地了解它是如何变化的。
CustNum TransNum InvType InvAmt ModInvAmt
124 1 P 2.00 2.00
124 2 I 10.00 8.00
124 3 I 10.00 10.00
153 4 I 10.00 10.00
153 5 P 2.00 2.00
153 6 I 10.00 8.00
324 7 I 10.00 10.00
324 8 I 10.00 10.00
324 9 P 2.00 2.00
441 10 P 12.00 12.00
441 11 I 10.00 0.00
441 12 I 10.00 8.00
455 13 I 10.00 10.00
455 14 P 12.00 12.00
455 15 I 10.00 0.00
667 16 I 10.00 10.00
667 17 I 10.00 10.00
667 18 P 12.00 12.00
我会用常见的 table 表达式递归地做。
此致 彼得
DECLARE @PData TABLE (
CustNum INT
,TransNum INT
,InvType NVARCHAR(1)
,InvAmt DECIMAL(5,2)
)
INSERT INTO @PData (
CustNum
,TransNum
,InvType
,InvAmt
)
VALUES
(124, 1,'P',2)
,(124, 2,'I',10)
,(124, 3,'I',10)
,(153, 4,'I',10)
,(153, 5,'P',2)
,(153, 6,'I',10)
,(324, 7,'I',10)
,(324, 8,'I',10)
,(324, 9,'P',2)
,(441,10,'P',12)
,(441,11,'I',10)
,(441,12,'I',10)
,(455,13,'I',10)
,(455,14,'P',12)
,(455,15,'I',10)
,(455,19,'I',10)
,(667,16,'I',10)
,(667,17,'I',10)
,(667,18,'P',12)
;WITH Data as (
SELECT
CustNum,
TransNum,
InvType,
InvAmt,
ROW_NUMBER() OVER (PARTITION BY custNum ORDER BY Transnum ASC) row,
CASE WHEN InvType = 'P' THEN cast(-1*InvAmt AS DECIMAL(5,2)) ELSE 0 END prepaidAmt
FROM
@PData
), modified as(
SELECT
CustNum,
TransNum,
InvType,
InvAmt,
prepaidAmt,
row,
InvAmt total
FROM Data d1
WHERE row = 1
UNION ALL
SELECT
d2.CustNum,
d2.TransNum,
d2.InvType,
d2.InvAmt,
CASE
WHEN
d2.InvAmt+m.prepaidAmt < 0
THEN
CAST (d2.InvAmt+m.prepaidAmt AS DECIMAL(5,2))
ELSE
CASE
WHEN
d2.invtype = 'P'
THEN
CAST(-1*d2.invamt AS DECIMAL(5,2))
ELSE
0
END
END ,
d2.row,
CASE
WHEN
d2.InvAmt+m.prepaidAmt <0
THEN
0
ELSE
CAST( d2.InvAmt+m.prepaidAmt AS DECIMAL(5,2))
END
FROM Data d2
JOIN modified m
ON
m.CustNum = d2.CustNum and
m.row = d2.row-1
)
SELECT
m.CustNum,
m.TransNum,
m.InvType,
m.InvAmt,
m.total
FROM modified m
ORDER BY
custnum,
transnum