SQL Counting Total Time but resetting total if large gap - sql

I have a table containing device movements.
MoveID DeviceID Start End
I want to find out if there is a way to sum up the total movement days for each device to the present. However if there is a gap 6 weeks bewtween an end date and the next start date then the time count is reset.
MoveID DeviceID Start End
1 1 2011-1-1 2011-2-1
2 1 2011-9-1 2011-9-20
3 1 2011-9-25 2011-9-28
The total for device should be 24 days as because there is a gap of greater than 6 weeks. Also I'd like to find out the number of days since the first movement in the group in this case 28 days as the latest count group started on the 2011-9-1
I thought I could do it with a stored proc and a cursor etc (which is not good) just wondered if there was anything better?
Thanks
Graeme

create table #test
(
MoveID int,
DeviceID int,
Start date,
End_time date
)
--drop table #test
insert into #test values
(1,1,'2011-1-1','2011-2-1'),
(2,1,'2011-9-1','2011-9-20'),
(3,1,'2011-9-25','2011-9-28')
select
a.DeviceID,
sum(case when datediff(dd, a.End_time, isnull(b.Start, a.end_time)) > 42 /*6 weeks = 42 days*/ then 0 else datediff(dd,a.Start, a.End_time)+1 /*we will count also the last day*/ end) as movement_days,
sum(case when datediff(dd, a.End_time, isnull(b.Start, a.end_time)) > 42 /6 weeks = 42 days/ then 0 else datediff(dd,a.Start, a.End_time)+1 /we will count also the last day/ end + case when b.MoveID is null then datediff(dd, a.Start, a.End_time) + 1 else 0 end) as total_days
from
#test a
left join #test b
on a.DeviceID = b.DeviceID
and a.MoveID + 1 = b.MoveID
group by
a.DeviceID
Let me know if you need some explanation - there can be more ways to do that...

DECLARE #Times TABLE
(
MoveID INT,
DeviceID INT,
Start DATETIME,
[End] DATETIME
)
INSERT INTO #Times VALUES (1, 1, '1/1/2011', '2/1/2011')
INSERT INTO #Times VALUES (2, 1, '9/1/2011', '9/20/2011')
INSERT INTO #Times VALUES (3, 1, '9/25/2011', '9/28/2011')
INSERT INTO #Times VALUES (4, 2, '1/1/2011', '2/1/2011')
INSERT INTO #Times VALUES (5, 2, '3/1/2011', '4/20/2011')
INSERT INTO #Times VALUES (6, 2, '5/1/2011', '6/20/2011')
DECLARE #MaxGapInWeeks INT
SET #MaxGapInWeeks = 6
SELECT
validTimes.DeviceID,
SUM(DATEDIFF(DAY, validTimes.Start, validTimes.[End]) + 1) AS TotalDays,
DATEDIFF(DAY, MIN(validTimes.Start), MAX(validTimes.[End])) + 1 AS TotalDaysInGroup
FROM
#Times validTimes LEFT JOIN
#Times timeGap
ON timeGap.DeviceID = validTimes.DeviceID
AND timeGap.MoveID <> validTimes.MoveID
AND DATEDIFF(WEEK, validTimes.[End], timeGap.Start) > #MaxGapInWeeks
WHERE timeGap.MoveID IS NULL
GROUP BY validTimes.DeviceID

Related

How to get one output table from select in while-loop

