SQL failing to add value from previous row into the next - sql

I am trying to add the value of the previous row to the current row into the column cumulative
Select
Ddate as Date, etype, Reference, linkacc as ContraAcc,
Description,
sum(case when amount > 0 then amount else 0 end) as Debits,
sum(case when amount < 0 then amount else 0 end) as Credits,
sum(amount) as Cumulative
from
dbo.vw_LT
where
accnumber ='8400000'
and [DDate] between '2016-04-01 00:00:00' and '2016-04-30 00:00:00'
and [DataSource] = 'PAS11CEDCRE17'
group by
Ddate, etype, Reference, linkacc, Description, Amount
Output(what i am getting):
Date Reference ContraAcc Description Debits Credits Cumulative
--------------------------------------------------------------------------
2016-04-01 CC007 8000000 D/CC007 0 -39.19 -39.19
2016-04-01 CC007 8000000 D/CC007 1117.09 0 1117.09
2016-04-01 CC009 8000000 CC009 2600 0 2600
in the cumulative column should like below(what i need):
Date Reference ContraAcc Description Debits Credits Cumulative
--------------------------------------------------------------------------
2016-04-01 CC007 8000000 D/CC007 0 -39.19 -39.19
2016-04-01 CC007 8000000 D/CC007 1117.09 0 1077.9
2016-04-01 CC009 8000000 CC009 2600 0 3677.9

Before we delve into the solution, let me tell you that if you are using SQL Server version more than 2012, there are LAG and LEAD, which can help you to solve this.
I am not giving you an exact query to solve your problem (as we dont know what your primary key for that table is), but you can get the idea by seeing the below example
DECLARE #t TABLE
(
accountNumber VARCHAR(50)
,dt DATETIME
,TransactedAmt BIGINT
)
INSERT INTO #t VALUES ('0001','7/20/2016',1000)
INSERT INTO #t VALUES ('0001','7/21/2016',-1000)
INSERT INTO #t VALUES ('0001','7/22/2016',2000)
INSERT INTO #t VALUES ('0002','7/20/2016',500)
INSERT INTO #t VALUES ('0002','7/21/2016',-500)
INSERT INTO #t VALUES ('0002','7/22/2016',2000)
;WITH CTE AS
(
SELECT ROW_NUMBER() OVER(Partition by accountNumber order by dt) as RN, *
FROM #t
),CTE1 AS
(
SELECT *,TransactedAmt As TotalBalance
FROM CTE WHERE rn = 1
UNION
SELECT T1.*,T1.TransactedAmt + T0.TransactedAmt as TotalBalance
FROM CTE T1
JOIN CTE T0
ON T1.accountNumber = T0.accountNumber
AND T1.RN = T0.RN+1
AND T1.RN > 1
)
select * from CTE1 order by AccountNumber

Related

SQL Server: query to get the data between two values from same columns and calculate time difference

