问题
I have this table:
Date |StockCode|DaysMovement|OnHand
29-Jul|SC123 |30 |500
28-Jul|SC123 |15 |NULL
27-Jul|SC123 |0 |NULL
26-Jul|SC123 |4 |NULL
25-Jul|SC123 |-2 |NULL
24-Jul|SC123 |0 |NULL
The reason only the top row has an OnHand value is because I can get this from another table that stores the current qty on hand for any stock code.
The other records in the table are taken from another table that logs all the movement for any given day.
I want to update the above table so that the OnHand column shows the QtyOnHand for that row's date based on the previous record's stock and movement, such that is looks like this at the end of the update:
Date |StockCode|DaysMovement|OnHand
29-Jul|SC123 |30 |500
28-Jul|SC123 |15 |470
27-Jul|SC123 |0 |455
26-Jul|SC123 |4 |455
25-Jul|SC123 |-2 |451
24-Jul|SC123 |0 |453
I'm currently achieving this with a CURSOR. But performance really sucks over thousands of records.
Is there some SET-based UPDATE statement I can run that will achieve the same result?
回答1:
Try this (Fiddle demo)
DECLARE @Movement INT , @OnHandRunning INT
;WITH CTE AS
(
SELECT TOP 100 percent DaysMovement, OnHand
FROM Table1
ORDER BY [StockCode], [Date] DESC
)
UPDATE CTE SET @OnHandRunning = OnHand = COALESCE(@OnHandRunning - @Movement, OnHand),
@Movement = DaysMovement
UPDATE: For multiple StockCodes
you can modify above query like below (Fiddle demo 2):
DECLARE @Movement INT , @OnHandRunning INT, @StockCode VARCHAR(10) = ''
;WITH CTE AS
(
SELECT TOP 100 percent DaysMovement, OnHand, StockCode
FROM Table1
ORDER BY [StockCode],[Date] DESC
)
UPDATE CTE SET @OnHandRunning = OnHand =
CASE WHEN @StockCode<> StockCode THEN OnHand ELSE @OnHandRunning - @Movement END,
@Movement = DaysMovement,
@StockCode = StockCode
回答2:
This works, no idea how it performs compared to a cursor though?
--Data
DECLARE @Table TABLE (
[Date] DATE,
StockCode VARCHAR(50),
DaysMovement INT,
OnHand INT);
INSERT INTO @Table VALUES ('20140729', 'SC123', 30, 500);
INSERT INTO @Table VALUES ('20140728', 'SC123', 15, NULL);
INSERT INTO @Table VALUES ('20140727', 'SC123', 0, NULL);
INSERT INTO @Table VALUES ('20140726', 'SC123', 4, NULL);
INSERT INTO @Table VALUES ('20140725', 'SC123', -2, NULL);
INSERT INTO @Table VALUES ('20140724', 'SC123', 0, NULL);
--Query
SELECT
t1.[Date],
t1.StockCode,
t1.DaysMovement,
CASE WHEN t1.OnHand IS NULL THEN MAX(t2.OnHand) - SUM(t2.DaysMovement) ELSE t1.OnHand END AS OnHand
FROM
@Table t1
LEFT JOIN @Table t2 ON t1.[Date] < t2.[Date]
GROUP BY
t1.[Date],
t1.StockCode,
t1.DaysMovement,
t1.OnHand
ORDER BY
t1.[Date] DESC;
Results are:
Date StockCode DaysMovement OnHand
2014-07-29 SC123 30 500
2014-07-28 SC123 15 470
2014-07-27 SC123 0 455
2014-07-26 SC123 4 455
2014-07-25 SC123 -2 451
2014-07-24 SC123 0 453
回答3:
In case you can't use LAG
, you can try Recursive CTE:
DECLARE @table TABLE ([Date] VARCHAR(100), StockCode VARCHAR(100), DaysMovement INT, OnHand INT)
INSERT INTO @table SELECT '29-Jul', 'SC123', 30, 500
INSERT INTO @table SELECT '28-Jul', 'SC123', 15, NULL
INSERT INTO @table SELECT '27-Jul', 'SC123', 0, NULL
INSERT INTO @table SELECT '26-Jul', 'SC123', 4, NULL
INSERT INTO @table SELECT '25-Jul', 'SC123', -2, NULL
INSERT INTO @table SELECT '24-Jul', 'SC123', 0, NULL
INSERT INTO @table SELECT '19-Jul', 'SC1234', 30, 500
INSERT INTO @table SELECT '18-Jul', 'SC1234', 15, NULL
INSERT INTO @table SELECT '17-Jul', 'SC1234', 0, NULL
INSERT INTO @table SELECT '16-Jul', 'SC1234', 4, NULL
INSERT INTO @table SELECT '15-Jul', 'SC1234', -2, NULL
INSERT INTO @table SELECT '14-Jul', 'SC1234', 0, NULL
;WITH addRowID As (
SELECT [Date], StockCode, DaysMovement, OnHand, ROW_NUMBER() OVER (PARTITION BY StockCode ORDER BY StockCode, [Date] DESC) AS ROWID
FROM @table
), CTE AS (
SELECT [Date], StockCode, DaysMovement, OnHand, ROWID
FROM addRowID
WHERE OnHand IS NOT NULL AND ROWID = 1
UNION ALL
SELECT D.[Date], D.StockCode, D.DaysMovement, C.OnHand - C.DaysMovement AS OnHand, D.ROWID
FROM CTE AS C
INNER JOIN addRowID AS D
ON D.StockCode = C.StockCode
AND D.ROWID = C.ROWID + 1
WHERE D.OnHand IS NULL
)
SELECT *
FROM CTE
ORDER BY [DATE] DESC
Instead of last SELECT
you can put update. Not sure about performance....
回答4:
Assuming you are not using sqlserver 2012+, that would make this alot easier. Try this. It will UPDATE your table:
DECLARE @t TABLE(Date date, StockCode char(5), DaysMovement int, OnHand int)
INSERT @t VALUES
('29-Jul-2014','SC123',30,500),
('28-Jul-2014','SC123',15,NULL),
('27-Jul-2014','SC123',0 ,NULL),
('26-Jul-2014','SC123',4 ,NULL),
('25-Jul-2014','SC123',-2,NULL),
('24-Jul-2014','SC123',0 ,NULL)
;WITH cte as
(
SELECT
Date,
StockCode,
DaysMovement,
OnHand,
max(case when Date = '2014-07-29' THEN OnHand END) over() BaseOnhand,
Calc.MovementChange
FROM @t t
CROSS APPLY
(SELECT
coalesce(SUM(DaysMovement), 0) MovementChange
FROM @t
WHERE
Date between DateAdd(Day, 1, t.Date) and '2014-07-29'
) calc
)
UPDATE CTE SET OnHand = BaseOnhand - MovementChange
SELECT * FROM @t
Result:
Date StockCode DaysMovement OnHand
2014-07-29 SC123 30 500
2014-07-28 SC123 15 470
2014-07-27 SC123 0 455
2014-07-26 SC123 4 455
2014-07-25 SC123 -2 451
2014-07-24 SC123 0 453
回答5:
If you have SQLServer 2008 or an older version a triangular JOIN
is a easy way to solve this
SELECT a.[Date], a.StockCode, a.DaysMovement
, OnHand = MAX(b.OnHand) - SUM(b.DaysMovement) + a.DaysMovement
FROM Table1 a
LEFT JOIN Table1 b ON a.StockCode = b.StockCode AND b.[Date] >= a.[Date]
GROUP BY a.[Date], a.StockCode, a.DaysMovement
ORDER BY a.[Date] DESC
SQLFiddle Demo
If you have SQLServer 2012 or better the possibility to use ORDER
in the windowing function make the query easier
SELECT [Date], StockCode, DaysMovement
, OnHand = MAX(OnHand) OVER (PARTITION BY StockCode)
- SUM(DaysMovement) OVER (PARTITION BY StockCode
ORDER BY [Date] Desc)
+ DaysMovement
FROM Table1
SQLFiddle Demo
Both queries use the same logic: for each row get the only value of OnHand
(using MAX
and remove the movement of every day after the current, doing this it'll be removed also the current day movement, so it's added up. This logic avoid the null value in the first (last) day of every StockCode
.
The UPDATE
is easily derived, using a WITH
if you have SQLServer 2005 or better (in this I use the SQLServer 2012 or better version)
WITH A AS (
SELECT [Date], StockCode, DaysMovement
, OnHand = MAX(OnHand) OVER (PARTITION BY StockCode)
- SUM(DaysMovement) OVER (PARTITION BY StockCode
ORDER BY [Date] Desc)
+ DaysMovement
FROM Table1
)
UPDATE Table1 SET
OnHand = A.OnHand
FROM Table1
INNER JOIN A ON Table1.StockCode = a.StockCode AND Table1.[Date] = a.[Date]
来源:https://stackoverflow.com/questions/25010794/sql-update-table-with-cumulative-value