T-SQL - 3 month moving sum - preceding null values - sql-server

Using SQL Server 2016. I have the following data table (sample)
Target Date Total
-----------------
2018-01-24 1
2018-02-28 1
2018-03-02 1
2018-03-08 1
2018-03-15 1
2018-03-30 1
2018-04-16 1
2018-04-18 1
2018-04-30 1
I would like to get to get a 3 month moving sum (grouping is by month):
Target Date Total_Sum
-----------------------
2018-01-01 1
2018-02-01 2
2018-03-01 6
2018-04-01 8

Ok, this should get the answer you want. Firstly you need to total the value your months, then you can do a running total for the last 3 months:
CREATE TABLE SampleTable (TargetDate date, Total int);
GO
INSERT INTO SampleTable
VALUES ('20180124', 1),
('20180228', 1),
('20180302', 1),
('20180308', 1),
('20180315', 1),
('20180330', 1),
('20180416', 1),
('20180418', 1),
('20180430', 1);
GO
SELECT *
FROM SampleTable;
GO
WITH Months AS (
SELECT DATEADD(MONTH,DATEDIFF(MONTH, 0, TargetDate),0) AS TargetMonth, SUM(Total) AS MonthTotal
FROM SampleTable
GROUP BY DATEADD(MONTH,DATEDIFF(MONTH, 0, TargetDate),0))
SELECT TargetMonth,
SUM(MonthTotal) OVER (ORDER BY TargetMonth ROWS BETWEEN 2 PRECEDING AND CURRENT ROW) AS Last3Months
FROM Months;
GO
DROP TABLE SampleTable;
GO

Pls try the below code
;WITH CTE(TargetDate,Total)
AS
(
SELECT '2018-01-24', 1 UNION ALL
SELECT '2018-02-28', 1 UNION ALL
SELECT '2018-03-02', 1 UNION ALL
SELECT '2018-03-08', 1 UNION ALL
SELECT '2018-03-15', 1 UNION ALL
SELECT '2018-03-30', 1 UNION ALL
SELECT '2018-04-16', 1 UNION ALL
SELECT '2018-04-18', 1 UNION ALL
SELECT '2018-04-30', 1
)
SELECT STUFF(TargetDate,9,2,'01') AS TargetDate
,Total_Sum
FROM
(
SELECT TargetDate,Total_Sum
,ROW_NUMBER()OVER(PARTITION BY Total_Sum ORDER BY TargetDate) AS Seq
FROM
(
SELECT TargetDate
,SUM(Total )OVER(ORDER BY MONTH(TargetDate) ) AS Total_Sum
FROM CTE
)dt
)fnl
WHERE Seq=1
Result
TargetDate Total_Sum
---------------------
2018-01-01 1
2018-02-01 2
2018-03-01 6
2018-04-01 9

Related

How to Sum (MAX values) from different value groups in same column SQL Server

I have a table like this:
Date
Consec_Days
2015-01-01
1
2015-01-03
1
2015-01-06
1
2015-01-07
2
2015-01-09
1
2015-01-12
1
2015-01-13
2
2015-01-14
3
2015-01-17
1
I need to Sum the max value (days) for each of the consecutive groupings where Consec_Days are > 1. So the correct result would be 5 days.
This is a type of gaps-and-islands problem.
There are many solutions, here is one simple one
Get the start points of each group using LAG
Calculate a grouping ID using a windowed conditional count
Group by that ID and take the highest sum
WITH StartPoints AS (
SELECT *,
IsStart = CASE WHEN LAG(Consec_Days) OVER (ORDER BY Date) = 1 THEN 1 END
FROM YourTable t
),
Groupings AS (
SELECT *,
GroupId = COUNT(IsStart) OVER (ORDER BY Date)
FROM StartPoints
WHERE Consec_Days > 1
)
SELECT TOP (1)
SUM(Consec_Days)
FROM Groupings
GROUP BY
GroupId
ORDER BY
SUM(Consec_Days) DESC;
db<>fiddle
with cte as (
select Consec_Days,
coalesce(lead(Consec_Days) over (order by Date), 1) as next
from YourTable
)
select sum(Consec_Days)
from cte
where Consec_Days <> 1 and next = 1
db<>fiddle