I have a requirement to get the number of hours between two values, say 20 and 25 or above (this will be user input values and not fixed). Below is the table with sample data.
Consider in the table on 01-09-2016 08:40 value_ID is 25 and it reaches back to 20 on 02-09-2016 13:20, I need to consider the number of hours between these two range ie 12 hours and 40 min it is .. Similarly 04-09-2016 13:20 it reached 26.3 (which is above 25 ) and '06-09-2016 16:20' reached 19.3 (below 20) and number of hours is 45 hours. I tried creating a function, however it's not working..
CODE TO CREATE TABLE:
CREATE TABLE [dbo].[NumOfHrs](
[ID] [float] NULL,
[Date] [datetime] NULL,
[Value_ID] [float] NULL
) ON [PRIMARY]
CODE to insert data :
INSERT INTO [dbo].[NumOfHrs]
([ID]
,[Date]
,[Value_ID])
VALUES
(112233,'8-31-2016 08:20:00',19.2),
(112233,'9-01-2016 08:30:00',24),
(112233,'9-01-2016 08:40:00',25),
(112233,'9-01-2016 09:20:00',26),
(112233,'9-02-2016 10:20:00',27),
(112233,'9-02-2016 10:20:00',24),
(112233,'9-02-2016 10:20:00',23),
(112233,'9-02-2016 11:20:00',22),
(112233,'9-02-2016 12:20:00',21),
(112233,'9-02-2016 13:20:00',20),
(112233,'9-03-2016 13:20:00',19.8),
(112233,'9-04-2016 13:20:00',21),
(112233,'9-04-2016 14:20:00',24),
(112233,'9-04-2016 16:20:00',24.6),
(112233,'9-04-2016 19:20:00',26.3),
(112233,'9-04-2016 23:20:00',27),
(112233,'9-05-2016 00:20:00',22),
(112233,'9-06-2016 16:20:00',19.3),
(112233,'9-07-2016 00:20:00',22),
(112233,'9-08-2016 00:20:00',21),
(112233,'9-09-2016 00:20:00',23),
(445566,'9-10-2016 00:20:00',24),
(445566,'9-11-2016 00:20:00',25),
(445566,'9-12-2016 00:20:00',26),
(445566,'9-13-2016 00:20:00',24),
(445566,'9-14-2016 00:20:00',23),
(445566,'9-15-2016 00:20:00',24),
(445566,'9-16-2016 00:20:00',21),
(445566,'9-17-2016 00:20:00',20),
(445566,'9-18-2016 00:20:00',18.5),
(445566,'9-19-2016 00:20:00',17)
image of the table:
Well, I couldn't think of anything simpler. Here's my try to solve the problem:
;with NumOfHrs_rn as (
select id, [Date], Value_ID,
row_number() over (partition by id order by [date]) AS rn
from [dbo].[NumOfHrs]
), NumOfHrs_lag as (
select t1.id, t1.[date],
t2.Value_ID as prev_value,
t1.Value_ID as curr_value
from NumOfHrs_rn as t1
-- get previous value (lag)
join NumOfHrs_rn as t2 on t1.id = t2.id and t1.rn = t2.rn + 1
), NumOfHrs_flag as (
select id, [Date], prev_value, curr_value,
case
when curr_value >= 25 and prev_value < 25 then 'start'
when curr_value <= 20 and prev_value > 20 then 'stop'
else 'ignore'
end as flag
from NumOfHrs_lag
), NumOfHrs_grp as (
select id, [Date], curr_value, flag,
row_number() over (partition by id order by [Date]) -
case flag
when 'start' then 0
when 'stop' then 1
end as grp
from NumOfHrs_flag
where flag in ('start', 'stop')
)
select min([Date]) AS 'start', max([Date]) as 'stop'
from NumOfHrs_grp
group by id, grp
order by min([Date])
Output:
start stop
------------------------------------------------
2016-09-01 08:40:00.000 2016-09-02 13:20:00.000
2016-09-04 19:20:00.000 2016-09-06 16:20:00.000
2016-09-11 00:20:00.000 2016-09-17 00:20:00.000
You can manipulate the above query in order to get the time difference expressed in hours/minutes/seconds format.
Demo here

How to find latest status of the day in SQL Server

