Update Query - Sum of previous row x and current row y - sql

I having a table LedgerData and need to update the Balance.
Table Structure
CREATE TABLE [dbo].[LedgerData]
(
[Id] INT NOT NULL,
[CustomerId] INT NOT NULL,
[Credit] INT,
[Debit] INT,
[Balance] INT
CONSTRAINT [PK_dbo_LedgerData]
PRIMARY KEY CLUSTERED ([Id] ASC)
WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF,
IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON,
ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY];
Sample Data
INSERT INTO [dbo].[LedgerData] VALUES (1, 1, 50, 0, 0);
INSERT INTO [dbo].[LedgerData] VALUES (2, 1, 0, 25, 0);
INSERT INTO [dbo].[LedgerData] VALUES (3, 2, 0, 75, 0);
INSERT INTO [dbo].[LedgerData] VALUES (4, 1, 0, 10, 0);
INSERT INTO [dbo].[LedgerData] VALUES (5, 2, 5, 0, 0);
INSERT INTO [dbo].[LedgerData] VALUES (6, 1, 10, 25, 0);
I tried to update the balance column customer wise ORDER BY [Id] ASC, but its not updating as expected. Also I explored the sql query to calculate sum and add sum from previous rows
Please assist me to calculate the balance column Balance = (Previous Row Balance + Credit - Debit)

