Can't use SQL lag sum - sql

My query is like this
SELECT
[day], [time],AvaliableTimes,
CASE
WHEN AvaliableTimes > 0
THEN SUM(AvaliableTimes) OVER (ORDER BY [day], [time], AvaliableTimes)
ELSE 0
END AS SumValue
FROM
[AvailableTimes]
WHERE
[day] = 1 AND BranchAreaId = 1
ORDER BY
[day], [time], AvaliableTimes
I want to start sum from 0 if value is null or 0.
Results:

you can use a recursive CTE to do it. Perform the cumulative sum in the rcte and if AvailableTimes = 0, reset it
; with
cte as
(
select *, rn = row_number() over (order by time)
from yourtable
),
rcte as
(
select *, sumvalues = AvailableTimes
from cte
where rn = 1
union all
select c.*, sumvalues = case when c.AvailableTimes <> 0
then r.sumvalues + c.AvailableTimes
else c.AvailableTimes
end
from cte c
inner join rcte r on c.rn = r.rn + 1
)
select day, time, AvailableTimes, sumvalues
from rcte
order by time

Related

Problem with Recursive CTE very long query plan

When I execute below query SQL run this plan and it took a long time to run it and it will not be over.
QueryPlanLink
I have 3 million records in #T table.
myCode:
;WITH cte1 AS (
SELECT NationalId,len(NationalId) as LenNationalId,CustomerType,FullDateInt,time,
SUM(Price) as SUMPrice
,AVG(Price) as Price
,SUM(Volume) as Volume
,SUM (sum([Volume])) OVER (PARTITION BY NationalId,len(NationalId) ORDER BY FullDateInt,[Time]) as SumVol
,ROW_NUMBER() OVER (PARTITION BY NationalId,len(NationalId) ORDER BY FullDateInt,[Time]) AS rn
from #T as T1
group by NationalId,len(NationalId),CustomerType,FullDateInt,time
), rcte AS (
SELECT *, Price AS Cost , cast(0 as decimal) as Profit
FROM cte1 AS base
WHERE base.rn = 1
UNION ALL
SELECT curr.*, Case when curr.Volume>0 Then ((curr.Volume *curr.Price) + (prev.Cost*prev.SumVol))/nullif(curr.SumVol,0)
when curr.Volume<0 Then prev.Cost
End
as Cost
,ISNULL(Cast (Case when curr.Volume<0 Then -1*(curr.Price-Cost)*curr.Volume End as decimal),0) as Profit
FROM cte1 AS curr
INNER JOIN rcte AS prev
ON curr.NationalId = prev.NationalId AND curr.rn = prev.rn + 1
)
Select * from rcte
option (maxrecursion 0)
Is there any way to make it better?
Thanks
I Change My Query like below And Everything is Done. Thanks For All.
SELECT NationalId,len(NationalId) as LenNationalId,CustomerType,FullDateInt,time,
SUM(Price) as SUMPrice
,AVG(Price) as Price
,SUM(Volume) as Volume
,SUM (sum([Volume])) OVER (PARTITION BY NationalId,len(NationalId) ORDER BY FullDateInt,[Time]) as SumVol
,ROW_NUMBER() OVER (PARTITION BY NationalId,len(NationalId) ORDER BY FullDateInt,[Time]) AS rn
into #TCTE from #T as T1
group by NationalId,len(NationalId),CustomerType,FullDateInt,time
;With rcte AS (
SELECT *, Price AS Cost , cast(0 as decimal) as Profit
FROM #TCTE AS base
WHERE base.rn = 1
UNION ALL
SELECT curr.*, Case when curr.Volume>0 Then ((curr.Volume *curr.Price) + (prev.Cost*prev.SumVol))/nullif(curr.SumVol,0)
when curr.Volume<0 Then prev.Cost
End
as Cost
,ISNULL(Cast (Case when curr.Volume<0 Then -1*(curr.Price-Cost)*curr.Volume End as decimal),0) as Profit
FROM #TCTE AS curr
INNER JOIN rcte AS prev
ON curr.NationalId = prev.NationalId AND curr.rn = prev.rn + 1
)
Select *
into #TFinal from rcte
option (maxrecursion 0)