To remove duplication of data if within 7 days

Following is my table and sample data
DECLARE #Employee_Log table(ID int,eid int, ecode varchar(100), emp_startdate date)
INSERT INTO #Employee_Log
SELECT 1, 1, 'aaa','2019-01-01'
UNION ALL
SELECT 2, 1, 'aaa','2019-01-05'
UNION ALL
SELECT 3, 1, 'bbb','2019-01-03'
UNION ALL
SELECT 4, 2, 'aaa','2019-01-03'
UNION ALL
SELECT 5, 1, 'aaa','2019-02-01'
UNION ALL
SELECT 6, 1, 'aaa','2019-02-15'
UNION ALL
SELECT 7, 1, 'aaa','2019-02-19'
UNION ALL
SELECT 8, 1, 'aaa','2019-02-28'
In the above data I want to remove the duplication based on eid and ecode .If the emp_startdate are within 7 days then take the latest data and ignore the rest data.
I tried the following code but how to add the condition check for week range
SELECT
ROW_NUMBER() OVER(PARTITION BY eid,ecode ORDER BY emp_startdate desc) as rownum,
ID,eid,ecode,emp_startdate
FROM #Employee_Log
I want the result as shown below
ID eid ecode emp_startdate
2 1 aaa 2019-01-05
5 1 aaa 2019-02-01
4 2 aaa 2019-01-03
7 1 aaa 2019-02-19
8 1 aaa 2019-02-28
3 1 bbb 2019-01-03
I am still not sure what you want to happen if more than 2 events happen in the same 7 days. But this solution will get the latest date of all series of dates where the difference between dates is 7 days or less.
select ID,eid,ecode,emp_startdate
from
(
select ID,
eid,
ecode,
emp_startdate,
datediff(day
,emp_startdate
,lead(emp_startdate)
over
(partition by eid,ecode order by emp_startdate)) l
from #Employee_Log
) a
where l is null or l>7
ID eid ecode emp_startdate
-- --- ----- -------------
3 1 bbb 2019-01-03
2 1 aaa 2019-01-05
5 1 aaa 2019-02-01
7 1 aaa 2019-02-19
8 1 aaa 2019-02-28
4 2 aaa 2019-01-03
The following query will give you what you have asked to get in plain English in your question but your sample data and desired output contradicts your own question:
SELECT *
FROM
(
SELECT
ROW_NUMBER() OVER (PARTITION BY eid , ecode , YEAR(emp_startdate)
, DATEPART(WEEK, emp_startdate)
ORDER BY emp_startdate DESC
) AS rownum
, ID
, eid
, ecode
, emp_startdate
FROM #Employee_Log
) x
WHERE x.rownum = 1;

T-SQL create multiply records from one records