I have a SQL Server question that I'm trying to figure out at work:
There is a table with a status field which can contain a status called "Participate." I am only trying to find records if the latest status of the day is "Participate" and only if the status changed on the same day from another status to "Participate."
I don't want any records where the status was already "Participate." It must have changed to that status on the same day. You can tell when the status was changed by the datetime field ChangedOn.
In the sample below I would only want to bring back ID 1880 since the status of "Participated" has the latest timestamp. I would not bring back ID 1700 since the last record is "Other," and I would not bring back ID 1600 since "Participated" is the only status of that day.
ChangedOn Status ID
02/01/17 15:23 Terminated 1880
02/01/17 17:24 Participated 1880
02/01/17 09:00 Other 1880
01/31/17 01:00 Terminated 1700
01/31/17 02:00 Participated 1700
01/31/17 03:00 Other 1700
01/31/17 02:00 Participated 1600
I was thinking of using a Window function, but I'm not sure how to get started on this. It's been a few months since I've written a query like this so I'm a bit out of practice.
Thanks!
You can use window functions for this:
select t.*
from (select t.*,
row_number() over (partition by cast(ChangedOn as date)
order by ChangedOn desc
) as seqnum,
sum(case when status <> 'Participate' then 1 else 0 end) over (partition by cast(ChangedOn as date)) as num_nonparticipate
from t
) t
where (seqnum = 1 and ChangedOn = 'Participate') and
num_nonparticipate > 0;
Can you check this?
WITH sample_table(ChangedOn,Status,ID)AS(
SELECT CONVERT(DATETIME,'02/01/2017 15:23'),'Terminated',1880 UNION ALL
SELECT '02/01/2017 17:24','Participated',1880 UNION ALL
SELECT '02/01/2017 09:00','Other',1880 UNION ALL
SELECT '01/31/2017 01:00','Terminated',1700 UNION ALL
SELECT '01/31/2017 02:00','Participated',1700 UNION ALL
SELECT '01/31/2017 03:00','Other',1700 UNION ALL
SELECT '01/31/2017 02:00','Participated',1600
)
SELECT ID FROM (
SELECT *
,ROW_NUMBER()OVER(PARTITION BY ID,CONVERT(VARCHAR,ChangedOn,112) ORDER BY ChangedOn) AS rn
,COUNT(0)OVER(PARTITION BY ID,CONVERT(VARCHAR,ChangedOn,112)) AS cnt
,CASE WHEN Status<>'Participated' THEN 1 ELSE 0 END AS ss
,SUM(CASE WHEN Status!='Participated' THEN 1 ELSE 0 END)OVER(PARTITION BY ID,CONVERT(VARCHAR,ChangedOn,112)) AS OtherStatusCnt
FROM sample_table
) AS t WHERE t.rn=t.cnt AND t.Status='Participated' AND t.OtherStatusCnt>0
--Return:
1880
try this with other sample data,
declare #t table(ChangedOn datetime,Status varchar(50),ID int)
insert into #t VALUES
('02/01/17 15:23', 'Terminated' ,1880)
,('02/01/17 17:24', 'Participated' ,1880)
,('02/01/17 09:00', 'Other' ,1880)
,('01/31/17 01:00', 'Terminated' ,1700)
,('01/31/17 02:00', 'Participated' ,1700)
,('01/31/17 03:00', 'Other' ,1700)
,('01/31/17 02:00', 'Participated' ,1600)
;
WITH CTE
AS (
SELECT *
,row_number() OVER (
PARTITION BY id
,cast(ChangedOn AS DATE) ORDER BY ChangedOn DESC
) AS seqnum
FROM #t
)
SELECT *
FROM cte c
WHERE seqnum = 1
AND STATUS = 'Participated'
AND EXISTS (
SELECT id
FROM cte c1
WHERE seqnum > 1
AND c.id = c1.id
)
2nd query,this is better
here CTE is same
SELECT *
FROM cte c
WHERE seqnum = 1
AND STATUS = 'Participated'
AND EXISTS (
SELECT id
FROM cte c1
WHERE STATUS != 'Participated'
AND c.id = c1.id
)

how to sum tow field at get inline result?

i have tow field for example credit an debit in one table.
and i need to sum them and get result at each line for example :
date debit credit amount
2015/01/01 20 0 20
2015/01/02 0 5 15
2015/01/03 0 30 -15
i hope you help me to get the amount by a query
thanks
With SQL-Server 2012 or newer you can use this:
SELECT [date], debit, credit, amount,
SUM(debit-credit) OVER(ORDER BY [date] ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) AS amount
FROM TableName
ORDER BY [date]
Read: OVER-clause, especially the ROWS | RANGE part
With other versions you have to use a correlated subquery:
SELECT [date], debit, credit, amount,
(SELECT SUM(debit-credit)
FROM TableName t2
WHERE [date] <= t1.[date]) AS amount
FROM TableName t1
ORDER BY [date]
I agree with Tim's answer, I added some extra lines:
declare #credit as table (
[date] datetime,
amount int
)
declare #debit as table (
[date] datetime,
amount int
)
insert into #debit values
('2015-01-01', 20)
insert into #credit values
('2015-01-02', 5),
('2015-01-03', 30)
select
[date], debit, credit, SUM(debit-credit) OVER(ORDER BY [date] ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) AS amount
from(
select
[date], sum(debit) debit, sum(credit) credit
from
(
select
[date], 0 credit, d.amount debit
from
#debit d
union all
select
[date], c.amount credit, 0 debit
from
#credit c
) j group by j.date
) x

