How count of numbers id for a period of time - sql

I am looking for a way to count the number of id in a period of time every 30 minutes.
I wrote a SQL query, but the result is incorrect
CREATE TABLE [dbo].[#tabl]
(
[Id] [varchar](100) NULL,
[TIMEStart] [datetime] NULL,
[TIMEEnd] [datetime] NULL
) ON [PRIMARY]
INSERT INTO [dbo].[#tabl] VALUES ('1', '2020-04-01 00:05:00.000', '2020-04-01 00:10:00.000')
INSERT INTO [dbo].[#tabl] VALUES ('2', '2020-04-01 00:11:00.000', '2020-04-01 00:29:00.000')
INSERT INTO [dbo].[#tabl] VALUES ('3', '2020-04-01 00:12:00.000', '2020-04-01 00:55:00.000')
WITH CTE AS
(
SELECT
[Id],
DATEADD(MINUTE, (DATEDIFF(MINUTE, [TIMEStart], [TIMEEnd]) / 30) * 30, 0) AS RangeTime
FROM
[dbo].[#tabl]
GROUP BY
[Id], DATEADD(MINUTE, (DATEDIFF(MINUTE, [TIMEStart],[TIMEEnd]) / 30) * 30, 0)
)
SELECT numreq, RangeTime
FROM
(SELECT COUNT(DISTINCT id) AS numreq, RangeTime
FROM CTE
GROUP BY RangeTime) temp
Correct result - table:
numreq RangeTime
-------------------------------------
3 1900-01-01 00:00:00.000
1 1900-01-01 00:30:00.000
Period of time:
1900-01-01 00:00:00.000 - includes 3 id:1, 2, 3
1900-01-01 00:30:00.000 - includes 1 id:1

I think what you need is to create a list of RangeTime values that lie within the range of the TIMEStart and TIMEEnd values in tabl (which you can do with a recursive CTE), then you can JOIN that list back to tabl on an overlapping time range and count the number of rows that overlap each RangeTime:
WITH CTE AS (
SELECT DATEADD(MINUTE,(DATEDIFF(MINUTE, 0, MIN([TIMEStart]))/30)*30,0) as RangeTime,
MAX([TIMEEnd]) AS MaxTime
FROM [dbo].[tabl]
UNION ALL
SELECT DATEADD(MINUTE, 30, RangeTime), MaxTime
FROM CTE
WHERE DATEADD(MINUTE, 30, RangeTime) < MaxTime
)
SELECT RangeTime, COUNT(tabl.Id) AS numreq
FROM CTE
LEFT JOIN tabl ON tabl.TIMEStart < DATEADD(MINUTE, 30, RangeTime)
AND RangeTime <= tabl.TIMEEnd
GROUP BY RangeTime
Output:
RangeTime numreq
2020-04-01T00:00:00Z 3
2020-04-01T00:30:00Z 1
Demo on SQLFiddle
Note: I've presumed you want the actual time for the range, not times starting at the beginning of 1900...

Related

Reading 0.5 value in ROW_NUMBER() and PARTITION BY | SQL Server 2012

I have this question before Looping between 2 case parameters (Date Range and Row Count) | SQL Server 2012 , now I'm thinking about a scenario, what if the value has a 0.5? or .5? Will this work using ROW_NUMBER()?
I'm trying to make this work using only CASE method.
This is my old script:
DECLARE #dbApple TABLE
(
FromDate varchar(30) NOT NULL,
ToDate varchar(30) NOT NULL,
Name varchar(30) NOT NULL,
Count float(30) NOT NULL
)
INSERT INTO #dbApple (FromDate, ToDate, Name, Count)
VALUES ('2019-10-05', '2019-10-09', 'APPLE', '2.5');
(SELECT
CONVERT(date, CONVERT(date, DATEADD(D, VAL.NUMBER, FromDate))) AS Date,
DB.Name,
CASE
WHEN CONVERT(date, CONVERT(date, DATEADD(D, VAL.NUMBER, FromDate))) BETWEEN CONVERT(date, CONVERT(date, DATEADD(D, VAL.NUMBER, FromDate))) AND CONVERT(date, CONVERT(date, DATEADD(D, VAL.NUMBER, ToDate)))
THEN
CASE
WHEN ROW_NUMBER() OVER (PARTITION BY Count, FromDate, ToDate ORDER BY Count) <= Count
THEN (COUNT / COUNT)
END
END AS Count
FROM
#dbApple DB
JOIN
MASTER..SPT_VALUES VAL ON VAL.TYPE = 'P'
AND VAL.NUMBER BETWEEN 0 AND DATEDIFF(D, FromDate, ToDate))
This is the output:
This is my expected output:
Is there a way for this to work? Thank you.
You can greatly simplify your query by noting that VAL.NUMBER is already your row number (just starting at 0 instead of 1). You can then compare your Count value to VAL.NUMBER and if Count - VAL.NUMBER is greater than 1, output 1; if it's greater than 0 output the difference, otherwise output NULL. For this demo query I've simulated your numbers table with a table value constructor:
declare #dbApple TABLE(
FromDate varchar(30) NOT NULL,
ToDate varchar(30) NOT NULL,
Name varchar(30) NOT NULL,
Count float(30) NOT NULL
)
INSERT INTO #dbApple
(FromDate,ToDate,Name,Count) VALUES ('2019-10-05','2019-10-09','APPLE',2.5);
SELECT
CONVERT(date,CONVERT(date,DATEADD(D,VAL.NUMBER,FromDate))) AS Date,
Name,
CASE WHEN Count - VAL.NUMBER > 1 THEN 1
WHEN Count - VAL.NUMBER > 0 THEN Count - VAL.NUMBER
END AS Count
FROM
#dbApple D
JOIN (VALUES (0), (1), (2), (3), (4), (5)) VAL(NUMBER)
ON VAL.NUMBER BETWEEN 0 AND DATEDIFF(D, FromDate, ToDate)
Output:
Date Name Count
2019-10-05 APPLE 1
2019-10-06 APPLE 1
2019-10-07 APPLE 0.5
2019-10-08 APPLE (null)
2019-10-09 APPLE (null)
Demo on dbfiddle

Table with inconsistent starttime and stoptime

I have a table that contains a date, a starttime, and a stoptime. I have several problems that I don't know how to solve for. Each row contains either a starttime OR a stoptime (not both). While this itself is not a problem, I need to calculate the runtime by date. Also, there are instances of multiple starttimes before there is a stoptime registered. Assume the following:
Date, Starttime, Stoptime
4/1/2016, 23:00:00, NULL
4/2/2016, NULL, 03:00:00
4/2/2016, 05:00:00, NULL
4/2/2016, 07:00:00, NULL
4/2/2016, NULL, 08:00:00
4/2/2016, 10:00:00, NULL
4/2/2016, NULL, 10:15:00
I need the output to be:
4/1/2016, 01:00:00
4/2/2016, 06:15:00
I have tried a few things, with very poor results. Can any experts out there solve this problem?
Here's one way how you could handle this:
Setup:
CREATE TABLE #Table1
([Date] date, [Starttime] time, [Stoptime] time)
;
INSERT INTO #Table1
([Date], [Starttime], [Stoptime])
VALUES
('2016-04-01', '23:00:00', NULL),
('2016-04-02', NULL, '00:00:00'),
('2016-04-02', '00:00:00', NULL),
('2016-04-02', NULL, '03:00:00'),
('2016-04-02', '05:00:00', NULL),
('2016-04-02', '07:00:00', NULL),
('2016-04-02', NULL, '08:00:00'),
('2016-04-02', '10:00:00', NULL),
('2016-04-02', NULL, '10:15:00')
;
SQL:
select
convert(date, StartTime),
convert(time, dateadd(second, sum(datediff(second, StartTime, EndTime)),0))
from (
select
min(Time) as StartTime,
min(case when Type = 0 then Time end) as EndTime
from
(
select
sum(Type) over (order by Time asc, Type Asc) as GRP, *
from (
select
isnull(lag(Type) over (order by Time asc, Type Asc), -1) as LagType, *
from (
select
case when Starttime is NULL then 0 else 1 end as Type,
case when Starttime is NULL
then convert(datetime, [Date]) + convert(datetime, Stoptime)
else convert(datetime, [Date]) + convert(datetime, Starttime) end as Time
from
#Table1
) A
) B
where Type != 1 or LagType != 1
) C
group by GRP
) D
group by convert(date, StartTime)
Result:
2016-04-01 01:00:00
2016-04-02 06:15:00
This requires that you have start and stop record in your data for each of the days, so you'll need to use union or something like that to add that to your initial data.
The innermost select generates typing (0/1) for the rows and calculates the date + start / end time into a single column. The next one adds TypeLag to the data and it's used to remove the duplicate start records. The next select has a running total over Type, so that start and end records belonging together will have a unique group number. The rest will just pick the start time + earliest end time from each group and calculate the durations.

Clone rows based on the column

I have data like shown below:
ID Duration Start Date End Date
------------------------------------------------------
10 2 2013-09-03 05:00:00 2013-09-03 05:02:00
I need output like below:
10 2 2013-09-03 05:00:00 2013-09-03 05:01:00 1
10 2 2013-09-03 05:01:00 2013-09-03 05:02:00 2
Based on the column Duration, if the value is 2, I need rows to be duplicated twice.
And if we see at the Output for Start Date and End Date time should be changed accordingly.
And Row count as an additional column for number rows duplicated in this case 1 / 2 shown above will help a lot.
And if duration is 0 and 1 then do nothing , only when duration > 1 then duplicate rows.
And at last Additional column for number row Sequence 1 , 2 ,3 for showing how many rows was duplicated.
try the sql below, I added some comments where I thought it was seemed necessery.
declare #table table(Id integer not null, Duration int not null, StartDate datetime, EndDate datetime)
insert into #table values (10,2, '2013-09-03 05:00:00', '2013-09-03 05:02:00')
insert into #table values (11,3, '2013-09-04 05:00:00', '2013-09-04 05:03:00')
;WITH
numbers AS (
--this is the number series generator
--(limited to 1000, you can change that to whatever you need
-- max possible duration in your case).
SELECT 1 AS num
UNION ALL
SELECT num+1 FROM numbers WHERE num+1<=100
)
SELECT t.Id
, t.Duration
, StartDate = DATEADD(MINUTE, IsNull(Num,1) - 1, t.StartDate)
, EndDate = DATEADD(MINUTE, IsNull(Num,1), t.StartDate)
, N.num
FROM #table t
LEFT JOIN numbers N
ON t.Duration >= N.Num
-- join it with numbers generator for Duration times
ORDER BY t.Id
, N.Num
This works better when Duration = 0:
declare #table table(Id integer not null, Duration int not null, StartDate datetime, EndDate datetime)
insert into #table values (10,2, '2013-09-03 05:00:00', '2013-09-03 05:02:00')
insert into #table values (11,3, '2013-09-04 05:00:00', '2013-09-04 05:03:00')
insert into #table values (12,0, '2013-09-04 05:00:00', '2013-09-04 05:03:00')
insert into #table values (13,1, '2013-09-04 05:00:00', '2013-09-04 05:03:00')
;WITH
numbers AS (
--this is the number series generator
--(limited to 1000, you can change that to whatever you need
-- max possible duration in your case).
SELECT 1 AS num
UNION ALL
SELECT num+1 FROM numbers WHERE num+1<=100
)
SELECT
Id
, Duration
, StartDate
, EndDate
, num
FROM
(SELECT
t.Id
, t.Duration
, StartDate = DATEADD(MINUTE, Num - 1, t.StartDate)
, EndDate = DATEADD(MINUTE, Num, t.StartDate)
, N.num
FROM #table t
INNER JOIN numbers N
ON t.Duration >= N.Num ) A
-- join it with numbers generator for Duration times
UNION
(SELECT
t.Id
, t.Duration
, StartDate-- = DATEADD(MINUTE, Num - 1, t.StartDate)
, EndDate --= DATEADD(MINUTE, Num, t.StartDate)
, 1 AS num
FROM #table t
WHERE Duration = 0)
ORDER BY Id,Num

SQL JOIN WITH A BUFFER TIME

I have tWO tables and have to check whether the time in a table lies in between range of other table.
ProgStartTime gives the start time of the range and ProgEndTime gives end of the range which lies in MBA table.
Am checking whether the AdvTime in MAP table exists in between the ProgStartTime and ProgEndTime from MBA table.
For time range less than one hour i have to give a buffer of + or - 5 mins.
i.e if ProgStarttTime is 18:00 & progEndTime is 19:00 & AdvTime is 17:55/19:05 it should match the range.
Whereas if ProgStarttTime is 18:00 & progEndTime is 20:00 & AdvTime is 17:55 it shouldnt match.
Sorry for the clumsy content am in bit of a hurry.
I used the below query for joining wihtout buffer time
SELECT DISTINCT mb.Id AS mbaid,
mp.id AS mapid,
mp.Channel AS Channel,
mp.Product,
mp.ProgDate,
mp.AdvTime,
mb.Channel,
mb.ProgStartTime,
mb.ProgEndTime,
convert(time, dateadd(MINUTE, datediff(MINUTE, mb.progStartTime, mb.progEndTime), 0)) AS timeDiff
FROM map22 AS mp
INNER JOIN mba22 AS mb ON ((mp.ProgDate = mp.ProgDate
AND mp.Channel=mb.Channel
AND mp.Product=mb.Product))
WHERE (mp.ProgDate = mb.ProgDate
AND AdvTime >= ProgStartTime
AND (AdvTime <= ProgEndTime
OR ProgEndTime < ProgStartTime))
OR (mp.ProgDate = Dateadd(DAY,1,mb.ProgDate)
AND ProgEndTime < ProgStartTime
AND AdvTime <= ProgEndTime)
ORDER BY mp.Id ASC
Your sample query has a lot going on, so I've created a simplified example.
Set up the data:
create table MBA (MBAID int, ProgStartTime datetime, ProgEndTime datetime)
insert into MBA select 1, '20130318 18:00:00', '20130318 19:00:00'
insert into MBA select 2, '20130318 18:00:00', '20130318 20:00:00'
create table Map (MapID int, AdvTime datetime)
insert into Map select 1, '20130318 17:55:00'
insert into Map select 2, '20130318 18:30:00'
insert into Map select 3, '20130318 19:05:00'
insert into Map select 4, '20130318 20:05:00'
Based on this, we can apply a CASE statement to give AdvTime more loose matching when the difference between the dates is an hour or less:
select *
from MBA
inner join Map on
MBA.ProgStartTime <=
case when datediff(mi, MBA.ProgStartTime, MBA.ProgEndTime) <= 60
then dateadd(mi, 5, Map.AdvTime)
else Map.AdvTime
end
and MBA.ProgEndTime >=
case when datediff(mi, MBA.ProgStartTime, MBA.ProgEndTime) <= 60
then dateadd(mi, -5, Map.AdvTime)
else Map.AdvTime
end
Gives results:
We can see that for MBA 1, which has an hour duration, we are matching AdvTime values slightly before and after, but for MBA 2, only matching those within the time period as required.
SQL Fiddle with demo.
Edit after comment:
Added another example for the values in the comments, with following data:
create table MBA (MBAID int, ProgStartTime datetime, ProgEndTime datetime)
insert into MBA select 1, '20130318 21:00:00', '20130318 22:00:00'
create table Map (MapID int, AdvTime datetime)
insert into Map select 1, '20130318 20:55:00'
insert into Map select 2, '20130318 22:05:00'
The original query matches both of the above rows as expected.
SQL Fiddle with demo.
Edit after comment:
Tested with more data:
create table MBA (MBAID int, ProgStartTime datetime, ProgEndTime datetime)
insert into MBA select 1, '20130318 23:00:00', '20130319 02:00:00'
create table Map (MapID int, AdvTime datetime)
insert into Map select 1, '20130319 00:30:00'
Still matching as expected.
SQL Fiddle with demo.
Final edit after comment?
OK, now we know a bit more about the schema we can make one final query. Set up the data:
create table MBA (MBAID int, ProgStartTime datetime, ProgEndTime datetime)
insert into MBA select 1, '18:00:00', '19:00:00'
insert into MBA select 2, '18:00:00', '20:00:00'
insert into MBA select 3, '21:00:00', '22:00:00'
insert into MBA select 4, '23:30:00', '02:00:00'
insert into MBA select 5, '23:30:00', '00:30:00'
create table Map (MapID int, AdvTime datetime)
insert into Map select 1, '17:55:00'
insert into Map select 2, '18:30:00'
insert into Map select 3, '19:05:00'
insert into Map select 4, '20:05:00'
insert into Map select 5, '20:55:00'
insert into Map select 6, '22:05:00'
insert into Map select 7, '23:25:00'
insert into Map select 8, '23:30:00'
insert into Map select 9, '00:30:00'
insert into Map select 10, '00:35:00'
Use the following query:
select *
from MBA
inner join Map on
(MBA.ProgStartTime < MBA.ProgEndTime
and MBA.ProgStartTime <=
case when datediff(mi, MBA.ProgStartTime, MBA.ProgEndTime) <= 60
then dateadd(mi, 5, Map.AdvTime)
else Map.AdvTime
end
and MBA.ProgEndTime >=
case when datediff(mi, MBA.ProgStartTime, MBA.ProgEndTime) <= 60
then dateadd(mi, -5, Map.AdvTime)
else Map.AdvTime
end) or
(MBA.ProgStartTime > MBA.ProgEndTime
and (MBA.ProgStartTime <=
case when 1440 - datediff(mi, MBA.ProgEndTime, MBA.ProgStartTime) <= 60
then dateadd(mi, 5, Map.AdvTime)
else Map.AdvTime
end
or MBA.ProgEndTime >=
case when 1440 - datediff(mi, MBA.ProgEndTime, MBA.ProgStartTime) <= 60
then dateadd(mi, -5, Map.AdvTime)
else Map.AdvTime
end))
We expect the following rows to be matched:
MBA Matched Maps
1 1,2,3
2 2,3
3 5,6
4 8,9,10
5 7,8,9,10
Results:
SQL Fiddle with demo.

T-SQL - query for records before and after current time in same statement

For you T-SQL gurus:
I have the following table:
ID Arrival
1 06:16:00
2 06:17:00
3 07:19:00
4 08:21:00
5 10:22:00
6 13:21:00
7 20:22:00
Say the time is currently 08:00 AM and I want to select 2 records before and after record with closest time to now. Result should return records with IDs 2,3,4,5 and 6.
Getting the records before and after record with ID=4 is straight forward but so far, I cannot figure out how to return the complete set as part of the same query. I have these two select statements:
SELECT TOP(2) * FROM Schedules
where (datepart(hour, Arrival) - datepart(hour, getdate()))*60 + datepart(minute, Arrival) - datepart(minute, getdate()) < 0
order by (datepart(hour, Arrival) - datepart(hour, getdate()))*60 + datepart(minute, Arrival) - datepart(minute, getdate())
SELECT TOP(2) * FROM Schedules
where (datepart(hour, Arrival) - datepart(hour, getdate()))*60 + datepart(minute, Arrival) - datepart(minute, getdate()) >= 0
order by (datepart(hour, Arrival) - datepart(hour, getdate()))*60 + datepart(minute, Arrival) - datepart(minute, getdate()) asc
which return records before and after. I tried using a union on both statements but this requires dropping the first order by clause which invalidates my query criteria.
Any ideas will help, thanks.
We can use ROW_NUMBER to partition on if the arrival is before or after and order by the absolute value of the difference between the arrival and the input time.
DECLARE #CurrentTime as time
SET #CurrentTime = '08:00 AM'
DECLARE #Schedules table (id int, arrival time)
INSERT INTO #Schedules
VALUES (1 , '06:16:00' ),
(2, '06:17:00' ),
(3, '07:19:00'),
(4, '08:21:00'),
(5, '10:22:00'),
(6, '13:21:00'),
(7, '20:22:00')
DECLARE #closestTime as time
SELECT TOP 1 #closestTime = arrival FROM #Schedules ORDER BY ABS(DATEDIFF(mi, #CurrentTime ,arrival))
;WITH cte
AS (SELECT id,
arrival,
Row_number() OVER (PARTITION BY (CASE WHEN #closestTime > arrival THEN 1
WHEN #closestTime < arrival THEN 2 END)
ORDER BY Abs(Datediff(mi, #closestTime, arrival))) rn
FROM #Schedules)
SELECT *
FROM cte
WHERE rn < 3
OR arrival = #closestTime
ORDER BY id
Results in
id arrival rn
----------- ---------------- --------------------
2 06:17:00.0000000 2
3 07:19:00.0000000 1
4 08:21:00.0000000 1
5 10:22:00.0000000 1
6 13:21:00.0000000 2
See working example at this data.se query
Can you try the following? I have tested, got the result that u want
create table Arriavel (ID int, Arr_time time)
insert into Arriavel values (1, '06:16:00')
insert into Arriavel values (2, '06:17:00')
insert into Arriavel values (3, '07:19:00')
insert into Arriavel values (4, '08:21:00')
insert into Arriavel values (5, '10:22:00')
insert into Arriavel values (6, '13:21:00')
insert into Arriavel values (7, '20:22:00')
declare #cur_time time
set #cur_time = '08:00:00'
select max(arr_time) arr_time
from Arriavel
where arr_time < #cur_time
union all
select min(arr_time) arr_time
from Arriavel
where arr_time > #cur_time