I have a cost record and I would like to create N records from it.
The children records have some different parameters.
For example:
The parents record:
date | amount | duration
20170201 | 5000 | 5 months
The children records:
date | amount | duration
20170301 | 1000 | 1 months
20170401 | 1000 | 1 months
20170501 | 1000 | 1 months
20170601 | 1000 | 1 months
20170701 | 1000 | 1 months
How can I do this without iteration? Without cursor or while?
Following SQL CTE query could be used based on Abdul's solution
/*
Create Table PARENT (PARENT_DATE DATE, PARENT_AMOUNT DECIMAL(18,2),PARENT_MONTH INT)
INSERT INTO PARENT SELECT '20170201',5000 ,5
INSERT INTO PARENT SELECT '20180601',120 ,3
*/
;WITH CTE_CHILD
AS (
SELECT
Parent_Date,
Parent_Amount,
Parent_Month,
DateAdd(Month, 1, Parent_Date) as Child_Date,
Parent_Amount/Parent_Month AS Child_Amount,
1 AS Child_Duration
FROM Parent
UNION ALL
SELECT
Parent_Date,
Parent_Amount,
Parent_Month,
DateAdd(Month, 1, Child_Date) as Child_Date,
Child_Amount,
Child_Duration
FROM CTE_CHILD
WHERE
DateAdd(Month, 1, Child_Date) <= DateAdd(Month, Parent_Month, Parent_Date)
)
SELECT
Child_Date,
Child_Amount,
Child_Duration
FROM CTE_CHILD
assuming you have a table like below:
create table tblRecords ( date int, amount money, duration int);
insert into tblRecords values
(20170201,5000,5),
(20180101,9000,3);
you can use a query like below:
select
date= date + r*100
,amount= amount/duration
,duration =1
from tblRecords
cross apply
(
select top (select duration)
r= row_number() over(order by (select null))
from
sys.objects s1
cross join
sys.objects s2
) h
see working demo
One method is CTE.
DECLARE #PARENT AS TABLE
(PARENT_DATE DATE, PARENT_AMOUNT DECIMAL(18,2),PARENT_MONTH INT)
INSERT INTO #PARENT
SELECT '20170201',5000 ,5
;WITH CTE_CHILD
AS (
SELECT DATEADD(MONTH,1,PARENT_DATE) AS CHILD_DATE
,PARENT_AMOUNT/PARENT_MONTH AS CHILD_AMOUNT
,1 AS CHILD_DURATION
FROM #PARENT
WHERE DATEADD(MONTH,1,PARENT_DATE) <= DATEADD(MONTH,PARENT_MONTH,PARENT_DATE)
UNION ALL
SELECT DATEADD(MONTH,1,CHILD_DATE)
,PARENT_AMOUNT/PARENT_MONTH
,1
FROM CTE_CHILD
INNER JOIN #PARENT ON DATEADD(MONTH,1,CHILD_DATE) <= DATEADD(MONTH,PARENT_MONTH,PARENT_DATE)
)
SELECT * FROM CTE_CHILD
option (maxrecursion 0)
Output:-
CHILD_DATE CHILD_AMOUNT CHILD_DURATION
2017-03-01 1000.0000000000000 1
2017-04-01 1000.0000000000000 1
2017-05-01 1000.0000000000000 1
2017-06-01 1000.0000000000000 1
2017-07-01 1000.0000000000000 1

Finding the Datediff between Records in same Table

