Time overlaping in sql code - sql

I have got a table:
---------------------------------------------------
***************************************************
worker | job | starttime | endtime
---------------------------------------------------
w1 | j1 | 2014-01-02 08:00 | 2014-01-02 10:00
---------------------------------------------------
w1 | j2 | 2014-01-02 08:00 | 2014-01-02 11:00
---------------------------------------------------
w1 | j3 | 2014-01-02 09:00 | 2014-01-02 12:00
---------------------------------------------------
w1 | j4 | 2014-01-02 12:00 | 2014-01-02 13:00
---------------------------------------------------
w1 | j1 | 2014-01-02 12:00 | 2014-01-02 13:00
I want the result as
w1's j1 is 2 hours
w1's j2 is 1 hours
w1's j3 is 1 hours its like extra time
w1's j4 is 1 hours
For example, When preparing the cake, coat flour and eggs at the same time but coat eggs takes longer time.
finding total time is easy but how can I separate them ?
Is it possible code it in sql?

Have a look at DATEDIFF
Returns the count (signed integer) of the specified datepart
boundaries crossed between the specified startdate and enddate.
(This is also true for timeparts)
select worker,job, DATEDIFF(HOUR,endtime,starttime) as diff from mytable

I edited my answer to handle the case where the end time is equal to a previous end time.
So here's something like what you want. We take the max end time where
the job is not the current one and
the end time is less than the current row's end time
and compare that the current row's end time to determine how long the current row extends the previous job.
If that response is null, the row is the first row in the job, and in that case we just use the difference between start and end.
If the end time is equal to a previous end time, determined because the row_number is greater than one when partitioned over end time, we output zero as the time since that is the differential between this row and the previous one.
Please note that the "time" data type is limited to 24 hours so it is probably not the appropriate data type for the output. I just used it because it produces nice looking results for the times in the example.
declare #times table (
rowId int identity,
worker varchar(10),
job varchar(10),
starttime datetime,
endtime datetime
)
insert into #times (worker,job,starttime,endtime)
select 'w1','j1','2014-01-02 08:00','2014-01-02 9:25'
union select 'w1','j2','2014-01-02 08:00','2014-01-02 11:00'
union select 'w1','j3','2014-01-02 09:00','2014-01-02 12:00'
union select 'w1','j4','2014-01-02 12:00','2014-01-02 14:22'
union select 'w1','j5','2014-01-02 12:00','2014-01-02 14:22'
select *,
case when
ROW_NUMBER() over (partition by endtime order by starttime,endtime) > 1 then cast('0:00' as time)
else
coalesce(
cast((endtime - (select max(endtime) from #times where endtime < t.endtime)) as time),
cast((endtime - starttime) as time)
)
end as differential3
from #times t

Related

SQL finding total flagged hours within first 8 hours worked

I am aiming to find total hours worked in a day for shifts or fractions of shifts per person that are tagged with a value and fall within the first 8 hours of work, excluding breaks, on any day. Then display tagged shifts along with eligible total value.
Example
2am - 4am (2 hrs) - Normal shift
5am - 9am (4 hrs) - Tagged shift
10am - 3pm (5 hrs) - Tagged shift
Eligible Total 4 hrs (5am - 9am) + 2 hrs (10am - 12pm)
Source Table Format
PersonID | WorkDate | StartTime | FinishTime | HoursWorked (pre calculated) | ShiftType
DECLARE #Table TABLE
(
PersonID INT
, WorkDate DATETIME
, StartTime DATETIME
, FinishTime DATETIME
, HoursWorked DECIMAL(4, 2)
, ShiftType VARCHAR(50)
);
INSERT INTO #Table VALUES (100,'2019-11-26','1900-01-01T02:00:00', '1900-01-01T04:00:00',2,'Normal')
INSERT INTO #Table VALUES (100,'2019-11-26','1900-01-01T05:00:00', '1900-01-01T09:00:00',4,'Tagged')
INSERT INTO #Table VALUES (100,'2019-11-26','1900-01-01T10:00:00', '1900-01-01T15:00:00',5,'Tagged')
Result Set
+----------+------------+---------------------+---------------------+------------------------------+-----------+---------------+
| PersonID | WorkDate | StartTime | FinishTime | HoursWorked (pre calculated) | ShiftType | EligibleHours |
+----------+------------+---------------------+---------------------+------------------------------+-----------+---------------+
| 100 | 2019-11-26 | 1900-01-01T05:00:00 | 1900-01-01T09:00:00 | 4 | Tagged | 4 |
+----------+------------+---------------------+---------------------+------------------------------+-----------+---------------+
| 100 | 2019-11-26 | 1900-01-01T10:00:00 | 1900-01-01T15:00:00 | 5 | Tagged | 2 |
+----------+------------+---------------------+---------------------+------------------------------+-----------+---------------+
Here's my understanding of the requirements:
Collect the first 8 hours of work done by each user on each day
If a shift starts before 8 hours and finishes after 8 hours, it should be marked with the number of hours that occurred before the user got to 8 hours
Filter out all untagged shifts
Filter out all shifts without eligible hours
To solve I used two windowing functions:
sum(TotalHours) over (...) to determine the cumulative sum of how many hours were worked in the current shift and all previous
(8 - lag(CumulativeWork, 1, 0)) over (...) to determine how much eligibility was left entering into the current shift.
Here's the code:
select
PersonID,
WorkDate,
StartTime,
FinishTime,
HoursWorked,
ShiftType,
case
when RemainingWork <= HoursWorked then RemainingWork
when RemainingWork > HoursWorked then HoursWorked
else 0 end as EligibleWork
from
(
select
-- Calculate how much eligible work can happen in a given shift by
-- subtracting the amount of work done in previous shifts from 8
8 - lag (CumulativeWork, 1, 0) over (Partition by PersonID, WorkDate order by StartTime) as RemainingWork
, *
from (
select
-- Create a cumulative sum of the hours worked
sum(HoursWorked) over (Partition by PersonID, WorkDate order by StartTime) as CumulativeWork
, *
from ShiftTable
) a
) b
where shiftType = 'Tagged' and remainingWork > 0
And the fiddle: http://sqlfiddle.com/#!18/7a8dd/12

Summing counts based on overlapping intervals in postgres

I want to sum the column for every two minute interval (so it would be the sum of 1,2 and 2,3 and 3,4, etc...), but I'm not exactly sure how to go about doing that.
My data looks something like:
minute | source | count
2018-01-01 10:00 | a | 7
2018-01-01 10:01 | a | 5
2018-01-01 10:02 | a | 10
2018-01-01 10:00 | b | 20
2018-01-01 10:05 | a | 12
What I want
(e.g. row1+row2, row2+3, row3, row4, row5)
minute | source | count
2018-01-01 10:00 | a | 12
2018-01-01 10:01 | a | 15
2018-01-01 10:02 | a | 10
2018-01-01 10:00 | b | 20
2018-01-01 10:05 | a | 12
You can use a correlated subquery selecting the sum of the counts for the records in the interval sharing the source (I guess that the source must match is an requirement. If not, just remove the comparison in the WHERE clause.).
SELECT "t1"."minute",
"t1"."source",
(SELECT sum("t2"."count")
FROM "elbat" "t2"
WHERE "t2"."source" = "t1"."source"
AND "t2"."minute" >= "t1"."minute"
AND "t2"."minute" <= "t1"."minute" + INTERVAL '1 MINUTE') "count"
FROM "elbat" "t1";
SQL Fiddle
the post above assumes all the timestamps are to the minute. if you want to check for every 2 minutes throughout the day you can use the generate_series function. the issue with including the beginning minute and ending time in each interval will be b having 2 rows in the results.
ie.
select begintime,
endtime,
source,
sum(count)
from mytable
inner join (
select begintime, endtime
from (
select lag(time, 1) over (order by time) as begintime,
time as endtime
from (
select *
from generate_series('2018-01-01 00:00:00', '2018-01-02 00:00:00', interval '2 minutes') time
) q
) q2
where begintime is not null
) times on minute between begintime and endtime
group by begintime, endtime, source
order by begintime, endtime, source
you can change the 'minute between begintime and endtime' to 'minute > begintime and minute <= endtime' if you don't want that overlap

Set end time of first row as start time of next row

I have table with data like:
Id | Start | End | Used
----------------------------------------
1 | 27-04-17 2:00 |27-04-17 0:00 | 1:30
---------------------------------------
2 | 27-04-17 2:00 |27-04-17 0:00 | 23:00
---------------------------------------
3 | 27-04-17 2:00 |27-04-17 0:00 | 1:00
---------------------------------------
4 | 28-04-17 2:00 |28-04-17 0:00 | 0:30
---------------------------------------
5 | 30-04-17 2:00 |30-04-17 0:00 | 3:30
---------------------------------------
I want to set it like
Id | Start | End | Used
----------------------------------------
1 | 27-04-17 2:00 |27-04-17 3:30 | 1:30
---------------------------------------
2 | 27-04-17 3:30 |28-04-17 2:30 | 23:00
---------------------------------------
3 | 28-04-17 2:30 |28-04-17 3:30 | 1:00
---------------------------------------
4 | 28-04-17 3:30 |28-04-17 4:00 | 0:30
---------------------------------------
5 | 30-04-17 2:00 |30-04-17 4:30 | 2:30
---------------------------------------
I want to set End time of previous id as Start time of next id, where Start time of first id is set by user.End time is just sum of Start time and Used Time I am using vb.net data table and SQL server for database. To set value in first row I am using SQL function DATEADD(), through which two hours are added and same function for adding time in end date.
I want to copy my End date of previous row in next row, so that whole calculation works properly.Can I do it through SQL Only? or I will need a function to do it in Vb.net Data table from where it will be used for reports. Note: Id sequence can be changed Thanks for help.
Below recursive CTE logic will give you the desired output.
create table #tmp
(
ID int,
starttime datetime2,
endtime datetime2,
used varchar(5)
)
insert into #tmp values
(1,'17-Apr-2017 2:00','17-Apr-2017','1:30'),
(2,'17-Apr-2017 2:00','17-Apr-2017','2:00'),
(3,'17-Apr-2017 2:00','17-Apr-2017','1:00'),
(4,'17-Apr-2017 2:00','17-Apr-2017','0:30'),
(5,'28-Apr-2017 2:00','28-Apr-2017','3:30')
;with CTE as (
select ID,starttime,endtime,used,levels FROM
(select row_number() over (partition by cast(starttime as date) order by starttime) RID,ID,starttime,dateadd(hour,cast(substring(used,1,charindex(':',used)-1) as int),dateadd(mi,cast(right(used,2) as int),starttime))
endtime,used,0 levels
from #tmp ) T
where RID=1
union all
select T.ID,C.endtime,dateadd(hour,cast(substring(T.used,1,charindex(':',T.used)-1) as int),dateadd(mi,cast(right(T.used,2) as int),C.endtime))
endtime,T.used,C.levels+1
from CTE C inner join #tmp T on T.ID=C.ID+1
where datediff(d,C.starttime,T.starttime)=0
)
select ID,StartTime,EndTime,Used from CTE order by ID
drop table #tmp

how to get data when using where clause of time stamp in select query

I’m trying to get date with select query that have time stamp in WHERE clause.
my data table look like this:
| startTime | endTime | TimeID
----------------------------------
| 07:00:00 | 15:00:00 | 1
| 15:00:00 | 23:00:00 | 2
| 23:00:00 | 07:00:00 | 3
---------------------------------
this is my query statement:
SELECT TimeID
FROM myTable
WHERE StartTime >= 'Current_TIME' AND EndTime < 'Current_TIME'
if the time is somewhere between 07:00 and 23:00 then I get the answer correct, else I don't.
for example:
if the current time is 02:00:00 then the first condition is false because 02 is not larger then 23 and second condition is valid > 02 is smaller then 07
I try to use BETWEEN clause and use CASE WHEN and ISNULL but the query always returns empty in the scenario above.
This will work, but it's not the best in performance wise:
SELECT TimeID
FROM myTable
WHERE
(
(StartTime <= 'Current_TIME' AND EndTime > 'Current_TIME') or
(StartTime <= 'Current_TIME' AND StartTime > EndTime) or
(EndTime > 'Current_TIME' AND StartTime > EndTime)
)
It would probably be better to split the row that spans midnight into 2 separate rows, if possible.

Finding correlated values from second table without resorting to PL/SQL

I have the following two tables in my database:
a) A table containing values acquired at a certain date (you may think of these as, say, temperature readings):
sensor_id | acquired | value
----------+---------------------+--------
1 | 2009-04-01 10:00:00 | 20
1 | 2009-04-01 10:01:00 | 21
1 | 2009-04 01 10:02:00 | 20
1 | 2009-04 01 10:09:00 | 20
1 | 2009-04 01 10:11:00 | 25
1 | 2009-04 01 10:15:00 | 30
...
The interval between the readings may differ, but the combination of (sensor_id, acquired) is unique.
b) A second table containing time periods and a description (you may think of these as, say, periods when someone turned on the radiator):
sensor_id | start_date | end_date | description
----------+---------------------+---------------------+------------------
1 | 2009-04-01 10:00:00 | 2009-04-01 10:02:00 | some description
1 | 2009-04-01 10:10:00 | 2009-04-01 10:14:00 | something else
Again, the length of the period may differ, but there will never be overlapping time periods for any given sensor.
I want to get a result that looks like this for any sensor and any date range:
sensor id | start date | v1 | end date | v2 | description
----------+---------------------+----+---------------------+----+------------------
1 | 2009-04-01 10:00:00 | 20 | 2009-04-01 10:02:00 | 20 | some description
1 | 2009-04-01 10:10:00 | 25 | 2009-04-01 10:14:00 | 30 | some description
Or in text from: given a sensor_id and a date range of range_start and range_end,
find me all time periods which have overlap with the date range (that is, start_date < range_end and end_date > range_start) and for each of these rows, find the corresponding values from the value table for the time period's start_date and end_date (find the first row with acquired > start_date and acquired > end_date).
If it wasn't for the start_value and end_value columns, this would be a textbook trivial example of how to join two tables.
Can I somehow get the output I need in one SQL statement without resorting to writing a PL/SQL function to find these values?
Unless I have overlooked something blatantly obvious, this can't be done with simple subselects.
Database is Oracle 11g, so any Oracle-specific features are acceptable.
Edit: yes, looping is possible, but I want to know if this can be done with a single SQL select.
You can give this a try. Note the caveats at the end though.
SELECT
RNG.sensor_id,
RNG.start_date,
RDG1.value AS v1,
RNG.end_date,
RDG2.value AS v2,
RNG.description
FROM
Ranges RNG
INNER JOIN Readings RDG1 ON
RDG1.sensor_id = RNG.sensor_id AND
RDG1.acquired => RNG.start_date
LEFT OUTER JOIN Readings RDG1_NE ON
RDG1_NE.sensor_id = RDG1.sensor_id AND
RDG1_NE.acquired >= RNG.start_date AND
RDG1_NE.acquired < RDG1.acquired
INNER JOIN Readings RDG2 ON
RDG2.sensor_id = RNG.sensor_id AND
RDG2.acquired => RNG.end_date
LEFT OUTER JOIN Readings RDG1_NE ON
RDG2_NE.sensor_id = RDG2.sensor_id AND
RDG2_NE.acquired >= RNG.end_date AND
RDG2_NE.acquired < RDG2.acquired
WHERE
RDG1_NE.sensor_id IS NULL AND
RDG2_NE.sensor_id IS NULL
This uses the first reading after the start date of the range and the first reading after the end date (personally, I'd think using the last date before the start and end would make more sense or the closest value, but I don't know your application). If there is no such reading then you won't get anything at all. You can change the INNER JOINs to OUTER and put additional logic in to handle those situations based on your own business rules.
It seems pretty straight forward.
Find the sensor values for each range. Find a row - I will call acquired of this row just X - where X > start_date and not exists any other row with acquired > start_date and acquired < X. Do the same for end date.
Select only the ranges that meet the query - start_date before and end_date after the dates supplied by the query.
In SQL this would be something like that.
SELECT R1.*, SV1.aquired, SV2.aquired
FROM ranges R1
INNER JOIN sensor_values SV1 ON SV1.sensor_id = R1.sensor_id
INNER JOIN sensor_values SV2 ON SV2.sensor_id = R1.sensor_id
WHERE SV1.aquired > R1.start_date
AND NOT EXISTS (
SELECT *
FROM sensor_values SV3
WHERE SV3.aquired > R1.start_date
AND SV3.aquired < SV1.aquired)
AND SV2.aquired > R1.end_date
AND NOT EXISTS (
SELECT *
FROM sensor_values SV4
WHERE SV4.aquired > R1.end_date
AND SV4.aquired < SV2.aquired)
AND R1.start_date < #range_start
AND R1.end_date > #range_end