How to calculate total hours from multiple in time and out time from below?

first punch as in time,
second punch as out time
if possible avoid duplicate punch on same time within a minute
I need to get all in time ,outtime in a row with total hours
like below any format.
I tried below query but can't get my expected output
WITH Level1
AS (
SELECT A.emp_reader_id,
DT
,A.EventCatId
,A.Belongs_to
,ROW_NUMBER() OVER ( PARTITION BY A.Belongs_to,A.emp_reader_id ORDER BY DT ) AS RowNum
FROM dbo.trnevents A
)
,
LEVEL2
AS (-- find the last and next event type for each row
SELECT A.emp_reader_id,A.DT , A.EventCatId ,COALESCE(LastVal.EventCatId, 10) AS LastEvent,
COALESCE(NextVal.EventCatId, 10) AS NextEvent ,A.Belongs_to
FROM Level1 A
LEFT JOIN Level1 LastVal
ON A.emp_reader_id = LastVal.emp_reader_id and A.Belongs_to=LastVal.Belongs_to
AND A.RowNum - 1 = LastVal.RowNum
LEFT JOIN Level1 NextVal
ON A.emp_reader_id = NextVal.emp_reader_id and A.Belongs_to=NextVal.Belongs_to
AND A.RowNum + 1 = NextVal.RowNum
)
select * from level2 where emp_reader_id=92 order by dt desc
Expected output:
Try this below script. I considered all DT with Sam Minutes as single entry for the calculation.
WITH CTE AS
(
SELECT MAX(emp_reader_id) emp_reader_id,
CAST(DT AS DATE) Date_for_Group,
LEFT(CAST(DT AS VARCHAR),16) Time_For_Group,
ROW_NUMBER() OVER(PARTITION BY CAST(DT AS DATE) ORDER BY LEFT(CAST(DT AS VARCHAR),16)) RN,
CASE
WHEN ROW_NUMBER() OVER(PARTITION BY CAST(DT AS DATE) ORDER BY LEFT(CAST(DT AS VARCHAR),16))%2 = 0 THEN 'OUT'
ELSE 'IN'
END In_Out
FROM your_table
GROUP BY CAST(DT AS DATE),LEFT(CAST(DT AS VARCHAR),16)
)
SELECT A.emp_reader_id,A.Date_for_Group,
SUM(DATEDIFF(Minute,CAST(A.Time_For_Group AS DATETIME),CAST(B.Time_For_Group AS DATETIME)))/60 Hr,
SUM(DATEDIFF(Minute,CAST(A.Time_For_Group AS DATETIME),CAST(B.Time_For_Group AS DATETIME)))%60 Min
FROM CTE A
INNER JOIN CTE B
ON A.emp_reader_id = B.emp_reader_id
AND A.RN = B.RN -1
AND A.Date_for_Group = B.Date_for_Group
WHERE A.In_Out = 'IN'
GROUP BY A.emp_reader_id,A.Date_for_Group
first assign rownumber to datetime column then start the same result set with rownumber+1
Then Inner join them on rownumbers. After that select min an max from timein and out columns and group by on date to get total workhours of that day. hope it helps.
select empid
,date
,min(timein) as timein,max (timeout) timeout,convert(nvarchar(20),datediff(hh,min (timein),max(timeout))%24)
+':'+
convert(nvarchar(20),datediff(mi,min (timein),max(timeout))%60) as totalhrs
from(
Select a.empid,cast(a.dt as date) date,b.dt as timein,a.dt as timeout from(
SELECT DT
,[empid]
, id
,row_number() over(order by dt) as inn
FROM [test1].[dbo].[Table_2]
)a
inner join(
SELECT distinct DT
,[empid]
, id
,rank() over(order by dt)+1 as out
FROM [test1].[dbo].[Table_2])b
on FORMAT(a.dt,'hh:mm') <> FORMAT(b.dt,'hh:mm')
and cast(a.dt as date)=cast(b.dt as date)
and a.inn=b.out)b
group by b.empid,b.date