Edited to live up to the rules here. Sorry about my first attempt.
I got the following sample data:
CREATE TABLE SampleData(
[Time] [time](7) NOT NULL
) ON [PRIMARY]
GO
INSERT INTO SampleData([Time]) VALUES ('01:00:00')
INSERT INTO SampleData([Time]) VALUES ('02:00:00')
INSERT INTO SampleData([Time]) VALUES ('02:00:00')
INSERT INTO SampleData([Time]) VALUES ('03:00:00')
INSERT INTO SampleData([Time]) VALUES ('03:00:00')
INSERT INTO SampleData([Time]) VALUES ('03:00:00')
INSERT INTO SampleData([Time]) VALUES ('04:00:00')
INSERT INTO SampleData([Time]) VALUES ('04:00:00')
INSERT INTO SampleData([Time]) VALUES ('04:00:00')
INSERT INTO SampleData([Time]) VALUES ('04:00:00')
GO
This is my query:
DECLARE #Counter INT
SET #Counter = 1
WHILE (#Counter <= 4)
BEGIN
SELECT Count([Time]) AS OrdersAmount
FROM SampleData
WHERE DATEPART(HOUR, [Time]) = #Counter
SET #Counter = #Counter + 1
END
This is the result of the query:
OrdersAmount
1
-----
OrdersAmount
2
-----
OrdersAmount
3
-----
OrdersAmount
4
So 4 seperate tables. What I need is one table, with alle values in it, on each their own row, like this:
OrdersAmount
1
2
3
4
I tried with cte and declaring a temp table, but I just can't make it work.
I don't have the data so can't test.
But if I get your problem right, this should work for you.
select PromisedPickupDt = cast(PromisedPickupDate as date),
[Hour] = datepart(hour, PromisedPickupDate),
HourlyAmount = sum(OrdersAmount)
from [FLX].[dbo].[NDA_SAP_Bestillinger]
where cast(PromisedPickupDate as date) = cast(getdate() as date)
group by cast(PromisedPickupDate as date), datepart(hour, PromisedPickupDate)
You need an Hours table to join and group against. You can create this using a VALUES constructor:
SELECT
Count(*) AS OrdersAmount
FROM (VALUES
(0),(1),(2),(3),(4),(5),(6),(7),(8),(9),(10),(11),(12),(13),(14),(15),(16),(17),(18),(19),(20),(21),(22),(23)
) AS v(Hour)
LEFT JOIN [FLX].[dbo].[NDA_SAP_Bestillinger]
ON v.Hour = DATEPART(hour, PromisedPickupTime)
AND PromisedPickupDate >= CAST(CAST(GETDATE() AS date) AS datetime)
AND PromisedPickupDate < CAST(DATEADD(day, 1, CAST(GETDATE() AS date)) AS datetime)
GROUP BY v.Hour;
What is going on here is that we start with a filter by the current date.
Then we join a constructed Hours table against the hour-part of the date. Because it is on the left-side of a LEFT JOIN, we now have all the hours whether or not there is any value in that hour. We can now group by it.
Note the correct method to compare to a date range: a half-open interval >= AND <
Do not use YEAR\MONTH etc in join and filter predicates, as indexes cannot be used for this.

SQL: How to group in 30 second intervals and mark the max time in that grouping?

I have the following table:
CREATE TABLE #times
(
num int,
atime datetime
)
INSERT #times VALUES (1, '8/27/2015 1:10:00');
INSERT #times VALUES (1, '8/27/2015 1:10:15');
INSERT #times VALUES (1, '8/27/2015 1:10:28' );
INSERT #times VALUES (2, '7/3/2018 2:20:50' );
INSERT #times VALUES (2, '7/3/2018 2:21:05' );
INSERT #times VALUES (2, '7/3/2018 2:21:10' );
INSERT #times VALUES (2, '7/3/2018 2:30:55' );
INSERT #times VALUES (3, '1/1/2018 10:20:25');
INSERT #times VALUES (4, '1/1/2018 10:20:05');
INSERT #times VALUES (5, '9/15/2015 2:20:55');
I would like to group by num and atime within a 30 second interval, then mark the max time with a 0 and the other times in the grouping with a 1.
So the result dataset would be this:
1 '8/27/2015 1:10:00' 1
1 '8/27/2015 1:10:15' 1
1 '8/27/2015 1:10:28' 0 <<this is the max time of the grouping within num and 30 secs
2 '7/3/2018 2:20:50' 1
2 '7/3/2018 2:21:05' 1
2 '7/3/2018 2:21:10' 0 <<this is the max time of the grouping within num and 30 secs
2 '7/3/2018 2:30:55' 0
3 '1/1/2018 10:20:25' 0
4 '1/1/2018 10:20:05' 0
5 '9/15/2015 2:20:55' 0
I find the other answers much too complicated. Basically, you want to mark rows where the next time is more than 30 seconds away.
This is easy using lead():
select t.*,
(case when lead(atime) over (partition by num order by atime) < dateadd(second, 30, atime)
then 1
else 0
end) as flag
from #times t;
Here is a SQL Fiddle.
Try this to select the required rows
SELECT DISTINCT num,
MAX(atime) OVER(PARTITION BY num) AS maxAtime
FROM #times
WHERE DATEPART(SECOND, atime) <= 30
Or this to mark the rows
SELECT num, atime, IIF(atime = sub.maxAtime, 1, 0) AS flagged
FROM (
SELECT num,
atime,
MAX(atime) OVER(PARTITION BY num) AS maxAtime
FROM #times
WHERE DATEPART(SECOND, atime) <= 30
) AS sub;
Not sure I've understood 100%, but have a look at this, see if it helps.
WITH cte
AS (SELECT *
, CAST(atime AS DATE) D
, DATEPART(HOUR, atime) H
, DATEPART(MINUTE, atime) M
, CASE WHEN DATEPART(SECOND, atime) < 30 THEN 0
ELSE 30
END AS S
FROM #times
),
cte2
AS (SELECT *
, ROW_NUMBER() OVER (PARTITION BY num, D, H, M, S ORDER BY atime DESC) RN
FROM cte
)
SELECT cte2.num
, cte2.atime
, CASE cte2.RN WHEN 1 THEN 0 ELSE 1 END
FROM cte2
ORDER BY atime
Here you go:
;with stats1(num,mintime) as(
select num,min(atime) mintime
from #times t1
group by num
),
stats2(num,maxtime) as(
select s1.num,max(t.atime) maxtime
from stats1 s1
inner join #times t on s1.num=t.num
where t.atime<=dateadd(second,30,s1.mintime)
group by s1.num
)
select t.num, t.atime, case when t.atime>=s2.maxtime then 0 else 1 end
from stats2 s2
inner join #times t on s2.num=t.num

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

How can I check to see if a data set has a grouping of 3 entries within 5 days

I am looking to see if from a specific date there are a total of 3 entries within a grouping of 5 days within +/- 5 days of the specified date. As long as the second column is false.
Here is an example data set.
Create Table #Data(entryDate date, complete bit)
Insert into #Data Values('2013-02-05', 1)
Insert into #Data Values('2013-02-06', 0)
Insert into #Data Values('2013-02-09', 0)
Insert into #Data Values('2013-02-11', 0)
Insert into #Data Values('2013-02-12', 0)
Insert into #Data Values('2013-02-14', 0)
Given a date of 2013-02-11 I want a true result given that there are 2 scenarios that satisfy my conditions.
2013-02-09, 2013-02-11, 2013-02-12
2013-02-11, 2013-02-12, 2013-02-14
Or given a date of 2013-02-09 I would get a true result but there is only 1 scenario that satisfies the conditions
2013-02-09, 2013-02-11, 2013-02-12
Note that 2013-02-05, 2013-02-06, 2013-02-09 do not satisfy the conditions due to the fact that 2013-02-05 is set to true.
How can I write a sql expression that gives me true or false as described above.
use Outer Apply and then evaluate your result.
declare #data table(entryDate date, complete bit)
Insert into #data Values('2013-02-05', 1)
Insert into #data Values('2013-02-06', 0)
Insert into #data Values('2013-02-09', 0)
Insert into #data Values('2013-02-11', 0)
Insert into #data Values('2013-02-12', 0)
Insert into #data Values('2013-02-14', 0)
select *
from #data d1
outer apply (
SELECT Count(*) AS CountMatches
FROM #data d2
where
ABS(DateDiff(d,d1.entryDate,d2.entryDate)) < 5 AND
complete = 0 AND
d1.entryDate <> d2.entryDate
) t
where
t.CountMatches >=3
I've also come up with the following which ensures that no dates after a completion are counted towards the 3 of 5 groupings
Declare #data table(Id int, entryDate date, complete bit)
Insert into #data Values(1, '2013-02-05', 1)
Insert into #data Values(2, '2013-02-06', 0)
Insert into #data Values(3, '2013-02-09', 0)
Insert into #data Values(4, '2013-02-11', 0)
Insert into #data Values(5, '2013-02-12', 0)
Insert into #data Values(6, '2013-02-14', 0)
Declare #windowMiddle date
Set #windowMiddle = '2013-02-09'
Declare #newestComplete date
select top 1 #newestComplete = entryDate from #data where complete =1 order by entryDate desc
--if(#newestComplete > #windowMiddle) --exit
;with cte as
(
select *
from #data cte
where
cte.entryDate > DATEADD(dd, -5, #windowMiddle)
and cte.entryDate >#newestComplete
and cte.entryDate < DATEADD(dd, 5, #windowMiddle)
)
select cte.entryDate, count(*)
from cte inner join cte d on cte.entryDate between DATEADD(dd, -4, d.entryDate) and d.entryDate
group by cte.entryDate
having (count(*)>=3)
Perhaps someone can comment on whether BlackjacketMack's answer is better than this.

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