Ideally this is something you should be doing as you INSERT the data, by getting the previous value (and locking the table so that other INSERT statements can't occur to avoid races) and then supplying a value for the Balance. You can, however, UPDATE all the rows with a cumulative SUM and an updatable CTE:
WITH CTE AS(
SELECT ID,
CustomerID,
Balance,
0 + SUM(Credit) OVER (PARTITION BY CustomerID ORDER BY ID) - SUM(Debit) OVER (PARTITION BY CustomerID ORDER BY ID) AS NewBalance
FROM dbo.LedgerData)
UPDATE CTE
SET Balance = NewBalance;
GO
SELECT *
FROM dbo.LedgerData;
Alternatively, don't store the aggregate value at all, and use a VIEW so that the value can always be calculated (accurately) with the same expression I have used in the CTE. For example:
CREATE VIEW dbo.LedgerDataCumulative
AS
SELECT Id,
CustomerId,
Credit,
Debit,
SUM(Credit) OVER (PARTITION BY CustomerID ORDER BY ID) - SUM(Debit) OVER (PARTITION BY CustomerID ORDER BY ID) AS Balance
FROM dbo.LedgerData;
GO

The update can be performed with a single window function.
with upd_cte as (
select *, sum([Credit]-[Debit]) over (partition by customerId order by id) sum_over
from #LedgerData)
update upd_cte
set Balance=sum_over;

Related

Calculate Running Total Amount with Bonus

I have following table:
create table test_table
(
employee_id integer,
salary_year integer,
raise_in_salary_perentage decimal(18,2),
annual_salary decimal(18,2)
);
**Test Data is following: **
insert into test_table values ( 1,2016, 0 , 100);
insert into test_table values ( 1,2017, 10, 100);
insert into test_table values ( 1,2018, 10, 100);
insert into test_table values ( 1,2019, 0, 100);
insert into test_table values ( 1,2020, 10, 100);
insert into test_table values ( 2,2016, 10 , 100);
insert into test_table values ( 2,2017, 10, 100);
insert into test_table values ( 2,2018, 0, 100);
insert into test_table values ( 2,2019, 0, 100);
insert into test_table values ( 2,2020, 0, 100);
I am trying to achieve following output:
The cumulative salary should include the running total of annual salary over years for each employee.
There is a percentage of raise every year, so if current year has a raise the cumulative salary will be sum of previous salaries plus the amount received in raise.
I tried to achieve it using following SQL, but results does seems right. Will be thankful for solution.
SELECT *
,sum(annual_salary) OVER (
PARTITION BY employee_id ORDER BY salary_year ROWS BETWEEN UNBOUNDED PRECEDING
AND CURRENT ROW
) AS cummulative_salary
,(
sum(annual_salary) OVER (
PARTITION BY employee_id ORDER BY salary_year ROWS BETWEEN UNBOUNDED PRECEDING
AND CURRENT ROW
)
) + (
sum(annual_salary) OVER (
PARTITION BY employee_id ORDER BY salary_year ROWS BETWEEN UNBOUNDED PRECEDING
AND CURRENT ROW
)
) * (
sum(raise_in_salary_perentage) OVER (
PARTITION BY employee_id ORDER BY salary_year ROWS BETWEEN UNBOUNDED PRECEDING
AND CURRENT ROW
) / 100
) AS csalary
FROM test_table;
Based on your description, the increase in salary should be cumulative. However, a given year's increase should not affect previous years.
That is not what your desired results show. Based on my interpretation, I think you want:
with recursive cte as (
select employee_id, salary_year, (t.annual_salary * (1 + raise_in_salary_perentage / 100.0))::numeric(18, 2) as annual_salary,
raise_in_salary_perentage,
(t.annual_salary * (1 + raise_in_salary_perentage / 100.0))::numeric(18, 2) as total
from test_table t
where salary_year = 2016
union all
select t.employee_id, t.salary_year, (cte.annual_salary * (1 + t.raise_in_salary_perentage / 100.0))::numeric(18, 2),
t.raise_in_salary_perentage,
(cte.total + cte.annual_salary * (1 + t.raise_in_salary_perentage / 100.0))::numeric(18, 2)
from cte join
test_table t
on t.employee_id = cte.employee_id and t.salary_year = cte.salary_year + 1
)
select *
from cte
order by employee_id, salary_year;
Here is a db<>fiddle.

Table not updating in single select query

i have a temporary table which i need to update, the first row is updated but the second row updates as null , please help
declare #T Table
(
ID int,
Name nvarchar(20),
rownum int
)
insert into #T(ID,rownum)
select ID, rownum = ROW_NUMBER() OVER(order by id) from testtabel4
select * from testtabel4
update #t
set Name=case when rownum>1 then (select top 1 Name from #T x where x.rownum=(y.rownum-1))
else 'first' end
from #t y
select * from #T
and here the definition of testtabel4
CREATE TABLE [dbo].[testtabel4](
[ID] [int] IDENTITY(1,1) NOT NULL,
[Name] [nvarchar](80) NULL,
PRIMARY KEY CLUSTERED
(
[ID] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
GO
and here is the output
ID Name
1 first
2 NULL
I think your update would be better written with lag() and an updateable CTE.
with cte as (
select name, lag(name, 1, 'first') over(order by rownum) lag_name
from #t
)
update cte set name = lag_name
With this technique at hand, it is plain to see that don't actually need to feed the table first, then insert into it. You can do both at once, like so:
insert into #t (id, name, rownum)
select
id,
lag(name, 1, 'first') over(order by id),
row_number() over(order by id)
from testtabel4
I am not sure that you even need rownum column anymore, unless it is needed for some other purpose.
You are only inserting two columns in the #T:
insert into #T (ID, rownum)
select ID, ROW_NUMBER() OVER (order by id)
from testtabel4;
You are not inserting name so it is NULL on all rows. Hence, the then part of the case expression will always be NULL.

SQL Group by and update/delete row

I have a SQL.Table
CREATE TABLE [dbo].[Stack_Example](
[ID] [bigint] NOT NULL,
[Product_ID] [bigint] NULL,
[Quantity] [decimal](18, 2) NULL,
[Price] [decimal](18, 2) NULL,
CONSTRAINT [PK_Stack_Example] PRIMARY KEY CLUSTERED
(
[ID] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
With data like shown under:
INSERT [dbo].[Stack_Example] ([ID], [Product_ID], [Quantity], [Price]) VALUES (1, 25, CAST(55.00 AS Decimal(18, 2)), CAST(1000.00 AS Decimal(18, 2)))
GO
INSERT [dbo].[Stack_Example] ([ID], [Product_ID], [Quantity], [Price]) VALUES (2, 25, CAST(1.00 AS Decimal(18, 2)), CAST(1000.00 AS Decimal(18, 2)))
GO
INSERT [dbo].[Stack_Example] ([ID], [Product_ID], [Quantity], [Price]) VALUES (3, 26, CAST(1.00 AS Decimal(18, 2)), CAST(500.00 AS Decimal(18, 2)))
GO
So my problem here is i need to group those items by Price, Product_Id and SUM(Quantity).
There is need for Update/Delete query assuming my output window need to looks like
Its simple when you do a select query
select Product_ID,SUM(Quantity) AS Quantity,Price from Stack_Example
Group by Product_ID,Price
So at very begin what i need to do is to delete the row with id : 2 And update the row with id 1 set Quantity = Quantity + 1 ( Quantity of deleted row ).
So when i run the select query without grouping and summing i need to get the same output
UPDATE Stack_Example SET Quantity = (SELECT SUM(Quantity)
FROM Stack_Example child
WHERE child.Product_ID = Stack_Example.Product_ID
GROUP BY Product_ID)
DELETE FROM Stack_Example WHERE ID IN (SELECT TOP 1 ID
FROM Stack_Example
WHERE Product_Id IN ( (SELECT TOP 1 Product_ID
FROM Stack_Example
GROUP BY Product_ID
HAVING COUNT(Product_ID)>1)))
First update your table with total amount of products with same price
with cte as (
select
Product_ID, Price, Quantity = sum(Quantity)
from
Stack_Example
group by Product_ID, Price
)
update s
set s.Quantity = c.Quantity
from
Stack_Example s
join cte c on s.Product_ID = c.Product_ID and s.Price = c.Price
Then run another script to delete duplicate rows
with cte as (
select
*, row_number() over (partition by Product_ID, Price order by Product_ID) rn
from
Stack_Example
)
delete from cte where rn > 1
I'm a bit unclear - but couldn't you just create a view with the aggrgates?
Anyhow.....
Not sure why you would do this but...you could insert the aggregated values and then delete the older ones....as per below.
DECLARE #MaxId INT;
DECLARE #ProductId = 25;
--GET THE MAX ID OF PRODUCT IN QUESTION
SELECT #MaxId = MAX(ID) FROM Stack_Example WHERE Product_Id = #ProductId;
--INSERT THE AGGREGATE VALUES INTO TABLE
INSERT INTO Stack_Example
SELECT #MaxId + 1, #ProductId, Price, SUM(Quantity)
FROM Stack_Example
WHERE
Product_Id = #ProductId
GROUP BY
#MaxId + 1, [Product_ID], [Price];
--DELETE THE PRE-AGGREGATE VALUES
DELETE FROM Stack_Example WHERE ProductId = #ProductId and Id <= #MaxId;
Well, I used aggregates and additional temporary table to achieve that:
--group records and insert them into temporary table
select distinct
Min(id) over (partition by product_id, price) [ID],
Product_id,
sum(Quantity) over (partition by product_id, price) [Quantity],
Price
into #new_stack_example
from #Stack_Example
--delete old values
delete #Stack_example
--insert new values
insert into #Stack_example
select * from #new_stack_example
First SELECT statement reflects your logic:
So my problem here is i need to group those items by Price, Product_Id and SUM(Quantity).
But instead of grouping, I use window functions and then select distinct records.
Check this:
UPDATE Stack_Example SET Quantity = a.Quantity
from ( select Product_ID,sum(Quantity) Quantity
from Stack_Example Group by Product_ID) a
where Stack_Example.Product_ID=a.Product_ID
;with cte as (
select *,ROW_NUMBER () over (partition by product_id order by product_id) rn
from Stack_Example)
delete from cte where rn > 1

How find duplicates in a table with no primary key or ID field?

I've inherited a SQL Server database that has duplicate data in it. I need to find and remove the duplicate rows. But without an id field, I'm not sure how to find the rows.
Normally, I'd compare it with itself using a LEFT JOIN and check that all fields are the same except the ID field would be table1.id <> table2.id, but without that, I don't know how to find duplicates rows and not have it also match on itself.
TABLE:
productId int not null,
categoryId int not null,
state varchar(255) not null,
dateDone DATETIME not null
SAMPLE DATA
1, 3, "started", "2016-06-15 04:23:12.000"
2, 3, "started", "2016-06-15 04:21:12.000"
1, 3, "started", "2016-06-15 04:23:12.000"
1, 3, "done", "2016-06-15 04:23:12.000"
In that sample, only rows 1 and 3 are duplicates.
How do I find duplicates?
Use having (and group by)
select
productId
, categoryId
, state
, dateDone
, count(*)
from your_table
group by productId ,categoryId ,state, dateDone
having count(*) >1
You can do this with windowing functions. For instance
create table #tmp
(
Id INT
)
insert into #tmp
VALUES (1), (1), (2) --so now we have duplicated rows
WITH CTE AS
(
SELECT
ROW_NUMBER() OVER(PARTITION BY Id ORDER BY Id) AS [DuplicateCounter],
Id
FROM #tmp
)
DELETE FROM CTE
WHERE DuplicateCounter > 1 --duplicated rows have DuplicateCounter > 1
For some reason I thought you wanted to delete them I guess I read that wrong but just switch DELETE in my statement to SELECT and now you have all of the duplicates and not the original. But using DELETE will remove all duplicates and still leave you 1 record which I suspect is your desire.
IF OBJECT_ID('tempdb..#TT') IS NOT NULL
BEGIN
DROP TABLE #TT
END
CREATE TABLE #TT (
productId int not null,
categoryId int not null,
state varchar(255) not null,
dateDone DATETIME not null
)
INSERT INTO #TT (productId, categoryId, state, dateDone)
VALUES (1, 3, 'started', '2016-06-15 04:23:12.000')
,(2, 3, 'started', '2016-06-15 04:21:12.000')
,(1, 3, 'started', '2016-06-15 04:23:12.000')
,(1, 3, 'done', '2016-06-15 04:23:12.000')
SELECT *
FROM
#TT
;WITH cte AS (
SELECT
*
,RowNum = ROW_NUMBER() OVER (PARTITION BY productId, categoryId, state, dateDone ORDER BY productId) --note what you order by doesn't matter
FROM
#TT
)
--if you want to delete them just do this otherwise change DELETE TO SELECT
DELETE
FROM
cte
WHERE
RowNum > 1
SELECT *
FROM
#TT
If you want to and can change schema you can always add an identity column after the fact too and it will populate the existing record
ALTER TABLE #TT
ADD Id INTEGER IDENTITY(1,1) NOT NULL
You can try CTE and then limit the actual selection from the CTE to where RN = 1. Here is the query:-
;WITH ACTE
AS
(
SELECT ProductID, categoryID, State, DateDone,
RN = ROW_NUMBER() OVER(PARTITION BY ProductID, CategoryID, State, DateDone
ORDER BY ProductID, CategoryID, State, DateDone)
FROM [Table]
)
SELECT * FROM ACTE WHERE RN = 1

SQL Server 2005 Update Triggers not working for dependent columns

I have encountered a strange situation where the update trigger on a table is not updating columns that are dependent on other columns which are also getting updated during the update. Here is the background and the code for replicating this problem.
I have a commodities management application that keeps track of fruit prices everyday. I have a need to calculate the Price and Volume trend for fruits on a daily purpose. The daily Fruit prices and price volume calculations are stored in the FruitTrades table. I have defined an Update trigger on this table which will calculate the Price and Volume trend whenever a row is inserted or updated in this table.
The daily fruit prices and volume come to me in a flat file which I import into a simple Table called PriceData. Then I move the Price and Volume information from this table to the FruitTrades table using a simple INSERT statement. This fires the update triggers in the FruitTrades, but two of the columns do not get updated by the trigger. Any idea why?
Steps for replicating this problem are as follows:
-- STEP 1 (create the FruitTrades table)
CREATE TABLE [dbo].[FruitTrades](
[FID] [nchar](3) NOT NULL,
[TradeDate] [smalldatetime] NOT NULL,
[TAID] [tinyint] NULL,
[Price] [real] NOT NULL,
[Vol] [int] NULL,
[3DAvgPrice] [real] NULL,
[5DAvgPrice] [real] NULL,
[VolTrend] [real] NULL,
[VolTrendPrevD] [real] NULL,
CONSTRAINT [PK_FruitTrades] PRIMARY KEY CLUSTERED
(
[FID] ASC,
[TradeDate] DESC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY];
-- STEP 2 (Create the Update trigger)
CREATE TRIGGER [dbo].[TRG_FruitTrades_Analysis]
ON [dbo].[FruitTrades]
AFTER INSERT, UPDATE
AS
BEGIN
-- SET NOCOUNT ON added to prevent extra result sets from
-- interfering with SELECT statements.
SET NOCOUNT ON;
UPDATE FruitTrades SET
-- Calculate the 3 day average price
[FruitTrades].[3DAvgPrice] =
(
SELECT AVG(Price) FROM
(
SELECT TOP 3 Price FROM FruitTrades
WHERE FID = [Inserted].[FID] AND TradeDate <= [Inserted].[TradeDate]
) AS Last3Trades
),
-- Calculate the 5 day average price
[FruitTrades].[5DAvgPrice] =
(
SELECT AVG(Price) FROM
(
SELECT TOP 5 Price FROM FruitTrades
WHERE FID = [Inserted].[FID] AND TradeDate <= [Inserted].[TradeDate]
) AS Last5Trades
),
-- Fetch the previous days VolTrend and update VolTrendPrev column
[FruitTrades].[VolTrendPrevD] =
(
SELECT TOP 1 VolTrend FROM FruitTrades
WHERE FID = [Inserted].[FID] AND TradeDate < [Inserted].[TradeDate]
),
-- Calculate Volume Trend and update VolTrend column
[FruitTrades].[VolTrend] =
(
ISNULL([FruitTrades].[VolTrendPrevD], 0) +
([Inserted].[Vol] * (([Inserted].[Price] /
(SELECT TOP 1 Price FROM FruitTrades WHERE FID = [Inserted].[FID] AND TradeDate < [Inserted].[TradeDate])) - 1.0 ))
),
-- Now Update the Action ID column
[FruitTrades].[TAID] =
(
CASE
WHEN [FruitTrades].[3DAvgPrice] >= [FruitTrades].[5DAvgPrice] AND [FruitTrades].[VolTrend] >= [FruitTrades].[VolTrendPrevD] THEN 1
WHEN [FruitTrades].[3DAvgPrice] >= [FruitTrades].[5DAvgPrice] AND [FruitTrades].[VolTrend] <= [FruitTrades].[VolTrendPrevD] THEN 2
WHEN [FruitTrades].[3DAvgPrice] <= [FruitTrades].[5DAvgPrice] AND [FruitTrades].[VolTrend] >= [FruitTrades].[VolTrendPrevD] THEN 3
WHEN [FruitTrades].[3DAvgPrice] <= [FruitTrades].[5DAvgPrice] AND [FruitTrades].[VolTrend] <= [FruitTrades].[VolTrendPrevD] THEN 4
ELSE NULL
END
)
FROM FruitTrades
INNER JOIN Inserted ON Inserted.FID = FruitTrades.FID AND Inserted.TradeDate = FruitTrades.TradeDate
END
-- STEP 3 (Create the PriceData table)
CREATE TABLE [dbo].[PriceData](
[FID] [nchar](3) NOT NULL,
[TradeDate] [smalldatetime] NOT NULL,
[Price] [real] NULL,
[Vol] [real] NULL,
CONSTRAINT [PK_PriceData] PRIMARY KEY CLUSTERED
(
[FID] ASC,
[TradeDate] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
-- STEP 4 (simulate data import into PriceData table)
INSERT INTO PriceData (FID, TradeDate, Price, Vol) VALUES ('APL', '4/30/2012', 200, 1000);
INSERT INTO PriceData (FID, TradeDate, Price, Vol) VALUES ('APL', '4/29/2012', 190, 1200);
INSERT INTO PriceData (FID, TradeDate, Price, Vol) VALUES ('APL', '4/28/2012', 195, 1250);
INSERT INTO PriceData (FID, TradeDate, Price, Vol) VALUES ('APL', '4/27/2012', 205, 1950);
INSERT INTO PriceData (FID, TradeDate, Price, Vol) VALUES ('APL', '4/26/2012', 200, 2000);
INSERT INTO PriceData (FID, TradeDate, Price, Vol) VALUES ('APL', '4/25/2012', 180, 1300);
INSERT INTO PriceData (FID, TradeDate, Price, Vol) VALUES ('APL', '4/24/2012', 185, 1250);
-- STEP 5 (move price vol date from PriceDate table to Fruit Tables)
INSERT INTO FruitTrades (FID, TradeDate, Price, Vol) SELECT FID, TradeDate, Price, Vol FROM PriceData;
-- STEP 6 (check the FruitTrades table for correctness)
SELECT * FROM FruitTrades ORDER BY TradeDate
--- Results
After Step 6 you will find that the TAID and VolTrendPrevD in the FruitTrades table columns remain NULL.
Any help on how to resolve this problem is highly appreciated.
After laboring on this problem for 5 days, and some Googling, I finally found the solution myself.
The first problem was on account of my lack of understanding of triggers and how they fire. As per SQL documentation,
SQL Server triggers fire only once per statement, not once per
affected row
Due to this design principle, when in Step 5, I do a bulk Insert from the PriceData table into the FruitTrades table, the trigger is fired only once, instead of once for each row. Hence the Updated values are incorrect.
The VolTrendPrevD remains null because the Select statement for it in the Update trigger always matches the first row in the FruitTrades table (since the Inserted table has multiple rows) and for this row, the VolTrend is null.
The TAID remains null because VolTrendPrevD is null.
Now the fix:
Import the text file containing the price data into a MSaccess table. From there do a bulk insert in to the SQL Server table (using Linked tables). This approach uses ODBC to convert the bulk insert into multiple single inserts, thus bypassing the first problem.
Convert the VolTrend into a computed column. There is no need to update it therefore in the trigger.
Introduce an additional column PricePrevD in the FruitTrades table and update its value in the trigger, in the same manner as the VolTrendPrevD column.
Most importantly, ensure that the inserted rows from Access are inserted in ascending order by date (by creating an appropriate date index in Access). Else the desired results will be missing.
Hope this helps... :-)