How to get non occuring time gaps in SQL Server? - sql

I have a table with time entries having start and end time. I want to get the time entries which are not there in the table.
Example: I have time entry having start time 08.00 - 09.00 and other for 10.20 - 11.00. I need a record which contains 09.00 - 10.19. As I need to do it for multiple occurrences, can anybody help me out to find this complex query?
i have a time range to show non occuring entries beteen 07.00 to 17.00 then it should return me 7.00 to 8.45 and 14.00 to 17.00

I lack reputation to do a comment (the comments by Mihai and TT were amazing and interesting), but a possible solution may be as simple as
SELECT a.endDT, Min(b.startDT)
FROM sched a, sched b
WHERE a.endDT<b.startDT
GROUP BY a.endDT
that will return for your sample data
2017-07-30 08:45:00.000 2017-07-30 09:30:00.000
2017-07-30 09:45:00.000 2017-07-30 10:30:00.000
2017-07-30 11:45:00.000 2017-07-30 13:15:00.000
2017-07-30 13:45:00.000 2017-07-30 14:00:00.000
However, as the comments of Mihai and TT point out, this will not get the time between say midnight and 8am, the first record.

I cannot tell what your sample data has to do with your description. But the problem seems to be solved by lag(). For the data you have provided:
select activity, prev_endtime as gap_start, starttime as gap_end
from (select t.*,
lag(endtime) over (partition by activity order by id) as prev_endtime
from t
) t
where starttime <> prev_endtime;
I should note that this will not work for all possible combinations of start times and end times. But, your time slots don't appear to overlap and they appear to be ordered by id, so this should work.

Related

Calculating difference (or deltas) between current and previous row with clickhouse

It would be awesome if there was a way to index rows during a query.
Is there a way to SELECT (compute) the difference of a single column between consecutive rows?
Let's say, something like the following query
SELECT
toStartOfDay(stamp) AS day,
count(day ) AS events ,
day[current] - day[previous] AS difference, -- how do I calculate this
day[current] / day[previous] as percent, -- and this
FROM records
GROUP BY day
ORDER BY day
I want to get the integer and percentage difference between the current row's 'events' column and the previous one for something similar to this:
day
events
difference
percent
2022-01-06 00:00:00
197
NULL
NULL
2022-01-07 00:00:00
656
459
3.32
2022-01-08 00:00:00
15
-641
0.02
2022-01-09 00:00:00
7
-8
0.46
2022-01-10 00:00:00
137
130
19.5
My version of Clickhouse doesn't support window-function but, on looking about the LAG() function mentioned in the comments, I found neighbor(), which works perfectly for what I'm trying to do
SELECT
toStartOfDay(stamp) AS day,
count(day ) AS events ,
(events - neighbor(events, -1)) as diff,
(events / neighbor(events, -1)) as perc
FROM records
GROUP BY day
ORDER BY day

Transpose and fill missing dates in date range

First, my main issue, I want to do is to check how many users that had an active product on a given date.
My data looks like this:
UserID ActiveFrom ActiveTo
1 2019-02-03 2019-03-05
2 2019-04-01 2019-04-30
1 2019-03-06 2019-04-04
3 2019-05-01 2019-05-31
I think the solution could to select all the ActiveFrom and union with ActiveFrom, and then fill in the missing dates so that it looked something like this:
UserID ActiveOnDate
1 2019-02-03
1 2019-02-04
1 2019-02-05
And so on
Then I could count all the UserID for each date. But i can’t find a query that fills out the missing dates in the date range. And I also don’t know if this is the “easiest” solution. Any ideas?
If your Dates have the Date datatype (and not VARCHAR for exemple), you can use the BETWEEN sql operator
https://sql.sh/cours/where/between
SELECT count(*) FROM user WHERE [dateToTest] BETWEEN ActiveFrom AND ActiveTo;

How to determine availability of rentals for back to back use using a recursive cte