SQL server to do like Group By task

I have a table with SQL server as below,
Date Value
---------------------------------------------------
08-01-2016 1
08-02-2016 1
08-03-2016 1
08-04-2016 1
08-05-2016 1
08-06-2016 2
08-07-2016 2
08-08-2016 2
08-09-2016 2.5
08-10-2016 1
08-11-2016 1
Since the original table is too large, even I used 'Results to file', it still raise the exception 'System.OutOfMemoryException'. That's why I want to organize the table into this kind.
But I don't have a good logic to deal with. Therefore, I want to change the table into this kind as below.
Date_from Date_to Value
-------------------------------------------------
08-01-2016 08-05-2016 1
08-06-2016 08-08-2016 2
08-09-2016 08-09-2016 2.5
08-10-2016 08-11-2016 1
I appreciate your ideas!
Commonly called as Groups and Island problem. Here is one trick to do this
;WITH data
AS (SELECT *,Lag(Value, 1)OVER(ORDER BY Dates) [pVal]
FROM (VALUES ('08-01-2016',1 ),
('08-02-2016',1 ),
('08-03-2016',1 ),
('08-04-2016',1 ),
('08-05-2016',1 ),
('08-06-2016',2 ),
('08-07-2016',2 ),
('08-08-2016',2 ),
('08-09-2016',2.5 ),
('08-10-2016',1 ),
('08-11-2016',1 )) tc (Dates, Value)),
intr
AS (SELECT Dates,
Value,
Sum(Iif(pVal = Value, 0, 1)) OVER(ORDER BY Dates) AS [Counter]
FROM data)
SELECT Min(Dates) AS Dates_from,
Max(Dates) AS Dates_to,
Value
FROM intr
GROUP BY [Counter],
Value
The cumulative sum/lag approach is one method. In this case, a simpler method is:
select min(date) as date_from, max(date) as date_to, value
from (select t.*,
dateadd(day, - row_number() over (partition by value order by date),date) as grp
from t
) t
group by value, grp;
This uses the observation that the dates are consecutive with no gaps. Hence, subtracting a sequence from the date will yield a constant -- when the values are the same.
Here is an example:
DECLARE #T TABLE (
[Date] DATE,
[Value] DECIMAL(9,2)
)
INSERT #T VALUES
( '08-01-2016', 1 ),
( '08-02-2016', 1 ),
( '08-03-2016', 1 ),
( '08-04-2016', 1 ),
( '08-05-2016', 1 ),
( '08-06-2016', 2 ),
( '08-07-2016', 2 ),
( '08-08-2016', 2 ),
( '08-09-2016', 2.5 ),
( '08-10-2016', 1 ),
( '08-11-2016', 1 )
SELECT * FROM #T
SELECT A.[Date] StartDate, B.[Date] EndDate, A.[Value] FROM (
SELECT A.*, ROW_NUMBER() OVER (ORDER BY A.[Date], A.[Value]) O FROM #T A
LEFT JOIN #T B ON B.[Value] = A.[Value] AND B.[Date] = DATEADD(d, -1, A.[Date])
WHERE B.[Date] IS NULL
) A
JOIN (
SELECT A.*, ROW_NUMBER() OVER (ORDER BY A.[Date], A.[Value]) O FROM #T A
LEFT JOIN #T B ON B.[Value] = A.[Value] AND B.[Date] = DATEADD(d, 1, A.[Date])
WHERE B.[Date] IS NULL
) B ON B.O = A.O
Prdp's solution is great but just in case if anyone is still using SQL Server 2008 where LAG() and The Parallel Data Warehouse (PDW) features are not available here is an alternative:
SAMPLE DATA:
IF OBJECT_ID('tempdb..#Temp') IS NOT NULL
DROP TABLE #Temp;
CREATE TABLE #Temp([Dates] DATE
, [Value] FLOAT);
INSERT INTO #Temp([Dates]
, [Value])
VALUES
('08-01-2016'
, 1),
('08-02-2016'
, 1),
('08-03-2016'
, 1),
('08-04-2016'
, 1),
('08-05-2016'
, 1),
('08-06-2016'
, 2),
('08-07-2016'
, 2),
('08-08-2016'
, 2),
('08-09-2016'
, 2.5),
('08-10-2016'
, 1),
('08-11-2016'
, 1);
QUERY:
;WITH Seq
AS (SELECT SeqNo = ROW_NUMBER() OVER(ORDER BY [Dates]
, [Value])
, t.Dates
, t.[Value]
FROM #Temp t)
SELECT StartDate = MIN([Dates])
, EndDate = MAX([Dates])
, [Value]
FROM
(SELECT [Value]
, [Dates]
, SeqNo
, rn = SeqNo - ROW_NUMBER() OVER(PARTITION BY [Value] ORDER BY SeqNo)
FROM Seq s) a
GROUP BY [Value]
, rn
ORDER BY StartDate;
RESULTS:

Get previous row updated value using LAG Without using Recursive CTE

How to use LAG function to get the updated previous row value (without using Recursive CTE). Please check the screenshot for sample output
Query Tried
Declare #Tbl as Table(SNO Int,Credit Money,Debit Money,PaidDate Date)
Insert into #Tbl
SELECT * FROM (VALUES (1,0,12,'7Jan16'), (2,10,0,'6Jan16'), (3,15,0,'5Jan16'), (4,0,5,'4Jan16'), (5,0,3,'3Jan16'), (6,0,2,'2Jan16'), (7,20,0,'1Jan16')) AS X(SNO,Credit,Debit,PaidDate)
Select
T.SNO,
T.Credit,
T.Debit,
TotalDebit = Case When Credit < LAG(T.Debit, 1, 0) OVER (ORDER BY SNO) Then Debit + (LAG(T.Debit, 1, 0) OVER (ORDER BY SNO)-Credit) Else Debit End,
Amount = Case When Credit < LAG(T.Debit, 1, 0) OVER (ORDER BY SNO) Then 0 Else Credit-LAG(T.Debit, 1, 0) OVER (ORDER BY SNO) End,
T.PaidDate
From #Tbl T
UPDATE:
Can get the expected result using recursive CTE, but when i convert the query to function and when i join the function with 3000 record, takes long time to execute. That's why i am trying to convert the query without recursive CTE part.
Recursive CTE Query:
Declare #Tbl as Table(SNO Int,Credit Money,Debit Money,PaidDate Date)
Insert into #Tbl
SELECT * FROM (VALUES (1,0,12,'7Jan16'), (2,10,0,'6Jan16'), (3,15,0,'5Jan16'), (4,0,5,'4Jan16'), (5,0,3,'3Jan16'), (6,0,2,'2Jan16'), (7,20,0,'1Jan16')) AS X(SNO,Credit,Debit,PaidDate)
;With Temp As(/* Detect Debited amount */
Select Top 1 SNO,Credit,Debit,Debit As TotalDebit,Credit As Amount,PaidDate From #Tbl
Union All
Select
R.SNO,
R.Credit,
R.Debit,
TotalDebit = Case When R.Credit < RP.TotalDebit Then R.Debit + (RP.TotalDebit-R.Credit) Else R.Debit End,
Amount = Case When R.Credit < RP.TotalDebit Then 0 Else R.Credit-RP.TotalDebit End,
R.PaidDate
From #Tbl R
Inner Join Temp RP ON R.SNO-1=RP.SNO
)
Select * From Temp
Spreadsheet sample:
https://docs.google.com/spreadsheets/d/1FNwzgGxmLiLFS_R5QANnfd16Iw64xhF0gWTc4ZocKsk/edit?usp=sharing
Performance here is suffering from recursive CTE. CTE on it's own is just syntactic sugar.
Just for this particular sample data this works without recursion:
Declare #Tbl as Table(SNO Int,Credit Money,Debit Money,PaidDate Date)
Insert into #Tbl
SELECT * FROM (VALUES (1,0,12,'7Jan16'), (2,10,0,'6Jan16'), (3,15,0,'5Jan16'), (4,0,5,'4Jan16'), (5,0,3,'3Jan16'), (6,0,2,'2Jan16'), (7,20,0,'1Jan16')) AS X(SNO,Credit,Debit,PaidDate);
With CTE1 As (
Select *
, CASE WHEN Credit > 0 THEN LEAD(1 - SIGN(Credit), 1, 1) OVER (ORDER BY SNO) ELSE 0 END As LastCrPerBlock
From #Tbl
), CTE2 As (
Select *
, SUM(LastCrPerBlock) OVER (ORDER BY SNO DESC ROWS UNBOUNDED PRECEDING) As BlockNumber
From CTE1
), CTE3 As (
Select *
, SUM(Credit - Debit) OVER (PARTITION BY BlockNumber) As BlockTotal
, SUM(Credit - Debit) OVER (PARTITION BY BlockNumber ORDER BY SNO ROWS UNBOUNDED PRECEDING) As BlockRunningTotal
From CTE2
)
Select SNO, Credit, Debit
, CASE WHEN BlockRunningTotal < 0 THEN -BlockRunningTotal ELSE 0 END As TotalDebit
, CASE WHEN BlockRunningTotal > 0 THEN CASE WHEN Credit < BlockRunningTotal THEN Credit ELSE BlockRunningTotal END ELSE 0 END As Amount
, PaidDate
From CTE3
Order By SNO;
This can help evaluate performance, but it will fail if in any block total of Debits exceed total of Credits. If BlockTotal is negative then it must be merged with one or several following blocks and that can't be done without iteration or recursion.
In real life I would dump CTE3 into temporary table and cycle over it merging blocks until there are no more negative BlockTotals.
From Y.B's answer, added recursive CTE to handle if any BlockTotal have negative. Cannot use while loop for recursion because i converted this query to inline table valued function.(Multi-statement table valued function is very slow)
Declare #Tbl as Table(ReceiptNo varchar(50),Credit Money,Debit Money,PaidDate Date)
Insert into #Tbl
SELECT * FROM (VALUES ('R1',20,0,'1Jan16'),('R2',0,2,'2Jan16'),('R3',0,3,'3Jan16'),('R4',0,5,'4Jan16'),('R5',10,0,'5Jan16'),('R6',0,1,'6Jan16'),('R7',0,10,'7Jan16')) AS X(ReceiptNo,Credit,Debit,PaidDate);
With Receipts As (
Select
SNO = ROW_NUMBER() OVER(ORDER BY PaidDate Desc),ReceiptNo,Credit,Debit,PaidDate,
LastCrPerBlock = CASE WHEN Credit > 0 THEN LEAD(1 - SIGN(Credit), 1, 1) OVER (ORDER BY PaidDate DESC) ELSE 0 END
From #Tbl
), Blocks As (
Select *
, SUM(LastCrPerBlock) OVER (ORDER BY SNO DESC ROWS UNBOUNDED PRECEDING) As BlockNumber
From Receipts
), BlockTotal As (
Select *
, SUM(Credit - Debit) OVER (PARTITION BY BlockNumber) As BlockTotal
, SUM(Credit - Debit) OVER (PARTITION BY BlockNumber ORDER BY SNO ROWS UNBOUNDED PRECEDING) As BlockRunningTotal
From Blocks
),
ReceiptAmount As (
Select ReceiptNo,
Amount = CASE WHEN BlockRunningTotal > 0 THEN CASE WHEN Credit < BlockRunningTotal THEN Credit ELSE BlockRunningTotal END ELSE 0 END,
Debit = IIF(BlockNumber<>LEAD(BlockNumber) OVER(ORDER BY SNO) and BlockRunningTotal<0,ABS(BlockRunningTotal),0),
PaidDate
From BlockTotal
),
FinalReceipt2012 As (
Select
SNO = ROW_NUMBER() OVER(ORDER BY PaidDate Desc),ReceiptNo,Amount,Debit,PaidDate,
Recur = IIF(Exists(Select Top 1 R1.Amount From ReceiptAmount R1 Where Debit>0),1,0)
From ReceiptAmount
Where Amount>0 or Debit>0
),
FinalReceipt As (
Select * From FinalReceipt2012 Where Recur=0 OR SNO=1
Union All
Select
R.SNO,R.ReceiptNo,
Amount = Case When R.Amount < RP.Debit Then 0 Else R.Amount-RP.Debit End,
Debit = Case When R.Amount < RP.Debit Then R.Debit + (RP.Debit-R.Amount) Else R.Debit End,
R.PaidDate,0 As Recur
From FinalReceipt2012 R
Inner Join FinalReceipt RP ON R.SNO=RP.SNO+1
Where R.Recur=1
)
Select ReceiptNo,Amount,PaidDate From FinalReceipt Where Amount>0
Input:
Output:

Find nearest date to start and end of the month

Table contains daily snapshots of specific parameter, but data can be missing for some days. Task is to calculate amount per month, for this sake we need values on start/end of the month, if data is missing, we need pairs of nearest dates i.e:
[Time] Value
2015-04-28 00:00:00.000 76127
2015-05-03 00:00:00.000 76879
2015-05-22 00:00:00.000 79314
2015-06-07 00:00:00.000 81443
Currently i use following code:
select
*
from(
select
[Time],
Value,
ROW_NUMBER() over (partition by CASE WHEN [Time] < '2015-05-01' THEN 1 ELSE 0 END order by abs(DATEDIFF(DAY, '2015-05-01', [Time]))) as rn2,
ROW_NUMBER() over (partition by CASE WHEN [Time] > '2015-05-01' THEN 1 ELSE 0 END order by abs(DATEDIFF(DAY, [Time], '2015-05-01'))) as rn3,
ROW_NUMBER() over (partition by CASE WHEN [Time] < '2015-05-31' THEN 1 ELSE 0 END order by abs(DATEDIFF(DAY, '2015-05-31', [Time]))) as rn4,
ROW_NUMBER() over (partition by CASE WHEN [Time] > '2015-05-31' THEN 1 ELSE 0 END order by abs(DATEDIFF(DAY, [Time], '2015-05-31'))) as rn5,
DATEDIFF(DAY, '2015-05-01', [Time]) as doff,
DATEDIFF(DAY, '2015-05-31', [Time]) as doff2
from
ValueTable
where
[Time] between '2015-04-01' and '2015-06-30'
) r
where
doff = 0 or doff2 = 0 or (doff != 0 and rn2 = 1 and rn3 = 1) or (doff2 != 0 and rn4 = 1 and rn5 = 1)
Is there any more efficient way to do it?
The following code is going to look more complicated because it is longer. However, it should be very fast, because it can make very good use of an index on ValueTable([Time]).
The idea is to look for exact matches. If there are no exact matches, then find the first and last records before and after the dates. This requires union all on six subqueries, but each should make optimal use of an index:
with exact_first as (
select t.*
from ValueTable t
where [Time] = '2015-05-01'
),
exact_last as (
select t.*
from ValueTable t
where [Time] = '2015-05-01'
)
(select ef.*
from exact_first ef
) union all
(select top 1 t.*
from ValueTable t
where [Time] < '2015-05-01' and
not exists (select 1 from exact_first ef2)
order by [Time]
) union all
(select top 1 t.*
from ValueTable t
where [Time] > '2015-05-01' and
not exists (select 1 from exact_first ef2)
order by [Time] desc
) union all
(select el.*
from exact_last el
) union all
(select top 1 t.*
from ValueTable t
where [Time] < '2015-05-31' and
not exists (select 1 from exact_last ef2)
order by [Time]
) union all
(select top 1 t.*
from ValueTable t
where [Time] > '2015-05-31' and
not exists (select 1 from exact_last ef2)
order by [Time] desc;
)