SQL Update table with cumulative value

只愿长相守 提交于 2021-02-08 05:14:39

问题


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

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!