I'm working on a proc that will determine whether or not an entity has enough rental equipment to cover all of its rentals during a set period of time.
We also have a new feature that allows customers to pickup their equipment while the location is closed.
However, we also allow back to back rentals, meaning a single piece of equipment can have multiple rentals while the entity is closed.
I need a way to determine (Assuming all rentals are picked up and returned on time) whether or not there is enough equipment to cover all reservations.
I attempted to use a recursive CTE to determine this, but I am not able to push my max recursion limit any further, and as some rentals are less than an hour, I must measure the dates in a maximum of 30 minute blocks.
Though, it is also my first time using a recursive cte so I may have made a mistake.
My example is as follows:
ID PickupTime DropOffTime
--- ------------------------------------- ------------------------------------
1 2019-2-28 23:00:00.000 2019-3-01 00:00:00.000
2 2019-3-01 00:00:00.000 2019-3-01 01:00:00.000
3 2019-3-01 04:00:00.000 2019-3-01 07:00:00.000
4 2019-2-28 22:00:00.000 2019-2-28 23:00:00.000
5 2019-2-2819:00:00.000 2019-2-28 21:00:00.000
6 2019-2-28 20:00:00.000 2019-2-28 22:00:00.000
7 2019-2-28 23:00:00.000 2019-3-01 01:00:00.000
8 2019-2-28 23:00:00.000 2019-3-01 01:00:00.000
9 2019-3-01 00:00:00.000 2019-3-01 02:00:00.000
10 2019-2-28 21:00:00.000 2019-2-28 22:00:00.000
11 2019-2-28 22:00:00.000 2019-2-28 23:00:00.000
And Equipment
ID EquipmentNumber
-- ---------------
1 AB123
2 AC321
3 BL854
Due to figuring this out on paper, I know that a minimum of 4 equipment is needed to cover these. So this should return a boolean stating yes or no (In this case a no) if all reservations are able to be covered.
This is what I tried:
DECLARE #MinprefDate DATETIME = (SELECT MIN(PreferredPickupDate) FROM #Results)
DECLARE #MaxprefDate DATETIME = (SELECT MAX(PreferredPickupDate) FROM #Results)
DECLARE #MinexpDate DATETIME = (SELECT MIN(ExpectedReceiveDate) FROM #Results)
DECLARE #MaxexpDate DATETIME = (SELECT MAX(ExpectedReceiveDate) FROM #Results)
;WITH CTE AS (
SELECT 1 AS n, r.ID, r.ContractID, #MinprefDate AS PreferredPickupDate, #MinexpDate AS ExpectedReceiveDate, r.PreviousPickup, r.PreviousExpected, r.NextPickup, r.NextExpected
FROM #Results r
UNION ALL
SELECT n + 1, ID, ContractID, DATEADD(MINUTE, 1, #MinprefDate), DATEADD(MINUTE, 1, #MaxexpDate), PreviousPickup, PreviousExpected, NextPickup, NextExpected FROM CTE
WHERE ExpectedReceiveDate <= #MaxexpDate
)
SELECT * FROM CTE OPTION (MaxRecursion 3000)
If I assume that your times are wrapping around, so 01:00:00 really means 25:00:00, then I rather understand your problem.
I'm not quite sure how to handle ties, but the following returns what you want:
select top (1) t.id, v.time, sum(inc) over (order by time, id) as overlaps
from t cross apply
(values (pickuptime, 1), (dropofftime, -1)) v(time, inc)
order by overlaps desc;
Here is a db<>fiddle.
There is no need to use recursive CTEs. You simply need to account for each pickup and dropoff, which you can do with a cumulative sum.

Count unique entries based on previous groups

Trying to find unique values in each group, however with a look back at the previously grouped items. It will be group by time, so if previous time block had the unique value it should not appear in the next time block. Lookback should span all previous time blocks. So at time 2, it looks at time 0 and 1, while at time 10 it looks back at time 0 to 9.
I am also looking to do this dynamically, without manually offsetting each time block with a subquery, as time here is continuous and not discrete data set.
Sample data:
2018-03-25 00:00:00.000, 123
2018-03-25 00:00:00.000, 231
2018-03-26 00:00:00.000, 234
2018-03-26 00:00:00.000, 123
2018-03-27 00:00:00.000, 123
2018-03-27 00:00:00.000, 231
2018-03-27 00:00:00.000, 234
2018-03-27 00:00:00.000, 432
Sample output:
2018-03-25 00:00:00.000, 2
2018-03-26 00:00:00.000, 1
2018-03-27 00:00:00.000, 1
If I got you right, you can consider that if the value exists in any past group, it should be excluded from the results set.
I think this kind of approach should help you:
select groupped.t, count(*) from
(select distinct base.t, base.v from foo as base where v not in
(
select u.v from foo as u where u.t < base.t
)
) as groupped group by groupped.t;
Heres also a fiddle. Hope this helps. http://sqlfiddle.com/#!18/4a65e/1

GROUP BY several hours

I have a table where our product records its activity log. The product starts working at 23:00 every day and usually works one or two hours. This means that once a batch started at 23:00, it finishes about 1:00am next day.
Now, I need to take statistics on how many posts are registered per batch but cannot figure out a script that would allow me achiving this. So far I have following SQL code:
SELECT COUNT(*), DATEPART(DAY,registrationtime),DATEPART(HOUR,registrationtime)
FROM RegistrationMessageLogEntry
WHERE registrationtime > '2014-09-01 20:00'
GROUP BY DATEPART(DAY, registrationtime), DATEPART(HOUR,registrationtime)
ORDER BY DATEPART(DAY, registrationtime), DATEPART(HOUR,registrationtime)
which results in following
count day hour
....
1189 9 23
8611 10 0
2754 10 23
6462 11 0
1885 11 23
I.e. I want the number for 9th 23:00 grouped with the number for 10th 00:00, 10th 23:00 with 11th 00:00 and so on. How could I do it?
You can do it very easily. Use DATEADD to add an hour to the original registrationtime. If you do so, all the registrationtimes will be moved to the same day, and you can simply group by the day part.
You could also do it in a more complicated way using CASE WHEN, but it's overkill on the view of this easy solution.
I had to do something similar a few days ago. I had fixed timespans for work shifts to group by where one of them could start on one day at 10pm and end the next morning at 6am.
What I did was:
Define a "shift date", which was simply the day with zero timestamp when the shift started for every entry in the table. I was able to do so by checking whether the timestamp of the entry was between 0am and 6am. In that case I took only the date of this DATEADD(dd, -1, entryDate), which returned the previous day for all entries between 0am and 6am.
I also added an ID for the shift. 0 for the first one (6am to 2pm), 1 for the second one (2pm to 10pm) and 3 for the last one (10pm to 6am).
I was then able to group over the shift date and shift IDs.
Example:
Consider the following source entries:
Timestamp SomeData
=============================
2014-09-01 06:01:00 5
2014-09-01 14:01:00 6
2014-09-02 02:00:00 7
Step one extended the table as follows:
Timestamp SomeData ShiftDay
====================================================
2014-09-01 06:01:00 5 2014-09-01 00:00:00
2014-09-01 14:01:00 6 2014-09-01 00:00:00
2014-09-02 02:00:00 7 2014-09-01 00:00:00
Step two extended the table as follows:
Timestamp SomeData ShiftDay ShiftID
==============================================================
2014-09-01 06:01:00 5 2014-09-01 00:00:00 0
2014-09-01 14:01:00 6 2014-09-01 00:00:00 1
2014-09-02 02:00:00 7 2014-09-01 00:00:00 2
If you add one hour to registrationtime, you will be able to group by the date part:
GROUP BY
CAST(DATEADD(HOUR, 1, registrationtime) AS date)
If the starting hour must be reflected accurately in the output (as 9, 23, 10, 23 rather than as 10, 0, 11, 0), you could obtain it as MIN(registrationtime) in the SELECT clause:
SELECT
count = COUNT(*),
day = DATEPART(DAY, MIN(registrationtime)),
hour = DATEPART(HOUR, MIN(registrationtime))
Finally, in case you are not aware, you can reference columns by their aliases in ORDER BY:
ORDER BY
day,
hour
just so that you do not have to repeat the expressions.
The below query will give you what you are expecting..
;WITH CTE AS
(
SELECT COUNT(*) Count, DATEPART(DAY,registrationtime) Day,DATEPART(HOUR,registrationtime) Hour,
RANK() over (partition by DATEPART(HOUR,registrationtime) order by DATEPART(DAY,registrationtime),DATEPART(HOUR,registrationtime)) Batch_ID
FROM RegistrationMessageLogEntry
WHERE registrationtime > '2014-09-01 20:00'
GROUP BY DATEPART(DAY, registrationtime), DATEPART(HOUR,registrationtime)
)
SELECT SUM(COUNT) Count,Batch_ID
FROM CTE
GROUP BY Batch_ID
ORDER BY Batch_ID
You can write a CASE statement as below
CASE WHEN DATEPART(HOUR,registrationtime) = 23
THEN DATEPART(DAY,registrationtime)+1
END,
CASE WHEN DATEPART(HOUR,registrationtime) = 23
THEN 0
END