SQL Deduct value from multiple rows

I would like to apply total $10.00 discount for each customers.The discount should be applied to multiple transactions until all $10.00 used.
Example:
CustomerID Transaction Amount Discount TransactionID
1 $8.00 $8.00 1
1 $6.00 $2.00 2
1 $5.00 $0.00 3
1 $1.00 $0.00 4
2 $5.00 $5.00 5
2 $2.00 $2.00 6
2 $2.00 $2.00 7
3 $45.00 $10.00 8
3 $6.00 $0.00 9
The query below keeps track of the running sum and calculates the discount depending on whether the running sum is greater than or less than the discount amount.
select
customerid, transaction_amount, transactionid,
(case when 10 > (sum_amount - transaction_amount)
then (case when transaction_amount >= 10 - (sum_amount - transaction_amount)
then 10 - (sum_amount - transaction_amount)
else transaction_amount end)
else 0 end) discount
from (
select customerid, transaction_amount, transactionid,
sum(transaction_amount) over (partition by customerid order by transactionid) sum_amount
from Table1
) t1 order by customerid, transactionid
http://sqlfiddle.com/#!6/552c2/7
same query with a self join which should work on most db's including mssql 2008
select
customerid, transaction_amount, transactionid,
(case when 10 > (sum_amount - transaction_amount)
then (case when transaction_amount >= 10 - (sum_amount - transaction_amount)
then 10 - (sum_amount - transaction_amount)
else transaction_amount end)
else 0 end) discount
from (
select t1.customerid, t1.transaction_amount, t1.transactionid,
sum(t2.transaction_amount) sum_amount
from Table1 t1
join Table1 t2 on t1.customerid = t2.customerid
and t1.transactionid >= t2.transactionid
group by t1.customerid, t1.transaction_amount, t1.transactionid
) t1 order by customerid, transactionid
http://sqlfiddle.com/#!3/552c2/2
You can do this with recursive common table expressions, although it isn't particularly pretty. SQL Server stuggles to optimize these types of query. See Sum of minutes between multiple date ranges for some discussion.
If you wanted to go further with this approach, you'd probably need to make a temporary table of x, so you can index it on (customerid, rn)
;with x as (
select
tx.*,
row_number() over (
partition by customerid
order by transaction_amount desc, transactionid
) rn
from
tx
), y as (
select
x.transactionid,
x.customerid,
x.transaction_amount,
case
when 10 >= x.transaction_amount then x.transaction_amount
else 10
end as discount,
case
when 10 >= x.transaction_amount then 10 - x.transaction_amount
else 0
end as remainder,
x.rn as rn
from
x
where
rn = 1
union all
select
x.transactionid,
x.customerid,
x.transaction_amount,
case
when y.remainder >= x.transaction_amount then x.transaction_amount
else y.remainder
end,
case
when y.remainder >= x.transaction_amount then y.remainder - x.transaction_amount
else 0
end,
x.rn
from
y
inner join
x
on y.rn = x.rn - 1 and y.customerid = x.customerid
where
y.remainder > 0
)
update
tx
set
discount = y.discount
from
tx
inner join
y
on tx.transactionid = y.transactionid;
Example SQLFiddle
I usually like to setup a test environment for such questions. I will use a local temporary table. Please note, I made the data un-ordered since it is not guaranteed in a real life.
-- play table
if exists (select 1 from tempdb.sys.tables where name like '%transactions%')
drop table #transactions
go
-- play table
create table #transactions
(
trans_id int identity(1,1) primary key,
customer_id int,
trans_amt smallmoney
)
go
-- add data
insert into #transactions
values
(1,$8.00),
(2,$5.00),
(3,$45.00),
(1,$6.00),
(2,$2.00),
(1,$5.00),
(2,$2.00),
(1,$1.00),
(3,$6.00);
go
I am going to give you two answers.
First, in 2014 there are new windows functions for rows preceding. This allows us to get a running total (rt) and a rt adjusted by one entry. Give these two values, we can determine if the maximum discount has been exceeded or not.
-- Two running totals for 2014
;
with cte_running_total
as
(
select
*,
SUM(trans_amt)
OVER (PARTITION BY customer_id
ORDER BY trans_id
ROWS BETWEEN UNBOUNDED PRECEDING AND
0 PRECEDING) as running_tot_p0,
SUM(trans_amt)
OVER (PARTITION BY customer_id
ORDER BY trans_id
ROWS BETWEEN UNBOUNDED PRECEDING AND
1 PRECEDING) as running_tot_p1
from
#transactions
)
select
*
,
case
when coalesce(running_tot_p1, 0) <= 10 and running_tot_p0 <= 10 then
trans_amt
when coalesce(running_tot_p1, 0) <= 10 and running_tot_p0 > 10 then
10 - coalesce(running_tot_p1, 0)
else 0
end as discount_amt
from cte_running_total;
Again, the above version is using a common table expression and advanced windowing to get the totals.
Do not fret! The same can be done all the way down to SQL 2000.
Second solution, I am just going to use the order by, sub-queries, and a temporary table to store the information that is normally in the CTE. You can switch the temporary table for a CTE in SQL 2008 if you want.
-- w/o any fancy functions - save to temp table
select *,
(
select count(*) from #transactions i
where i.customer_id = o.customer_id
and i.trans_id <= o.trans_id
) as sys_rn,
(
select sum(trans_amt) from #transactions i
where i.customer_id = o.customer_id
and i.trans_id <= o.trans_id
) as sys_tot_p0,
(
select sum(trans_amt) from #transactions i
where i.customer_id = o.customer_id
and i.trans_id < o.trans_id
) as sys_tot_p1
into #results
from #transactions o
order by customer_id, trans_id
go
-- report off temp table
select
trans_id,
customer_id,
trans_amt,
case
when coalesce(sys_tot_p1, 0) <= 10 and sys_tot_p0 <= 10 then
trans_amt
when coalesce(sys_tot_p1, 0) <= 10 and sys_tot_p0 > 10 then
10 - coalesce(sys_tot_p1, 0)
else 0
end as discount_amt
from #results
order by customer_id, trans_id
go
In short, your answer is show in the following screen shot. Cut and paste the code into SSMS and have some fun.

