我的程序需要将一定数量传入查询以执行此类计算,但在我的情况下,它逐行循环并扣除正确的数量,我知道这不是一种有效的实现方式。所以我在这里寻求更好的方式。
PS:这只是我的草稿代码,很抱歉由于某些原因我无法发布完整的源代码。现在我重新构建了我的代码,使其更加完整和合理。
--- the amount column is just for reference.
insert into tbl1 (idx,amount,balance) values (1, 50, 50)
insert into tbl1 (idx,amount,balance) values (2, 30, 30)
insert into tbl1 (idx,amount,balance) values (3, 20, 20)
insert into tbl1 (idx,amount,balance) values (4, 50, 50)
insert into tbl1 (idx,amount,balance) values (5, 60, 60)
declare @total_value_to_deduct int
declare @cs_index int, @cs_balance int, @deduct_amount int
set @total_value_to_deduct = 130
declare csDeduct Cursor for select idx, balance from tbl1 where balance > 0
open csDeduct fetch next from csDeduct into @cs_index, @cs_balance
while @@FETCH_STATUS = 0 and @total_value_to_deduct > 0
begin
if @cs_balance >= @total_value_to_deduct
set @deduct_amount = @total_value_to_deduct
else
set @deduct_amount = @cs_balance
-- contine deduct row by row if the total_value_to_deduct is not 0
set @total_value_to_deduct = @total_value_to_deduct - @deduct_amount
update tbl1 set balance = balance - @deduct_amount where idx = @cs_index
fetch next from csDeduct into @cs_index, @cs_balance
end
close csDeduct
deallocate csDeduct
预期结果:
idx amount balance
1 50 0
2 30 0
3 20 0
4 50 20
5 60 60
你必须得到你的帮助。感谢
答案 0 :(得分:4)
修订版1:我添加了第三个解决方案
1)第一个解决方案(SQL2005 +; online query)
DECLARE @tbl1 TABLE
(
idx INT IDENTITY(2,2) PRIMARY KEY,
amount INT NOT NULL,
balance INT NOT NULL
);
INSERT INTO @tbl1 (amount,balance) VALUES (50, 50);
INSERT INTO @tbl1 (amount,balance) VALUES (30, 30);
INSERT INTO @tbl1 (amount,balance) VALUES (20, 20);
INSERT INTO @tbl1 (amount,balance) VALUES (50, 50);
INSERT INTO @tbl1 (amount,balance) VALUES (60, 60);
DECLARE @total_value_to_deduct INT;
SET @total_value_to_deduct = 130;
WITH CteRowNumber
AS
(
SELECT *, ROW_NUMBER() OVER(ORDER BY idx) AS RowNum
FROM @tbl1 a
), CteRecursive
AS
(
SELECT a.idx,
a.amount,
a.amount AS running_total,
CASE
WHEN a.amount <= @total_value_to_deduct THEN 0
ELSE a.amount - @total_value_to_deduct
END AS new_balance,
a.RowNum
FROM CteRowNumber a
WHERE a.RowNum = 1
--AND a.amount < @total_value_to_deduct
UNION ALL
SELECT crt.idx,
crt.amount,
crt.amount + prev.running_total AS running_total,
CASE
WHEN crt.amount + prev.running_total <= @total_value_to_deduct THEN 0
WHEN prev.running_total < @total_value_to_deduct AND crt.amount + prev.running_total > @total_value_to_deduct THEN crt.amount + prev.running_total - @total_value_to_deduct
ELSE crt.amount
END AS new_balance,
crt.RowNum
FROM CteRowNumber crt
INNER JOIN CteRecursive prev ON crt.RowNum = prev.RowNum + 1
--WHERE prev.running_total < @total_value_to_deduct
)
UPDATE @tbl1
SET balance = b.new_balance
FROM @tbl1 a
2)第二个解决方案(SQL2012)
UPDATE @tbl1
SET balance = b.new_balance
FROM @tbl1 a
INNER JOIN
(
SELECT x.idx,
SUM(x.amount) OVER(ORDER BY x.idx) AS running_total,
CASE
WHEN SUM(x.amount) OVER(ORDER BY x.idx) <= @total_value_to_deduct THEN 0
WHEN SUM(x.amount) OVER(ORDER BY x.idx) - x.amount < @total_value_to_deduct --prev_running_total < @total_value_to_deduct
AND SUM(x.amount) OVER(ORDER BY x.idx) > @total_value_to_deduct THEN SUM(x.amount) OVER(ORDER BY x.idx) - @total_value_to_deduct
ELSE x.amount
END AS new_balance
FROM @tbl1 x
) b ON a.idx = b.idx;
3)第三个解决方案(SQ2000 +)使用triangular join:
UPDATE @tbl1
SET balance = d.new_balance
FROM @tbl1 e
INNER JOIN
(
SELECT c.idx,
CASE
WHEN c.running_total <= @total_value_to_deduct THEN 0
WHEN c.running_total - c.amount < @total_value_to_deduct --prev_running_total < @total_value_to_deduct
AND c.running_total > @total_value_to_deduct THEN c.running_total - @total_value_to_deduct
ELSE c.amount
END AS new_balance
FROM
(
SELECT a.idx,
a.amount,
(SELECT SUM(b.amount) FROM @tbl1 b WHERE b.idx <= a.idx) AS running_total
FROM @tbl1 a
) c
)d ON d.idx = e.idx;
答案 1 :(得分:1)
我很确定这个查询无论如何都不会起作用,因为&#34; index&#34;是一个关键字,所以应该用方括号括起来表示不是。
一般情况下,逐行执行任何操作并不是一个好主意。
如果我正确阅读,您可以将每个余额列设置为金额列减去@total_value_to_deduct变量,或者如果扣除金额会导致负值,则将其设置为0。如果这是真的那么为什么不直接对其进行计算呢?如果没有你发布任何预期的结果,我不能仔细检查我的逻辑,但如果我错了,请纠正我,并且它比这更复杂。
UPDATE tbl1
SET balance = CASE
WHEN amount < @total_value_to_deduct THEN 0
ELSE amount - @total_value_to_deduct
END
编辑:好的,感谢编辑问题,现在更清楚了。您正在尝试按顺序获取所有帐户的总金额。我会看看我是否可以提出一个脚本来执行此操作并进一步编辑我的答案。
编辑#2:好的,我无法在不通过所有行进行交互的情况下找到一种方法(我尝试了递归CTE,但无法获得它工作)所以我用你原来的while循环完成了它。它有效地每行进行3次数据访问 - 我试图将其降为2但又没有运气。无论如何我都会发布它,以防它比你现在的速度快。这应该是您需要的所有代码(除了表创建/填充)。
DECLARE @id INT
SELECT @id = Min([index])
FROM tbl1
WHILE @id IS NOT NULL
BEGIN
UPDATE tbl1
SET balance = CASE
WHEN amount < @total_value_to_deduct THEN 0
ELSE amount - @total_value_to_deduct
END
FROM tbl1
WHERE [index] = @id
SELECT @total_value_to_deduct = CASE
WHEN @total_value_to_deduct < amount THEN 0
ELSE @total_value_to_deduct - amount
END
FROM tbl1
WHERE [index] = @id
SELECT @id = Min([index])
FROM tbl1
WHERE [index] > @id
END
答案 2 :(得分:1)
以下是其中一种方法。它找到大于或等于请求数量的第一个运行总和,然后更新参与此总和的所有记录。在应该引入列“toDeduct”并且最初具有amount值的意义上,这可能应该以不同的方式编写。这将允许此更新适用于以前使用的数据集,因为toDeduct = 0意味着不能从此行中扣除任何内容。此外,toDeduct,idx上的索引将允许快速toDeduct&lt;&gt;您将使用0过滤器来减少无意义的搜索/更新次数。
declare @total_value_to_deduct int
set @total_value_to_deduct = 130
update tbl1
set balance = case when balance.idx = tbl1.idx
then balance.sumamount - @total_value_to_deduct
else 0
end
from tbl1 inner join
(
select top 1 *
from
(
select idx, (select sum (a.amount)
from tbl1 a
where a.idx <= tbl1.idx) sumAmount
from tbl1
) balance
where balance.sumamount >= @total_value_to_deduct
order by sumamount
) balance
on tbl1.idx <= balance.idx
现在开始你的光标。只需声明游标fast_forward即可获得性能:
declare csDeduct Cursor local fast_forward
for select idx, balance
from tbl1
where balance > 0
order by idx
你可能会重写fetch循环以避免重复fetch语句:
open csDeduct
while 1 = 1
begin
fetch next from csDeduct into @cs_index, @cs_balance
if @@fetch_status <> 0
break
if @cs_balance >= @total_value_to_deduct
set @deduct_amount = @total_value_to_deduct
else
set @deduct_amount = @cs_balance
-- contine deduct row by row if the total_value_to_deduct is not 0
set @total_value_to_deduct = @total_value_to_deduct - @deduct_amount
update tbl1 set balance = balance - @deduct_amount where idx = @cs_index
end
close csDeduct
deallocate csDeduct
更改光标的选择部分更容易。
答案 3 :(得分:1)
如果您的索引没有差距,最简单的解决方案是
CTE
,从递归部分中扣除和递减它的值开始。CTE
的结果更新实际表格SQL声明
;WITH q AS (
SELECT idx, amount, balance, 130 AS Deduct
FROM tbl1
WHERE idx = 1
UNION ALL
SELECT t.idx, t.amount, t.balance, q.Deduct - q.balance
FROM q
INNER JOIN @tbl1 t ON t.idx = q.idx + 1
WHERE q.Deduct - q.balance > 0
)
UPDATE @tbl1
SET Balance = CASE WHEN q.Balance - q.Deduct > 0 THEN q.Balance - q.Deduct ELSE 0 END
FROM q
INNER JOIN tbl1 t ON t.idx = q.idx
使用ROW_NUMBER
可以缓解差距问题,但这会使查询复杂化。
;WITH r AS (
SELECT idx, amount, balance, rn = ROW_NUMBER() OVER (ORDER BY idx)
FROM tbl1
), q AS (
SELECT rn, amount, balance, 130 AS Deduct, idx
FROM r
WHERE rn = 1
UNION ALL
SELECT r.rn, r.amount, r.balance, q.Deduct - q.balance, r.idx
FROM q
INNER JOIN r ON r.rn = q.rn + 1
WHERE q.Deduct - q.balance > 0
)
UPDATE tbl1
SET Balance = CASE WHEN q.Balance - q.Deduct > 0 THEN q.Balance - q.Deduct ELSE 0 END
FROM q
INNER JOIN @tbl1 t ON t.idx = q.idx
测试脚本
DECLARE @tbl1 TABLE (idx INTEGER, Amount INTEGER, Balance INTEGER)
INSERT INTO @tbl1 (idx,amount,balance) VALUES (1, 50, 50)
INSERT INTO @tbl1 (idx,amount,balance) VALUES (2, 30, 30)
INSERT INTO @tbl1 (idx,amount,balance) VALUES (3, 20, 20)
INSERT INTO @tbl1 (idx,amount,balance) VALUES (4, 50, 50)
INSERT INTO @tbl1 (idx,amount,balance) VALUES (5, 60, 60)
;WITH q AS (
SELECT idx, amount, balance, 130 AS Deduct
FROM @tbl1
WHERE idx = 1
UNION ALL
SELECT t.idx, t.amount, t.balance, q.Deduct - q.balance
FROM q
INNER JOIN @tbl1 t ON t.idx = q.idx + 1
WHERE q.Deduct - q.balance > 0
)
UPDATE @tbl1
SET Balance = CASE WHEN q.Balance - q.Deduct > 0 THEN q.Balance - q.Deduct ELSE 0 END
FROM q
INNER JOIN @tbl1 t ON t.idx = q.idx
SELECT *
FROM @tbl1
<强>输出强>
idx Amount Balance
1 50 0
2 30 0
3 20 0
4 50 20
5 60 60
答案 4 :(得分:0)
在表格中创建一个新列,每行包含前一个余额,然后您可以在INSERT / UPDATE上使用触发器为新插入的行创建余额。