Find the total time taken by each `id` within a 1 hours - sql

How to find the total time taken by each id.
There must be one activity within an hour (After the start of the one activity),
and if there is any activity before the 1hrs time is elapsed, consider that time in the previous slot, calculate the time differences.
if 1 hrs has already passed now check if there is any other activity within a 1hrs period
Sample data is:
create table test (
id integer not null,
ts datetime not null
);
insert into test values (1, '2012-01-01 08:00');
insert into test values (1, '2012-01-01 08:01');
insert into test values (1, '2012-01-01 08:06');
insert into test values (1, '2012-01-01 08:30');
insert into test values (1, '2012-01-01 09:35');
insert into test values (1, '2012-01-02 16:10');
insert into test values (1, '2012-01-02 16:20');
insert into test values (1, '2012-01-03 06:40');
insert into test values (1, '2012-01-03 06:41');
insert into test values (2, '2012-01-01 08:30');
insert into test values (2, '2012-01-01 09:26');
insert into test values (2, '2012-01-01 10:25');
The output column will be:
insert into test values (1, '2012-01-01 08:00');
insert into test values (1, '2012-01-01 08:01');
insert into test values (1, '2012-01-01 08:06');
insert into test values (1, '2012-01-01 08:30');
Here the differences are exactly: 00:30:00
and there is the record which but it is after 1 hr, so we didn't add this activity on the previous slot and there is no activity up to 1 hour from this time so we do not consider this activity
insert into test values (1, '2012-01-01 09:35');`
Now, this runs for: 00:10:00
insert into test values (1, '2012-01-02 16:10');
insert into test values (1, '2012-01-02 16:20');
Similarly, this runs for 00:01:00
insert into test values (1, '2012-01-03 06:40');
insert into test values (1, '2012-01-03 06:41');
So the total time for id=1 will be 00:41:00.
id | Time duration (hh:mm:ss)
1 | 00:41:00
2 | 01:55:00
I couldn't think of how to start with this problem, any possible head-start would be appreciated. Thank you!

Your rules are a bit hard to follow, but I think I understand them. Count the gap between two rows for the same id if the second is less than an hour after the first. Then aggregate by the id.
So:
select id, sum(datediff(minute, ts, next_ts)) as duration_minutes
from (select t.*,
lead(ts) over (partition by id order by ts) as next_ts
from test t
) t
where datediff(minute, ts, next_ts) < 60
group by id;
Here is a db<>fiddle.
I am reluctant to convert the duration to a time value, because that is limited to just 24 hours.

Related

SQL : 12 hr shift function from 7am - 7am

I need to query all production records by 12 hr. shift. 7am-7am. if the date is after midnight and before 7am it's actually the previous day shift. In the below example I need to make them all 2022-01-01 like the last column. If I query by 2022-01-01 I don't get all the rows. Can I use a function for this to compare the time and make it the previous day?
declare #temp table
(
Emp_id int,
Time datetime,
shiftDate date
);
insert into #temp values (1, '2022-01-01 08:10:00:000', '2022-01-01')
insert into #temp values (1, '2022-01-01 10:21:00:000', '2022-01-01')
insert into #temp values (1, '2022-01-01 13:10:00:000', '2022-01-01')
insert into #temp values (1, '2022-01-01 22:22:00:000', '2022-01-01')
insert into #temp values (1, '2022-01-02 02:15:00:000', '2022-01-01')
insert into #temp values (1, '2022-01-02 04:22:00:000', '2022-01-01')
insert into #temp values (1, '2022-01-02 06:18:00:000', '2022-01-01')
insert into #temp values (1, '2022-01-02 06:55:00:000', '2022-01-01')
select * from #temp
select * from #temp
where convert(date, [time]) = '2022-01-01'

SQL search full time or partial time

I am trying to create a stored procedure that can filter on a full or partial time. It should only filter on hours or minutes (not seconds) or both hours and minutes.
Using the sample data below:
#StartTimeFilter = '09:15' --> should return record #1
#StartTimeFilter = '10' --> should return records 2, 3, 10
#StartTimeFilter = '5' --> should return records 1, 2, 5, 6, 7, 8, 9
#StartTimeFilter = '45' --> should return records 5, 6
#StartTimeFilter = '13:45' --> should return record #6
#StartTimeFilter = '11:' --> should return records 4, 5
Code:
CREATE TABLE test
(
id INT,
startTime DateTime
);
INSERT INTO test (id, startTime) VALUES (1, '2021-10-25 09:15:00');
INSERT INTO test (id, startTime) VALUES (2, '2021-10-25 10:15:00');
INSERT INTO test (id, startTime) VALUES (3, '2021-10-25 10:30:00');
INSERT INTO test (id, startTime) VALUES (4, '2021-10-25 11:30:00');
INSERT INTO test (id, startTime) VALUES (5, '2021-10-25 11:45:00');
INSERT INTO test (id, startTime) VALUES (6, '2021-10-25 13:45:00');
INSERT INTO test (id, startTime) VALUES (7, '2021-10-25 14:50:00');
INSERT INTO test (id, startTime) VALUES (8, '2021-10-25 15:51:00');
INSERT INTO test (id, startTime) VALUES (9, '2021-10-25 15:58:00');
INSERT INTO test (id, startTime) VALUES (10,'2021-10-25 16:10:00');
It looks like you need a simple like string comparision:
declare #StartTimeFilter varchar(10)='5'
select *
from test
where Convert(varchar(5),starttime,114) like Concat('%',#StartTimeFilter,'%')

How to calculate the average value of following n rows based on another column - SQL (Oracle)

I am trying to calculate average monthly value of premiums for each POLICY_ID in monthly basis as shown below. When a customer updates his/her yearly payment frequency to a value different than 12, I need to manually calculate the average monthly value for the PREMIUM. How can I achieve the values shown in MONTHLY _PREMIUM_DESIRED?
Thanks in advance.
Note: Oracle version 12c
What I've tried:
SELECT
T.*,
SUM(PREMIUM) OVER(PARTITION BY T.POLICY_ID ORDER BY T.POLICY_ID, T.PAYMENT_DATE ROWS BETWEEN CURRENT ROW AND 12/T.YEARLY_PAYMENT_FREQ-1 FOLLOWING ) / (12/T.YEARLY_PAYMENT_FREQ) MONTLY_PREMIUM_CALCULATED
FROM MYTABLE T
;
Code for data:
DROP TABLE MYTABLE;
CREATE TABLE MYTABLE (POLICY_ID NUMBER(11), PAYMENT_DATE DATE, PREMIUM NUMBER(5), YEARLY_PAYMENT_FREQ NUMBER(2),MONTHLY_PREMIUM_DESIRED NUMBER(5));
INSERT INTO MYTABLE VALUES (1, DATE '2014-10-01',120,12,120);
INSERT INTO MYTABLE VALUES (1, DATE '2014-11-01',360,4,120);
INSERT INTO MYTABLE VALUES (1, DATE '2014-12-01',0,4,120);
INSERT INTO MYTABLE VALUES (1, DATE '2015-01-01',0,4,120);
INSERT INTO MYTABLE VALUES (1, DATE '2015-02-01',360,4,120);
INSERT INTO MYTABLE VALUES (1, DATE '2015-03-01',0,4,120);
INSERT INTO MYTABLE VALUES (1, DATE '2015-04-01',0,4,120);
INSERT INTO MYTABLE VALUES (1, DATE '2015-05-01',720,2,120);
INSERT INTO MYTABLE VALUES (1, DATE '2015-06-01',0,2,120);
INSERT INTO MYTABLE VALUES (1, DATE '2015-07-01',0,2,120);
INSERT INTO MYTABLE VALUES (1, DATE '2015-08-01',0,2,120);
INSERT INTO MYTABLE VALUES (1, DATE '2015-09-01',0,2,120);
INSERT INTO MYTABLE VALUES (1, DATE '2015-10-01',0,2,120);
INSERT INTO MYTABLE VALUES (1, DATE '2015-11-01',120,12,120);
INSERT INTO MYTABLE VALUES (2, DATE '2015-01-01',60,3,15);
INSERT INTO MYTABLE VALUES (2, DATE '2015-02-01',0,3,15);
INSERT INTO MYTABLE VALUES (2, DATE '2015-03-01',0,3,15);
INSERT INTO MYTABLE VALUES (2, DATE '2015-04-01',0,3,15);
INSERT INTO MYTABLE VALUES (2, DATE '2015-05-01',180,1,15);
INSERT INTO MYTABLE VALUES (2, DATE '2015-06-01',0,1,15);
INSERT INTO MYTABLE VALUES (2, DATE '2015-07-01',0,1,15);
INSERT INTO MYTABLE VALUES (2, DATE '2015-08-01',0,1,15);
INSERT INTO MYTABLE VALUES (2, DATE '2015-09-01',0,1,15);
INSERT INTO MYTABLE VALUES (2, DATE '2015-10-01',0,1,15);
INSERT INTO MYTABLE VALUES (2, DATE '2015-11-01',0,1,15);
INSERT INTO MYTABLE VALUES (2, DATE '2015-12-01',0,1,15);
INSERT INTO MYTABLE VALUES (2, DATE '2016-01-01',0,1,15);
INSERT INTO MYTABLE VALUES (2, DATE '2016-02-01',0,1,15);
INSERT INTO MYTABLE VALUES (2, DATE '2016-03-01',0,1,15);
INSERT INTO MYTABLE VALUES (2, DATE '2016-04-01',0,1,15);
INSERT INTO MYTABLE VALUES (2, DATE '2016-05-01',15,12,15);
INSERT INTO MYTABLE VALUES (2, DATE '2016-06-01',15,12,15);
SELECT * FROM MYTABLE;
EDIT:
Regardless from payment frequency PREMIUM amount can also be changed by customer. Below, for the POLICY_ID = 1, I have added new records starting from "2015/11/01" to demonstrate this situation. In this case, average monthly premium increased from 120 to 240.
Also removed the screenshot to make the question more readable.
DROP TABLE MYTABLE2;
CREATE TABLE MYTABLE2 (POLICY_ID NUMBER(11), PAYMENT_DATE DATE, PREMIUM NUMBER(5), YEARLY_PAYMENT_FREQ NUMBER(2),MONTHLY_PREMIUM_DESIRED NUMBER(5));
INSERT INTO MYTABLE2 VALUES (1, DATE '2014-10-01',120,12,120);
INSERT INTO MYTABLE2 VALUES (1, DATE '2014-11-01',360,4,120);
INSERT INTO MYTABLE2 VALUES (1, DATE '2014-12-01',0,4,120);
INSERT INTO MYTABLE2 VALUES (1, DATE '2015-01-01',0,4,120);
INSERT INTO MYTABLE2 VALUES (1, DATE '2015-02-01',360,4,120);
INSERT INTO MYTABLE2 VALUES (1, DATE '2015-03-01',0,4,120);
INSERT INTO MYTABLE2 VALUES (1, DATE '2015-04-01',0,4,120);
INSERT INTO MYTABLE2 VALUES (1, DATE '2015-05-01',720,2,120);
INSERT INTO MYTABLE2 VALUES (1, DATE '2015-06-01',0,2,120);
INSERT INTO MYTABLE2 VALUES (1, DATE '2015-07-01',0,2,120);
INSERT INTO MYTABLE2 VALUES (1, DATE '2015-08-01',0,2,120);
INSERT INTO MYTABLE2 VALUES (1, DATE '2015-09-01',0,2,120);
INSERT INTO MYTABLE2 VALUES (1, DATE '2015-10-01',0,2,120);
INSERT INTO MYTABLE2 VALUES (1, DATE '2015-11-01',240,12,240);
INSERT INTO MYTABLE2 VALUES (1, DATE '2016-12-01',240,12,240); --newly added records
INSERT INTO MYTABLE2 VALUES (1, DATE '2016-01-01',960,4,240); --newly added records
INSERT INTO MYTABLE2 VALUES (1, DATE '2016-02-01',0,4,240); --newly added records
INSERT INTO MYTABLE2 VALUES (1, DATE '2016-03-01',0,4,240); --newly added records
INSERT INTO MYTABLE2 VALUES (1, DATE '2016-04-01',0,4,240); --newly added records
INSERT INTO MYTABLE2 VALUES (1, DATE '2016-05-01',960,4,240); --newly added records
INSERT INTO MYTABLE2 VALUES (1, DATE '2016-06-01',0,4,240); --newly added records
INSERT INTO MYTABLE2 VALUES (1, DATE '2016-07-01',0,4,240); --newly added records
INSERT INTO MYTABLE2 VALUES (1, DATE '2016-08-01',0,4,240); --newly added records
INSERT INTO MYTABLE2 VALUES (2, DATE '2015-01-01',60,3,15);
INSERT INTO MYTABLE2 VALUES (2, DATE '2015-02-01',0,3,15);
INSERT INTO MYTABLE2 VALUES (2, DATE '2015-03-01',0,3,15);
INSERT INTO MYTABLE2 VALUES (2, DATE '2015-04-01',0,3,15);
INSERT INTO MYTABLE2 VALUES (2, DATE '2015-05-01',180,1,15);
INSERT INTO MYTABLE2 VALUES (2, DATE '2015-06-01',0,1,15);
INSERT INTO MYTABLE2 VALUES (2, DATE '2015-07-01',0,1,15);
INSERT INTO MYTABLE2 VALUES (2, DATE '2015-08-01',0,1,15);
INSERT INTO MYTABLE2 VALUES (2, DATE '2015-09-01',0,1,15);
INSERT INTO MYTABLE2 VALUES (2, DATE '2015-10-01',0,1,15);
INSERT INTO MYTABLE2 VALUES (2, DATE '2015-11-01',0,1,15);
INSERT INTO MYTABLE2 VALUES (2, DATE '2015-12-01',0,1,15);
INSERT INTO MYTABLE2 VALUES (2, DATE '2016-01-01',0,1,15);
INSERT INTO MYTABLE2 VALUES (2, DATE '2016-02-01',0,1,15);
INSERT INTO MYTABLE2 VALUES (2, DATE '2016-03-01',0,1,15);
INSERT INTO MYTABLE2 VALUES (2, DATE '2016-04-01',0,1,15);
INSERT INTO MYTABLE2 VALUES (2, DATE '2016-05-01',15,12,15);
INSERT INTO MYTABLE2 VALUES (2, DATE '2016-06-01',15,12,15);
SELECT * FROM MYTABLE2;
I think the calculation is:
select t.*,
premium / (12 / yearly_payment_freq)) as monthly_premium_calculated
from mytable t;
EDIT:
I see, you also need this spread over the intermediate months. So you can assign the groups by counting the number of non-zero payments. Then:
select t.*,
( max(premium) over (partition by policy_id, grp) /
(12 / yearly_payment_freq)
) as monthly_premium_calculated
from (select t.*,
sum(case when premium > 0 then 1 else 0 end) over (partition by policy_id order by payment_date) as grp
from mytable t
) t;
Here is a db<>fiddle (it uses Postgres because that is easier to set up than Oracle).

Sample observations per group without replacement in SQL

Using the provided table I would like to sample let's say 2 users per day so that users assigned to the two days are different. Of course the problem I have is more sophisticated, but this simple example gives the idea.
drop table if exists test;
create table test (
user_id int,
day_of_week int);
insert into test values (1, 1);
insert into test values (1, 2);
insert into test values (2, 1);
insert into test values (2, 2);
insert into test values (3, 1);
insert into test values (3, 2);
insert into test values (4, 1);
insert into test values (4, 2);
insert into test values (5, 1);
insert into test values (5, 2);
insert into test values (6, 1);
insert into test values (6, 2);
The expected results would look like this:
create table results (
user_id int,
day_of_week int);
insert into results values (1, 1);
insert into results values (2, 1);
insert into results values (3, 2);
insert into results values (6, 2);
You can use window functions. Here is an example . . . although the details do depend on your database (functions for random numbers vary by database):
select t.*
from (select t.*, row_number() over (partition by day_of_week order by random()) as seqnum
from test t
) t
where seqnum <= 2;

Get closest previous or future datetime to a given datetime in sql server

I am trying to get a closest Polling datetime to OrderSubmittedtime where the closest Polling datetime can be a datetime previous or future date(+ or -) compared to the OrderSubmittedtime as long as the datetime is close enough.
Below is the example for this :
create table Rosters
(
OrderID int,
PollingTime datetime
,OrdersubmittedTime datetime
)
insert into Rosters values (1,'2017-08-07 11:30:00.000','2017-08-07 04:12:51.000')
insert into Rosters values (1,'2017-08-07 12:13:34.000','2017-08-07 04:12:51.000')
insert into Rosters values (1,'2017-08-07 03:30:00.000','2017-08-07 04:12:51.000')
insert into Rosters values (1,'2017-08-08 00:30:00.000','2017-08-07 04:12:51.000')
insert into Rosters values (2,'2017-08-05 10:30:00.000','2017-08-07 04:12:51.000')
insert into Rosters values (2,'2017-08-06 11:30:00.000','2017-08-07 04:12:51.000')
insert into Rosters values (2,'2017-08-08 00:30:00.000','2017-08-07 04:12:51.000')
expected result set is :
for OrderID=1, the closest Polling Time will be '2017-08-07 03:30:00.000'
and for OrderID=2, the closest Polling Time will be '2017-08-06 11:30:00.000'
i could currently get to write a query like below, but it is not correct :
select PollingTime
,OrdersubmittedTime
,OrderID
, abs(DATEDIFF(MINUTE,OrdersubmittedTime,PollingTime)) as ClosestPollingTime
from Rosters
Kindly help me out , Thanks.
You can use an OUTER APPLY to join a subquery on the same table and take the closest value using your ClosestPollingTime like so (modified to filter on Seconds rather than minutes, based on comment from Damien_The_Unbeliever:
CREATE TABLE #Rosters
(
OrderID INT,
PollingTime DATETIME,
OrdersubmittedTime DATETIME
);
INSERT INTO #Rosters
VALUES
(1, '2017-08-07 11:30:00.000', '2017-08-07 04:12:51.000'),
(1, '2017-08-07 12:13:34.000', '2017-08-07 04:12:51.000'),
(1, '2017-08-07 03:30:00.000', '2017-08-07 04:12:51.000'),
(1, '2017-08-08 00:30:00.000', '2017-08-07 04:12:51.000'),
(2, '2017-08-05 10:30:00.000', '2017-08-07 04:12:51.000'),
(2, '2017-08-06 11:30:00.000', '2017-08-07 04:12:51.000'),
(2, '2017-08-08 00:30:00.000', '2017-08-07 04:12:51.000');
SELECT r.OrdersubmittedTime,
r.OrderID,
t.ClosestPollingTime
FROM #Rosters AS r
OUTER APPLY
(
SELECT TOP 1
r2.OrderID,
r2.PollingTime as ClosestPollingTime
FROM #Rosters AS r2
WHERE r2.OrderID = r.OrderID
ORDER BY ABS(DATEDIFF(SECOND, r2.OrdersubmittedTime, r2.PollingTime))
) t
GROUP BY r.OrdersubmittedTime,
r.OrderID,
t.ClosestPollingTime;
DROP TABLE #Rosters;
Produces:
OrdersubmittedTime OrderID ClosestPollingTime
2017-08-07 04:12:51.000 1 2017-08-07 03:30:00.000
2017-08-07 04:12:51.000 2 2017-08-06 11:30:00.000