IP QID ScanDate Rank
101.110.32.80 6 2016-09-28 18:33:21.000 3
101.110.32.80 6 2016-08-28 18:33:21.000 2
101.110.32.80 6 2016-05-30 00:30:33.000 1
I have a Table with certain records, grouped by Ipaddress and QID.. My requirement is to find out which record missed the sequence in the date column or other words the date difference is more than 30 days. In the above table date diff between rank 1 and rank 2 is more than 30 days.So, i should flag the rank 2 record.
You can use LAG in Sql 2012+
declare #Tbl Table (Ip VARCHAR(50), QID INT, ScanDate DATETIME,[Rank] INT)
INSERT INTO #Tbl
VALUES
('101.110.32.80', 6, '2016-09-28 18:33:21.000', 3),
('101.110.32.80', 6, '2016-08-28 18:33:21.000', 2),
('101.110.32.80', 6, '2016-05-30 00:30:33.000', 1)
;WITH Result
AS
(
SELECT
T.Ip ,
T.QID ,
T.ScanDate ,
T.[Rank],
LAG(T.[Rank]) OVER (ORDER BY T.[Rank]) PrivSRank,
LAG(T.ScanDate) OVER (ORDER BY T.[Rank]) PrivScanDate
FROM
#Tbl T
)
SELECT
R.Ip ,
R.QID ,
R.ScanDate ,
R.Rank ,
R.PrivScanDate,
IIF(DATEDIFF(DAY, R.PrivScanDate, R.ScanDate) > 30, 'This is greater than 30 day. Rank ' + CAST(R.PrivSRank AS VARCHAR(10)), '') CFlag
FROM
Result R
Result:
Ip QID ScanDate Rank CFlag
------------------------ ----------- ----------------------- ----------- --------------------------------------------
101.110.32.80 6 2016-05-30 00:30:33.000 1
101.110.32.80 6 2016-08-28 18:33:21.000 2 This is greater than 30 day. Rank 1
101.110.32.80 6 2016-09-28 18:33:21.000 3 This is greater than 30 day. Rank 2
While Window Functions could be used here, I think a self join might be more straight forward and easier to understand:
SELECT
t1.IP,
t1.QID,
t1.Rank,
t1.ScanDate as endScanDate,
t2.ScanDate as beginScanDate,
datediff(day, t2.scandate, t1.scandate) as scanDateDays
FROM
table as t1
INNER JOIN table as t2 ON
t1.ip = t2.ip
t1.rank - 1 = t2.rank --get the record from t2 and is one less in rank
WHERE datediff(day, t2.scandate, t1.scandate) > 30 --only records greater than 30 days
It's pretty self-explanatory. We are joining the table to itself and joining the ranks together where rank 2 gets joined to rank 1, rank 3 gets joined to rank 2, and so on. Then we just test for records that are greater than 30 days using the datediff function.
I would use windowed function to avoid self join which in many case will perform better.
WITH cte
AS (
SELECT
t.IP
, t.QID
, LAG(t.ScanDate) OVER (PARTITION BY t.IP ORDER BY T.ScanDate) AS beginScanDate
, t.ScanDate AS endScanDate
, DATEDIFF(DAY,
LAG(t.ScanDate) OVER (PARTITION BY t.IP ORDER BY t.ScanDate),
t.ScanDate) AS Diff
FROM
MyTable AS t
)
SELECT
*
FROM
cte c
WHERE
Diff > 30;

How can I calculate time duration between two rows of a column in SQL Server?

