SQL # of days between different date ranges - sql

How can I count the number of days between a start and end date in SQL?
ID | START | END
1 |2018-1-1 |2018-1-3
2 |2018-1-1 |2018-1-4
3 |2018-1-1 |2018-1-5
Ideally would return:
DATE | COUNT
2018-1-1 | 3
2018-1-2 | 3
2018-1-3 | 3
2018-1-4 | 2
2018-1-5 | 1

One option is to generate all dates between min start and max end with a recursive cte and then count them.
with dates(dt1,dt2) as (select min([start]),max([end])
from tbl
union all
select dateadd(day,1,dt1),dt2
from dates
where dt1 < dt2
)
select d.dt1,count(*)
from dates d
join tbl t on d.dt1 between t.[start] and t.[end]
group by d.dt1

Similar approach worked out with a complete example:
DECLARE #range TABLE
(
id INT NOT NULL IDENTITY(1,1),
s_date DATETIME NOT NULL,
e_date DATETIME NOT NULL
);
INSERT INTO #range
(s_date, e_date)
VALUES
('2018-1-1','2018-1-3'),
('2018-1-1','2018-1-4'),
('2018-1-1','2018-1-5');
DECLARE #date TABLE
(
date DATETIME NOT NULL
);
INSERT INTO #date
(date)
VALUES
('2018-1-1'), ('2018-1-1'), ('2018-1-1'),
('2018-1-2'), ('2018-1-2'), ('2018-1-2'),
('2018-1-3'), ('2018-1-3'), ('2018-1-3'),
('2018-1-4'), ('2018-1-4'),
('2018-1-5');
SELECT d.date, COUNT(DISTINCT r.id)
FROM #range r
JOIN #date d ON d.date BETWEEN r.s_date AND r.e_date
GROUP BY d.date

I post a solution to fill gaps with a simple generator:
Check it at SQL Fiddle
MS SQL Server 2017 Schema Setup:
create table d
( ID int, fSTART date, fEND date );
insert into d values
(1, '2018-1-1' ,'2018-1-3'),
(2, '2018-1-1' ,'2018-1-4'),
(3, '2018-1-1' ,'2018-1-5');
Query 1:
;WITH Nbrs_3( n ) AS ( SELECT 1 UNION SELECT 0 ),
Nbrs_2( n ) AS ( SELECT 1 FROM Nbrs_3 n1 CROSS JOIN Nbrs_3 n2 ),
Nbrs_1( n ) AS ( SELECT 1 FROM Nbrs_2 n1 CROSS JOIN Nbrs_2 n2 ),
Nbrs_0( n ) AS ( SELECT 1 FROM Nbrs_1 n1 CROSS JOIN Nbrs_1 n2 ),
Nbrs ( n ) AS ( SELECT 1 FROM Nbrs_0 n1 CROSS JOIN Nbrs_0 n2 ),
ns (n) as (SELECT ROW_NUMBER() OVER (ORDER BY n) FROM Nbrs )
select distinct dateadd( day, n-1,fSTART )
from d inner join ns on dateadd( day, n-1, fSTART ) between fStart and fend
order by 1
Results:
| |
|------------|
| 2018-01-01 |
| 2018-01-02 |
| 2018-01-03 |
| 2018-01-04 |
| 2018-01-05 |

I used cross apply to get your results:
I am assuming you want to know how many times that date is between a start and end date of the other table.
Setup:
declare #s table
( ID int, fSTART date, fEND date );
insert into #s values
(1, '2018-1-1' ,'2018-1-3'),
(2, '2018-1-1' ,'2018-1-4'),
(3, '2018-1-1' ,'2018-1-5');
declare #d table
(dte date)
insert into #d
values
('1/1/2018')
,('1/2/2018')
,('1/3/2018')
,('1/4/2018')
,('1/5/2018')
The Query:
select d.dte
,ct = sum(case when d.dte between s.fstart and s.fend then 1 else 0 end)
from #d d
cross apply #s s
group by dte
results:
dte ct
2018-01-01 3
2018-01-02 3
2018-01-03 3
2018-01-04 2
2018-01-05 1

Related

SQL query to return aggregations in a constant format, even when the categories are missing