SQL Rollup last 4 weeks total

I have a table which I want to get the previous four weeks Order total in a query. But I want to return it with a SELECT (A total of the row's previous 4 weeks Order1 column - if they exist)
PurchasingID Order1 Date FourWeekTotal
------------ ------------------- ------- ---------------
1 1.00 2013-04-21 14.00
2 2.00 2013-04-14 12.00
3 3.00 2013-04-07 9.00
4 4.00 2013-03-31 5.00
5 5.00 2013-03-24 0.00
My understanding is for each record in your table, you want to see the sum of Order1 for itself and each record that has a Date value within four weeks prior to the primary record. Here you go:
create table MysteryTable
(
PurchasingId int not null primary key identity(1,1),
Order1 money not null,
[Date] date not null
)
insert MysteryTable( Order1, [Date] ) values ( 1.00, '2013-04-21' )
insert MysteryTable( Order1, [Date] ) values ( 2.00, '2013-04-14' )
insert MysteryTable( Order1, [Date] ) values ( 3.00, '2013-04-07' )
insert MysteryTable( Order1, [Date] ) values ( 4.00, '2013-03-31' )
insert MysteryTable( Order1, [Date] ) values ( 5.00, '2013-03-24' )
select
t1.PurchasingId
, t1.Order1
, t1.Date
, SUM( ISNULL( t2.Order1, 0 ) ) FourWeekTotal
from
MysteryTable t1
left outer join MysteryTable t2
on DATEADD( ww, -4, t1.Date ) <= t2.Date and t1.Date > t2.Date
group by
t1.PurchasingId
, t1.Order1
, t1.Date
order by
t1.Date desc
Explanation:
Join the table on itself, t1 representing the records to return, t2 to be the records to aggregate. Join based on t1's Date minus four weeks being less than or equal to t2's Date and t1's Date being greater than t2's Date. Then group the records by the t1 fields and sum t2.Order1. Left outer join is to account for the one record that will not have any preceding data.
Try this...
Declare #LBDate date
SET #LBDate = DATEADD(d,-28,getdate())
Now write ur select query...
Select * from Orders where Date between #LBDate and Getdate()
You can also use your required date instead to current date..