I have a data like this in the database
ID Server DownTime ServerStatus
--- ----------------------- ------------
1 2012-03-30 00:00:00.000 1
2 2012-03-30 00:30:00.000 0
3 2012-03-30 01:00:00.000 0
4 2012-03-30 01:30:00.000 0
5 2012-03-30 02:00:00.000 1
6 2012-03-30 02:30:00.000 1
7 2012-03-30 03:00:00.000 0
8 2012-03-30 03:30:00.000 1
I need a query or stored procedure that will give me output as
Start Time EndTime TotalDownTimeinMinutes
------------ ------------ ----------------------
3/30/12 0:30 3/30/12 2:00 90
3/30/12 3:00 3/30/12 3:30 30
-- because each "back up" can relate to multiple "down" times,
-- we take the longest period using MIN
SELECT Min(ServerDownTime) StartTime,
UpTime EndTime,
DateDiff(MI, Min(ServerDownTime), UpTime)
FROM
(
SELECT Down.ServerDownTime,
(-- subquery gives you the time when it came back up
SELECT Top 1 Up.ServerDownTime
FROM Tbl Up
WHERE Up.ServerDownTime > Down.ServerDownTime
AND Up.ServerStatus=1
ORDER BY Up.ServerDownTime ASC) UpTime
FROM Tbl Down
WHERE Down.ServerStatus=0 -- find all the downs
) X
GROUP BY UpTime
ORDER BY UpTime
You can test the above query using this DDL
create table Tbl
(
ID int,
ServerDownTime datetime,
ServerStatus bit
)
insert Tbl select
1 ,'2012-03-30 00:00:00.000', 1 union all select
2 ,'2012-03-30 00:30:00.000', 0 union all select
3 ,'2012-03-30 01:00:00.000', 0 union all select
4 ,'2012-03-30 01:30:00.000', 0 union all select
5 ,'2012-03-30 02:00:00.000', 1 union all select
6 ,'2012-03-30 02:30:00.000', 1 union all select
7 ,'2012-03-30 03:00:00.000', 0 union all select
8 ,'2012-03-30 03:30:00.000', 1
Or if you're on the web and nowhere near a SQL Server, here's an SQL Fiddle
This solution is based on recursive CTE's:
DECLARE #MyTable TABLE (
ID INT PRIMARY KEY,
ServerDownTime DATETIME NOT NULL,
UNIQUE (ServerDownTime),
ServerStatus BIT NOT NULL
);
INSERT #MyTable (ID, ServerDownTime, ServerStatus)
SELECT 1,'2012-03-30T00:00:00',1 UNION ALL
SELECT 2,'2012-03-30T00:30:00',0 UNION ALL
SELECT 3,'2012-03-30T01:00:00',0 UNION ALL
SELECT 4,'2012-03-30T01:30:00',0 UNION ALL
SELECT 5,'2012-03-30T02:00:00',1 UNION ALL
SELECT 6,'2012-03-30T02:30:00',1 UNION ALL
SELECT 7,'2012-03-30T03:00:00',0 UNION ALL
SELECT 8,'2012-03-30T03:30:00',1;
WITH Base
AS
(
SELECT *, ROW_NUMBER() OVER(ORDER BY t.ServerDownTime) AS RowNum
FROM #MyTable t
), DownTimeGrouping
AS
(
SELECT crt.RowNum,
crt.ID,
crt.ServerDownTime,
crt.ServerStatus,
CASE WHEN crt.ServerStatus=0 THEN 1 END AS GroupID,
CASE WHEN crt.ServerStatus=0 THEN 1 ELSE 0 END AS LastGroupID
FROM Base crt
WHERE crt.RowNum=1
UNION ALL
SELECT crt.RowNum,
crt.ID,
crt.ServerDownTime,
crt.ServerStatus,
CASE
WHEN prev.ServerStatus=0 AND crt.ServerStatus IN(0,1) THEN prev.GroupID
WHEN prev.ServerStatus=1 AND crt.ServerStatus=0 THEN prev.LastGroupID+1
END AS GroupID,
CASE
WHEN prev.ServerStatus=0 AND crt.ServerStatus IN(0,1) THEN prev.GroupID
WHEN prev.ServerStatus=1 AND crt.ServerStatus=0 THEN prev.LastGroupID+1
WHEN prev.ServerStatus=1 AND crt.ServerStatus=1 THEN prev.GroupID
END AS LastGroupID
FROM Base crt
INNER JOIN DownTimeGrouping prev ON crt.RowNum=prev.RowNum+1
)
SELECT *, DATEDIFF(MINUTE,x.StartTime,x.EndTime) AS MinutesDiff
FROM (
SELECT t.GroupID, MIN(t.ServerDownTime) AS StartTime, MAX(t.ServerDownTime) AS EndTime
FROM DownTimeGrouping t
WHERE t.GroupID IS NOT NULL
GROUP BY t.GroupID
) x
The basic idea is to group the rows starting with a ServerStatus=0 row and ending with a ServerStatus=1 row. For example, if you run this query you will see the downtime groups (column GroupID)::
WITH Base
AS
(...), DownTimeGrouping
AS
(...)
SELECT *
FROM DownTimeGrouping g
ORDER BY g.RowNum
RowNum ID ServerDownTime ServerStatus GroupID LastGroupID
-------------------- ----------- ----------------------- ------------ ----------- -----------
1 1 2012-03-30 00:00:00.000 1 NULL 0
2 2 2012-03-30 00:30:00.000 0 1 1
3 3 2012-03-30 01:00:00.000 0 1 1
4 4 2012-03-30 01:30:00.000 0 1 1
5 5 2012-03-30 02:00:00.000 1 1 1
6 6 2012-03-30 02:30:00.000 1 NULL 1
7 7 2012-03-30 03:00:00.000 0 2 2
8 8 2012-03-30 03:30:00.000 1 2 2

Resources