Is it possible with SQL Server to return table with a constant format?
Let's say we have the following raw data:
DATE | CATEGORY | VALUE
---------------------------------
01.01.2022 | Category 1 | 10
01.01.2022 | Category 1 | 20
01.01.2022 | Category 1 | 33
01.01.2022 | Category 3 | 15
03.01.2022 | Category 1 | 10
03.01.2022 | Category 2 | 20
03.01.2022 | Category 3 | 50
(...)
And the desired output would be:
DATE | CATEGORY | VALUE
---------------------------------
01.01.2022 | Category 1 | 63
01.01.2022 | Category 2 | 0
01.01.2022 | Category 3 | 15
02.01.2022 | Category 1 | 0
02.01.2022 | Category 2 | 0
02.01.2022 | Category 3 | 0
03.01.2022 | Category 1 | 10
03.01.2022 | Category 2 | 20
03.01.2022 | Category 3 | 50
(...)
Please notice that in the desired outcome there's a date present that's missing in the raw data, as well as sum of VALUE are 0 when the category is not present for a given date in the raw data.
-- Contiguous dates table
DECLARE #dates TABLE(dt date) ;
DECLARE #dateFrom date;
DECLARE #dateTo date;
select #dateFrom = (Select DateAdd(day, -1, Min(date)) from Agg);
select #dateTo = (Select Max(date) from Agg);
-- Query:
WHILE(#dateFrom < #dateTo)
BEGIN
SELECT #dateFrom = DATEADD(day, 1,#dateFrom)
INSERT INTO #dates
SELECT #dateFrom
END
-- Category table
DECLARE #categories TABLE(category nvarchar(20)) ;
insert into #categories values ('Category 1'),('Category 2'),('Category 3');
-- This cte helps in creating the constant output required
with cte1 as (
select dt, category from
#dates cross join #categories
)
select cte1.dt as [Date], cte1.category, Sum(coalesce(yourTableName.value,0)) as Value
from cte1 left join yourTableName
on cte1.dt = yourTableName.[Date] and cte1.category = yourTableName.category
group by cte1.dt, cte1.category
order by cte1.dt, cte1.category
WITH ctedate AS
(
SELECT d= v2.d * 10 + v1.d
FROM (VALUES (0), (1), (2), (3), (4), (5), (6), (7), (8), (9)) v1(d)
CROSS JOIN (VALUES (0), (1), (2), (3), (4), (5), (6), (7), (8), (9)) v2(d)
)
Select b.date, a.category, sum(coalesce(a.value,0)) from
yourtablename a
cross join
(SELECT DATEADD(DAY, ctedate.d, '2022-01-01') date
FROM ctedate
ORDER BY ctedate.d) b
group by b.date, a.category
Here is a script with solution using cte:
Calculate the min date and the max date
then make a calendar (cte1) with all dates between (min date) and (max date) using recursivity
get the list of all category => cte2
make a cross join between cte1 and cte2 ==> cte3
make a left join between cte3 and the table data (#mytable), replace Null values by 0
declare #mytable as table (date date,category varchar(50),Value int)
insert into #mytable values
('01/01/2022','Category 1',10),
('01/01/2022','Category 1',20),
('01/01/2022','Category 1',33),
('01/01/2022','Category 3',15),
('01/03/2022','Category 1',10),
('01/03/2022','Category 2',20),
('01/03/2022','Category 3',50);
declare #mindate as date, #maxdate as date
select #mindate=min(date),#maxdate=max(date) from #mytable;
with
cte1 as (select #mindate mydate union all select dateadd(day,1,mydate) from cte1 where dateadd(day,1,mydate) <= #maxdate),
cte2 as (select distinct(category) from #mytable),
cte3 as (select mydate,category from cte1 cross join cte2),
cte4 as (select mydate date ,cte3.category,isnull(value,0) value from cte3 left outer join #mytable t on cte3.mydate=t.date and cte3.category=t.category )
select * from cte4

SQL: Repeat patterns between date range

DECLARE
#startDate date = '2020-07-03'
#endDate date = 2020-07-06'
I have a tabe as below
---------------------------------------------------------
|EmployeeID | EmpName |Pattern | Frequency |
---------------------------------------------------------
| 11 | X | 1,2,3 | 1 |
| 12 | Y | 4,5 | 1 |
| 13 | Y | 1,2 | 3 |
| 14 | Z | 1,2 | 2 |
---------------------------------------------------------
AND I want to generate dates between given date range.
WANT result table as bellows:
--------------------------------
| EmpId | Dates | Pattern |
--------------------------------
| 11 |2020-07-03 | 1 |
| 11 |2020-07-04 | 2 |
| 11 |2020-07-05 | 3 |
| 11 |2020-07-06 | 1 |
| 12 |2020-07-03 | 4 |
| 12 |2020-07-04 | 5 |
| 12 |2020-07-05 | 4 |
| 12 |2020-07-06 | 5 |
| 13 |2020-07-03 | 1 |
| 13 |2020-07-04 | 1 |
| 13 |2020-07-05 | 1 |
| 13 |2020-07-06 | 2 |
| 14 |2020-07-03 | 1 |
| 14 |2020-07-04 | 1 |
| 14 |2020-07-05 | 2 |
| 14 |2020-07-06 | 2 |
Generate the dates as per given date range for each employee and repeat the pattern for each employee as per their pattern and frequency(days).
means as per frequency(days) pattern will change.
What I have acheived :
Able to generate the records for each employees between the given date range.
What I am not able to get:
I am not able to repeat the pattern based on the frequency for each employee between the date range.
I am able achieve everything but need little help while repeating the pattern based on frequency.*
Note:
Data are storing in this way only.. now I won't change existing schema...
I've came up with this. It's basically a splitter, a tally table and some logic.
Joining (Frequency)-Amount of Tally-datasets with the splitted pattern for the correct amount of pattern-values. Sorting them by their position in the pattern-string.
Join everything together and repeat the pattern by using modulo.
DECLARE #t TABLE( EmployeeID INT
, EmpName VARCHAR(20)
, Pattern VARCHAR(255)
, Frequency INT )
DECLARE #startDate DATE = '2020-07-03'
DECLARE #endDate DATE = '2020-07-09'
INSERT INTO #t
VALUES (11, 'X', '1,2,3', 1),
(12, 'Y', '4,5', 1),
(13, 'Y', '1,2', 3),
(14, 'Z', '1,2', 2)
DECLARE #delimiter CHAR(1) = ',';
WITH split(Txt
, i
, elem
, EmployeeID)
AS (SELECT STUFF(Pattern, 1, CHARINDEX(#delimiter, Pattern+#delimiter+'~'), '')
, 1
, CAST(LEFT(Pattern, CHARINDEX(#delimiter, Pattern+#delimiter+'~')-1) AS VARCHAR(MAX))
, EmployeeID
FROM #t
UNION ALL
SELECT STUFF(Txt, 1, CHARINDEX(#delimiter, Txt+#delimiter+'~'), '')
, i + 1
, CAST(LEFT(Txt, CHARINDEX(#delimiter, Txt+#delimiter+'~')-1) AS VARCHAR(MAX))
, EmployeeID
FROM split
WHERE Txt > ''),
E1(N) AS (SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL
SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL
SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL
SELECT 1), --10E+1 or 10 rows
E2(N) AS (SELECT 1 FROM E1 AS a, E1 AS b), --10E+2 or 100 rows
E4(N) AS (SELECT 1 FROM E2 AS a, E2 AS b), --10E+4 or 10,000 rows
E8(N) AS (SELECT 1 FROM E4 AS a , E4 AS b), --10E+8 or 100,000,000 rows
PatternXFrequency(EmployeeID
, Sort
, elem)
AS (SELECT split.EmployeeID
, ROW_NUMBER() OVER(PARTITION BY split.EmployeeID ORDER BY i) - 1
, elem
FROM split
INNER JOIN #t AS t ON t.EmployeeID = split.EmployeeID
CROSS APPLY (SELECT TOP (t.Frequency) 1
FROM E8
) AS Freq(Dummy))
SELECT EmployeeID
, DATEADD(DAY, i_count, #startDate) AS Dates
, elem
FROM (SELECT DATEDIFF(DAY, #startDate, #endDate) + 1) AS t_datediff(t_days)
CROSS APPLY (SELECT TOP (t_days) ROW_NUMBER() OVER(ORDER BY (SELECT 0) ) - 1 FROM E8
) AS t_dateadd(i_count)
CROSS APPLY (SELECT PatternXFrequency.*
FROM (SELECT DISTINCT EmployeeID FROM #t) AS t(EmpID)
CROSS APPLY (SELECT COUNT(Sort)
FROM PatternXFrequency
WHERE EmployeeID = EmpID
) AS EmpPattern(sortCount)
CROSS APPLY (SELECT *
FROM PatternXFrequency
WHERE EmployeeID = EmpID
AND Sort = ((i_count % sortCount))
) AS PatternXFrequency
) AS t
ORDER BY t.EmployeeID
, Dates
This isn't particularly pretty, but it avoids the recursion of a rCTE, so should provide a faster experience. As STRING_SPLIT still doesn't know what ordinal position means, we have to use something else here; I use DelimitedSplit8k_LEAD.
I also assume your expected results are wrong, as they stop short of your end date (20200709). This results in the below:
CREATE TABLE dbo.YourTable (EmployeeID int,
EmpName char(1),
Pattern varchar(8000), --This NEEDS fixing
Frequency tinyint);
INSERT INTO dbo.YourTable
VALUES(11,'X','1,2,3',1),
(12,'Y','4,5',1),
(13,'Y','1,2',3),
(14,'Z','1,2',2);
GO
DECLARE #StartDate date = '20200703',
#EndDate date = '20200709';
WITH CTE AS(
SELECT *,
MAX(ItemNumber) OVER (PARTITION BY EmployeeID) AS MaxItemNumber
FROM dbo.YourTable YT
CROSS APPLY dbo.DelimitedSplit8K_LEAD(YT.Pattern,',') DS),
N AS(
SELECT N
FROM (VALUES(NULL),(NULL),(NULL),(NULL),(NULL),(NULL),(NULL),(NULL),(NULL),(NULL))N(N)),
Tally AS(
SELECT TOP (SELECT DATEDIFF(DAY,#startDate, #EndDate)+1)
ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) - 1 AS I
FROM N N1, N N2, N N3) --1000 Rows
SELECT C.EmployeeID,
DATEADD(DAY,T.I, #StartDate),
C.Item
FROM CTE C
JOIN Tally T ON ISNULL(NULLIF((T.I +1) % C.MaxItemNumber,0),C.MaxItemNumber) = C.ItemNumber
ORDER BY EmployeeID,
T.I;
GO
DROP TABLE dbo.YourTable;
Like mentioned in the comments fix your data model.
Your output pattern is a little bit strange.
But is it something like this you are looking for?
DECLARE #startDate date = '2020-07-03'
DECLARE #endDate date = '2020-07-09'
DECLARE #Dates TABLE([Date] Date)
;WITH seq(n) AS
(
SELECT 0 UNION ALL SELECT n + 1 FROM seq
WHERE n < DATEDIFF(DAY, #StartDate, #endDate)
)
INSERT INTO #Dates ([Date])
SELECT DATEADD(Day,n, cast(GetDate() as date)) Date
FROM seq
ORDER BY n
OPTION (MAXRECURSION 0);
SELECT e.EmployeeId, d.Date, x.Value Pattern
FROM Employee e
CROSS APPLY STRING_SPLIT(e.Pattern, ',') x
INNER JOIN #Dates d on 1=1
-- Correct for the first iteration of the pattern
AND DATEDIFF(DAY, DATEADD(DAY, -1, #StartDate), d.Date) = x.Value

How to repeat values in a table in SQL Server?

I have a table in Microsoft SQL Server that logged some values on data change triggers. Now, in order to display some graphs, I would like to get (or repeat) a value per 10 minutes from each column(for example).
I would try to avoid, if possible, an INSERT command modifying the table itself.
Original table:
Time Stamp---- | A | B | C |
---------------+---+---+---+
01-01-19 10:20 | 1 | 0 | 0 |
01-01-19 15:30 | 0 | 0 | 1 |
01-01-19 22:50 | 0 | 1 | 0 |
02-01-19 01:40 | 1 | 0 | 0 |
...
Result I would like to achieve:
Time Stamp---- | A | B | C |
---------------+---+---+---+
01-01-19 10:20 | 1 | 0 | 0 |
01-01-19 10:30 | 1 | 0 | 0 |
01-01-19 10:40 | 1 | 0 | 0 |
01-01-19 10:50 | 1 | 0 | 0 |
...
01-01-19 15:30 | 0 | 0 | 1 |
01-01-19 15:40 | 0 | 0 | 1 |
01-01-19 15:50 | 0 | 0 | 1 |
01-01-19 16:00 | 0 | 0 | 1 |
...
Assuming your dates are mm-dd-yy and times are hh:mm...
create table #Original (
[Time Stamp----] datetime2,
A int,
B int,
C int
)
insert #Original
values ({ts '2019-01-01 10:20:00.000'}, 1, 0, 0)
, ({ts '2019-01-01 15:30:00.000'}, 0, 0, 1)
, ({ts '2019-01-01 22:50:00.000'}, 0, 1, 0)
, ({ts '2019-01-02 01:40:00.000'}, 1, 0, 0)
;
with
boundaries as (
select min(o.[Time Stamp----]) as s
, dateadd(minute, 10, max(o.[Time Stamp----])) as e
from #Original o
),
timeslist as (
select 1 as i
, (select s from boundaries) as s
, (select s from boundaries) as d
union all
select t.i + 1
, t.s
, dateadd(minute, 10, d)
from timeslist t
where d < (select e from boundaries)
),
result as (
select
right('0' + cast(MONTH(t.d) as varchar(2)), 2) + '-' +
right('0' + cast(DAY(t.d) as varchar(2)), 2) + '-' +
right('0' + cast(year(t.d) % 100 as varchar(2)), 2) + ' ' +
right('0' + cast(datepart(hour, t.d) as varchar(2)), 2) + ':' +
right('0' + cast(datepart(minute, t.d) as varchar(2)), 2) as 'Time Stamp----'
, o2.A
, o2.B
, o2.C
from timeslist t
inner join (
select o.[Time Stamp----]
, o.A
, o.B
, o.C
, lead (o.[Time Stamp----], 1, dateadd(minute, 10, o.[Time Stamp----])) over (order by o.[Time Stamp----]) as OldTs
from #Original o
) o2 on o2.[Time Stamp----] <= t.d and o2.OldTs > t.d
)
select *
from result
order by [Time Stamp----]
drop table #Original
To select records with manufactured duplicates, try
SELECT Dateadd(mi, DQ.T,TimeStamp) as 'TimeStamp', A, B, C From YourTable
CROSS JOIN (Select 0 T UNION ALL
Select 10 T UNION ALL
Select 20 T UNION ALL
Select 30 T) DQ
or to insert duplicates, try
INSERT YourTable
SELECT Dateadd(mi, DQ.T,TimeStamp) as 'TimeStamp', A, B, C From YourTable
CROSS JOIN (
Select 10 T UNION ALL
Select 20 T UNION ALL
Select 30 T) DQ
Personally I recommend maling a "Time Table", but i do this on the fly here using a Tally. Anyway, I think this is what you're after?
USE Sandbox;
GO
CREATE TABLE dbo.YourTable ([timestamp] datetime2(0), --This is a bad name for a column, as timestamp means soemthing else in SQL Server
A bit,
B bit,
C bit);
INSERT INTO dbo.YourTable ([timestamp],
A,
B,
C)
VALUES ('2019-01-01T10:20:00',1,0,0),
('2019-01-01T15:30:00',0,0,1),
('2019-01-01T22:50:00',0,1,0),
('2019-01-02T01:40:00',1,0,0);
GO
WITH N AS
(SELECT N
FROM (VALUES(NULL),(NULL),(NULL),(NULL),(NULL),(NULL))N(N)),
Tally AS(
SELECT TOP(144) ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) -1 AS I
FROM N N1, N N2, N N3),
Times AS(
SELECT DATEADD(MINUTE,T.I * 10,CONVERT(time(0),'00:00:00')) AS TimeSlot
FROM Tally T),
DateTimes AS(
SELECT DISTINCT
CONVERT(datetime,CONVERT(date,YT.[timestamp])) + CONVERT(datetime,T.TimeSlot) AS DateTimeSlot
FROM dbo.YourTable YT
CROSS JOIN Times T),
Groups AS(
SELECT DT.DateTimeSlot,
CONVERT(tinyint,YT.A) AS A, --Can't aggregate Bits
CONVERT(tinyint,YT.B) AS B,
CONVERT(tinyint,YT.C) AS C,
COUNT(YT.A) OVER (ORDER BY DT.DateTimeSlot ASC ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) AS Grp
FROM DateTimes DT
LEFT JOIN dbo.YourTable YT ON DT.DateTimeSlot = YT.[timestamp])
SELECT G.DateTimeSlot,
MAX(G.A) OVER (PARTITION BY G.Grp) AS A,
MAX(G.B) OVER (PARTITION BY G.Grp) AS B,
MAX(G.C) OVER (PARTITION BY G.Grp) AS C
FROM Groups G
ORDER BY G.DateTimeSlot;
GO
DROP TABLE dbo.YourTable;
You can use SQL RECURSION and CROSS JOIN
SQL FIDDLE
Demo
declare #mytable as table(timestamp datetime,A int,B int,C int)
insert into #mytable values
('01-01-19 10:20',1,0,0),('01-01-19 15:30',0,0,1),
('01-01-19 22:50',0,1,0),('01-01-19 01:40',1,0,0)
;with cte as(
select 0 n
union all
select n+10 from cte where n+10 <40)
select dateadd(mi,n,timestamp)[TIMESTAMP],t1.A,t1.B,T1.C
from #mytable t1 cross join cte
order by timestamp

SQL get consecutive days ignoring weekend

I have a table with following format:
ID ID1 ID2 DATE
1 1 1 2018-03-01
2 1 1 2018-03-02
3 1 1 2018-03-05
4 1 1 2018-03-06
5 1 1 2018-03-07
6 2 2 2018-03-05
7 2 2 2018-03-05
8 2 2 2018-03-06
9 2 2 2018-03-07
10 2 2 2018-03-08
From this table I have to get all records where ID1 and ID2 are the same in that column and where DATE is 5 consecutive work days (5 dates in a row, ignoring missing dates for Saturday/Sunday; ignore holidays).
I have really no idea how to achieve this. I did search around, but couldn't find anything that helped me. So my question is, how can I achieve following output?
ID ID1 ID2 DATE
1 1 1 2018-03-01
2 1 1 2018-03-02
3 1 1 2018-03-05
4 1 1 2018-03-06
5 1 1 2018-03-07
SQLFiddle to mess around
Assuming you have no duplicates and work is only on weekdays, then there is a simplish solution for this particular case. We can identify the date 4 rows ahead. For a complete week, it is either 4 days ahead or 6 days ahead:
select t.*
from (select t.*, lead(dat, 4) over (order by id2, dat) as dat_4
from t
) t
where datediff(day, dat, dat_4) in (4, 6);
This happens to work because you are looking for a complete week.
Here is the SQL Fiddle.
select t.* from
(select id1,id2,count(distinct dat) count from t
group by id1,id2
having count(distinct dat)=5) t1 right join
t
on t.id1=t1.id1 and t.id2=t1.id2
where count=5
Check this-
Dates of Two weeks with 10 valid dates
http://sqlfiddle.com/#!18/76556/1
Dates of Two weeks with 10 non-unique dates
http://sqlfiddle.com/#!18/b4299/1
and
Dates of Two weeks with less than 10 but unique
http://sqlfiddle.com/#!18/f16cb/1
This query is very verbose without LEAD or LAG and it is the best I could do on my lunch break. You can probably improve on it given the time.
DECLARE #T TABLE
(
ID INT,
ID1 INT,
ID2 INT,
TheDate DATETIME
)
INSERT #T SELECT 1,1,1,'03/01/2018'
INSERT #T SELECT 2,1,1,'03/02/2018'
INSERT #T SELECT 3,1,1,'03/05/2018'
INSERT #T SELECT 4,1,1,'03/06/2018'
INSERT #T SELECT 5,1,1,'03/07/2018'
--INSERT #T SELECT 5,1,1,'03/09/2018'
INSERT #T SELECT 6,2,2,'03/02/2018'
INSERT #T SELECT 7,2,2,'03/05/2018'
INSERT #T SELECT 8,2,2,'03/05/2018'
--INSERT #T SELECT 9,2,2,'03/06/2018'
INSERT #T SELECT 10,2,2,'03/07/2018'
INSERT #T SELECT 11,2,2,'03/08/2018'
INSERT #T SELECT 12,2,2,'03/15/2018'
INSERT #T SELECT 13,1,1,'04/01/2018'
INSERT #T SELECT 14,1,1,'04/02/2018'
INSERT #T SELECT 15,1,1,'04/05/2018'
--SELECT * FROM #T
DECLARE #LowDate DATETIME = DATEADD(DAY,-1,(SELECT MIN(TheDate) FROM #T))
DECLARE #HighDate DATETIME = DATEADD(DAY,1,(SELECT MAX(TheDate) FROM #T))
DECLARE #DaysThreshold INT = 5
;
WITH Dates AS
(
SELECT DateValue=#LowDate
UNION ALL
SELECT DateValue + 1 FROM Dates
WHERE DateValue + 1 < #HighDate
),
Joined AS
(
SELECT * FROM Dates LEFT OUTER JOIN #T T ON T.TheDate=Dates.DateValue
),
Calculations AS
(
SELECT
ID=MAX(J1.ID),
J1.ID1,J1.ID2,
J1.TheDate,
LastDate=MAX(J2.TheDate),
LastDateWasWeekend = CASE WHEN ((DATEPART(DW,DATEADD(DAY,-1,J1.TheDate) ) + ##DATEFIRST) % 7) NOT IN (0, 1) THEN 0 ELSE 1 END,
Offset = DATEDIFF(DAY,MAX(J2.TheDate),J1.TheDate)
FROM
Joined J1
LEFT OUTER JOIN Joined J2 ON J2.ID1=J1.ID1 AND J2.ID2=J1.ID2 AND J2.TheDate<J1.TheDate
WHERE
NOT J1.ID IS NULL
GROUP BY J1.ID1,J1.ID2,J1.TheDate
)
,FindValid AS
(
SELECT
ID,ID1,ID2,TheDate,
IsValid=CASE
WHEN LastDate=TheDate THEN 0
WHEN LastDate IS NULL THEN 1
WHEN Offset=1 THEN 1
WHEN Offset>3 THEN 0
WHEN Offset<=3 THEN
LastDateWasWeekend
END
FROM
Calculations
UNION
SELECT DISTINCT ID=NULL,ID1,ID2, TheDate=#HighDate,IsValid=0 FROM #T
),
FindMax As
(
SELECT
This.ID,This.ID1,This.ID2,This.TheDate,MaxRange=MIN(Next.TheDate)
FROM
FindValid This
LEFT OUTER JOIN FindValid Next ON Next.ID2=This.ID2 AND Next.ID1=This.ID1 AND This.TheDate<Next.TheDate AND Next.IsValid=0
GROUP BY
This.ID,This.ID1,This.ID2,This.TheDate
),
FindMin AS
(
SELECT
This.ID,This.ID1,This.ID2,This.TheDate,This.MaxRange,MinRange=MIN(Next.TheDate)
FROM
FindMax This
LEFT OUTER JOIN FindMax Next ON Next.ID2=This.ID2 AND Next.ID1=This.ID1 AND This.TheDate<Next.MaxRange-- AND Next.IsValid=0 OR Next.TheDate IS NULL
GROUP BY
This.ID,This.ID1,This.ID2,This.TheDate,This.MaxRange
)
,Final AS
(
SELECT
ID1,ID2,MinRange,MaxRange,SequentialCount=COUNT(*)
FROM
FindMin
GROUP BY
ID1,ID2,MinRange,MaxRange
)
SELECT
T.ID,
T.ID1,
T.ID2,
T.TheDate
FROM #T T
INNER JOIN Final ON T.TheDate>= Final.MinRange AND T.TheDate < Final.MaxRange AND T.ID1=Final.ID1 AND T.ID2=Final.ID2
WHERE
SequentialCount>=#DaysThreshold
OPTION (MAXRECURSION 0)

Update table for each date and add remaining sites from another table

I have a table 'test' like this-
ID Site Start Time End Time
1 A 30-12-2014 16:06:54 30-12-2014 16:39:52
2 B 30-12-2014 12:12:50 30-12-2014 12:13:52
3 C 31-12-2014 12:14:23 31-12-2014 12:15:22
4 A 01-01-2015 12:20:29 01-01-2015 12:23:32
5 B 01-01-2015 12:28:49 01-01-2015 12:29:47
I have another table 'list' with a listing of sites-
Site
A
B
C
I need an output table where for each date, all the sites from 'list' is included like this-
ID Site Start Time End Time
1 A 30-12-2014 16:06:54 30-12-2014 16:39:52
2 B 30-12-2014 12:12:50 30-12-2014 12:13:52
NULL C 30-12-2014 00:00:00 30-12-2014 00:00:00
NULL A 31-12-2014 00:00:00 31-12-2014 00:00:00
NULL B 31-12-2014 00:00:00 31-12-2014 00:00:00
3 C 31-12-2014 12:14:23 31-12-2014 12:15:22
4 A 01-01-2015 12:20:29 01-01-2015 12:23:32
5 B 01-01-2015 12:28:49 01-01-2015 12:29:47
NULL C 01-01-2015 00:00:00 01-01-2015 00:00:00
Till now I have been table to separate the 'test' table on each date into intermediate tables and select the non matching sites from 'list' table. I am stuck with the loop. Please help.
Here is my code-
ALTER TABLE [test] ADD [DATE] date;
update [test]
set [DATE] = CAST(Start Time] as Date)
select t1.[Site]
from list t1
left join test t2 on t1.[site]=t2.[site] where t2.site is null;
select distinct [DATE] into #Temp1 from [test]
order by [DATE];
select [DATE], row_number()over(order by ([Date])asc) as [Row] into #Temp2 from #Temp1;
drop table #Temp1;
GO
declare #row int
select #row = 0
while ( #row <= (select COUNT(*) from #Temp2))
begin
select #row = 1 + #row
select c.* into #temp3
from(
select a.* , b.[DATE] as b_date, b.[row]
from test a
inner join #Temp2 b
on a.[Date] = b.[Date] where b.[row] = #row
) c
End;
You can get the output you want using a select:
select t.id, l.site, coalesce(t.starttime, d.d) as starttime, coalesce(t.endtime, d.d) as endtime
from list l cross join
(select distinct cast(starttime as date) as d from test) d left join
test t
on t.site = l.site and cast(t.starttime as date) = d.d;
You can insert non-matching rows into the table with similar logic:
insert into test(id, site, starttime, endtime)
select t.id, l.site, d.d, d.d
from list l cross join
(select distinct cast(starttime as date) as d from test) d left join
test t
on t.site = l.site and cast(t.starttime as date) = d.d
where t.site is null;
Try this,
Declare #t table(ID int, Site varchar(50),StartTime datetime,EndTime datetime)
insert into #t values
(1, 'A', '12-30-2014 16:06:54','12-30-2014 16:39:52'),
(2 , 'B', '12-30-2014 12:12:50','12-30-2014 12:13:52'),
(3 , 'C', '12-31-2014 12:14:23','12-31-2014 12:15:22'),
(4 , 'A', '01-01-2015 12:20:29','01-01-2015 12:23:32'),
(5, 'B', '01-01-2015 12:28:49','01-01-2015 12:29:47')
dECLARE #lIST TABLE(Site varchar(50))
insert into #lIST values('A'),('B'),('C')
;WITH CTE AS
(
SELECT min(cast(StartTime as date)) st FROM #t
union all
SELECT dateadd(day,1, st) FROM CTE where
st<casT('01-01-2015 12:28:49' as date)--max date(can be dynamic)
)
,CTE1 as
(
select * from #lIST a
cross apply (select * from cte)b
)
,CTE2 as
(
select y.ID,x.Site
,ISNULL(y.StartTime,x.st)StartTime,ISNULL(y.EndTime,x.st)EndTime
from CTE1 x
left join #t y on x.site=y.site and
cast(x.st as date)=cast(y.StartTime as date)
)
SELECT * FROM CTE2