Related
I'm looking for help with a SQL query. Below are the details.
Database: Microsoft SQL Server 2016
Data Table:
It's a "version history" table with 3 columns: version number, effective date, and end date.
The version number with an end_dt of 12/31/9999 is considered the "active" version number.
Users can "restore" prior versions and make them active again.
version_number
eff_dt
end_dt
0
2021-04-13 18:03:26.483
2021-04-16 18:35:06.367
1
2021-04-16 18:35:06.370
2021-04-19 20:45:38.993
1
2021-04-19 20:45:38.997
2021-05-06 16:00:59.990
2
2021-05-06 16:00:59.990
2021-05-06 16:13:03.997
3
2021-05-06 16:13:04.000
2021-05-06 16:17:23.127
4
2021-05-06 16:17:23.130
2021-05-06 16:52:45.250
4
2021-05-06 16:52:45.253
2021-05-11 15:36:25.283
4
2021-05-11 15:36:25.283
2021-05-14 15:52:50.843
5
2021-05-14 15:52:50.847
2021-05-20 17:14:55.860
4
2021-05-20 17:14:55.863
2021-05-20 17:14:55.867
1
2021-05-20 17:14:55.870
9999-12-31 00:00:00.000
Desired Output:
A query to display a consolidated version history where consecutive entries in the version history table are displayed as a single row encompassing the entire date range the version was active.
version_number
eff_dt
end_dt
0
2021-04-13 18:03:26.483
2021-04-16 18:35:06.367
1
2021-04-16 18:35:06.370
2021-05-06 16:00:59.990
2
2021-05-06 16:00:59.990
2021-05-06 16:13:03.997
3
2021-05-06 16:13:04.000
2021-05-06 16:17:23.127
4
2021-05-06 16:17:23.130
2021-05-14 15:52:50.843
5
2021-05-14 15:52:50.847
2021-05-20 17:14:55.860
4
2021-05-20 17:14:55.863
2021-05-20 17:14:55.867
1
2021-05-20 17:14:55.870
9999-12-31 00:00:00.000
Question:
How would one write a SQL statement to generate the Desired Output based on the Data Table?
SQL Script to create sample data:
CREATE TABLE #t1(
[version_number] [int] NULL,
[eff_dt] [datetime] NOT NULL,
[end_dt] [datetime] NOT NULL
)
GO
INSERT #t1 ([version_number], [eff_dt], [end_dt]) VALUES (1, CAST(N'2021-05-20T17:14:55.870' AS DateTime), CAST(N'9999-12-31T00:00:00.000' AS DateTime))
GO
INSERT #t1 ([version_number], [eff_dt], [end_dt]) VALUES (5, CAST(N'2021-05-14T15:52:50.847' AS DateTime), CAST(N'2021-05-20T17:14:55.860' AS DateTime))
GO
INSERT #t1 ([version_number], [eff_dt], [end_dt]) VALUES (4, CAST(N'2021-05-20T17:14:55.863' AS DateTime), CAST(N'2021-05-20T17:14:55.867' AS DateTime))
GO
INSERT #t1 ([version_number], [eff_dt], [end_dt]) VALUES (4, CAST(N'2021-05-11T15:36:25.283' AS DateTime), CAST(N'2021-05-14T15:52:50.843' AS DateTime))
GO
INSERT #t1 ([version_number], [eff_dt], [end_dt]) VALUES (4, CAST(N'2021-05-06T16:52:45.253' AS DateTime), CAST(N'2021-05-11T15:36:25.283' AS DateTime))
GO
INSERT #t1 ([version_number], [eff_dt], [end_dt]) VALUES (4, CAST(N'2021-05-06T16:17:23.130' AS DateTime), CAST(N'2021-05-06T16:52:45.250' AS DateTime))
GO
INSERT #t1 ([version_number], [eff_dt], [end_dt]) VALUES (3, CAST(N'2021-05-06T16:13:04.000' AS DateTime), CAST(N'2021-05-06T16:17:23.127' AS DateTime))
GO
INSERT #t1 ([version_number], [eff_dt], [end_dt]) VALUES (2, CAST(N'2021-05-06T16:00:59.990' AS DateTime), CAST(N'2021-05-06T16:13:03.997' AS DateTime))
GO
INSERT #t1 ([version_number], [eff_dt], [end_dt]) VALUES (1, CAST(N'2021-04-19T20:45:38.997' AS DateTime), CAST(N'2021-05-06T16:00:59.990' AS DateTime))
GO
INSERT #t1 ([version_number], [eff_dt], [end_dt]) VALUES (1, CAST(N'2021-04-16T18:35:06.370' AS DateTime), CAST(N'2021-04-19T20:45:38.993' AS DateTime))
GO
INSERT #t1 ([version_number], [eff_dt], [end_dt]) VALUES (0, CAST(N'2021-04-13T18:03:26.483' AS DateTime), CAST(N'2021-04-16T18:35:06.367' AS DateTime))
GO
You can achieve this in two steps, using successive common table expressions (cte). Firstly, you need a consecutive ranking number within your data. On the basis of this you can then do a recursive cte, looking at the version_number of consecutive rows (necessarily one apart). This allows us to create a "batch" number: if the version_number is the same, then we take the previous batch number, if it is different, we increment the previous batch number by one. Finally we need a simple min and max on the dates grouping by the batch number. The result looks like this:
declare #t1 TABLE (
[version_number] [int] NULL,
[eff_dt] [datetime] NOT NULL,
[end_dt] [datetime] NOT NULL
);
INSERT #t1 ([version_number], [eff_dt], [end_dt])
VALUES
(1, CAST(N'2021-05-20T17:14:55.870' AS DateTime), CAST(N'9999-12-31T00:00:00.000' AS DateTime)),
(5, CAST(N'2021-05-14T15:52:50.847' AS DateTime), CAST(N'2021-05-20T17:14:55.860' AS DateTime)),
(4, CAST(N'2021-05-20T17:14:55.863' AS DateTime), CAST(N'2021-05-20T17:14:55.867' AS DateTime)),
(4, CAST(N'2021-05-11T15:36:25.283' AS DateTime), CAST(N'2021-05-14T15:52:50.843' AS DateTime)),
(4, CAST(N'2021-05-06T16:52:45.253' AS DateTime), CAST(N'2021-05-11T15:36:25.283' AS DateTime)),
(4, CAST(N'2021-05-06T16:17:23.130' AS DateTime), CAST(N'2021-05-06T16:52:45.250' AS DateTime)),
(3, CAST(N'2021-05-06T16:13:04.000' AS DateTime), CAST(N'2021-05-06T16:17:23.127' AS DateTime)),
(2, CAST(N'2021-05-06T16:00:59.990' AS DateTime), CAST(N'2021-05-06T16:13:03.997' AS DateTime)),
(1, CAST(N'2021-04-19T20:45:38.997' AS DateTime), CAST(N'2021-05-06T16:00:59.990' AS DateTime)),
(1, CAST(N'2021-04-16T18:35:06.370' AS DateTime), CAST(N'2021-04-19T20:45:38.993' AS DateTime)),
(0, CAST(N'2021-04-13T18:03:26.483' AS DateTime), CAST(N'2021-04-16T18:35:06.367' AS DateTime));
with rowdata as
(
SELECT version_number, eff_dt, end_dt,
ROW_NUMBER() OVER(ORDER BY eff_dt) rn
FROM #t1
),
cte_recursive as
(
SELECT 1 as batchno, rn, version_number, eff_dt, end_dt
FROM rowdata
WHERE version_number = 0
UNION ALL
SELECT CASE WHEN rec.version_number = rd.version_number
THEN rec.batchno
ELSE rec.batchno + 1
END,
rd.rn, rd.version_number, rd.eff_dt, rd.end_dt
FROM cte_recursive rec
INNER JOIN rowdata rd on rec.rn = rd.rn - 1
)
SELECT
version_number, min(eff_dt) as eff_dt, max(end_dt) as end_dt
FROM cte_recursive
GROUP BY version_number, batchno
A couple of points to note. I prefer to use table variables to temporary tables (has a slight advantage that they don't need to be deleted!). Secondly you can insert multiple values separated by commas, as I have shown (no need for multiple inserts).
To help you understand how the recursive element works, we begin by a simple select which is the base case, in this case selecting where version_number is 0. We then build up from that by joining to the recursive part where rn (the value returned by ROW_NUMBER()) is one greater than the value we already have. We simply need to check for a difference in the version_number between our old value and the new row, to decide if the batch number needs incrementing or not.
You may find it helpful to run these queries one at a time, to help you understand what is happening (for example just run the sub-select that includes the row_number()).
BTW it was good of you to add the create statements.
This is easily accomplished using window functions. It's a variation on the gaps and islands problem.
The premise is to identify the islands of consecutive values of the version_number. The first CTE uses lag to compare the current row value to the previous row value and marks the start of the Next Group when the values are different. The second CTE uses sum as a window function to produce a running total of the groups. This provides each group of like version_numbers with its own sequential value.
The final select is then able to group by the version_number and its sequential group number, using the min and max dates for each.
Note also that using windows function and hitting the source table just once will also be significantly more efficient than a recursive solution.
with ng as (
select *, case when Lag(version_number) over(order by end_dt) = version_number then 0 else 1 end as ng
from #t1
), grp as (
select *, Sum(ng) over(order by end_dt) as grp
from ng
)
select version_number, Min(eff_dt) eff_dt, Max(end_dt) end_dt
from grp
group by version_number, grp
order by eff_dt
you can use this Query :
DROP TABLE #t1
CREATE TABLE #t1(
[version_number] [int] NULL,
[eff_dt] [datetime] NOT NULL,
[end_dt] [datetime] NOT NULL
)
GO
INSERT #t1 ([version_number], [eff_dt], [end_dt]) VALUES (1, CAST(N'2021-05-20T17:14:55.870' AS DateTime), CAST(N'9999-12-31T00:00:00.000' AS DateTime))
GO
INSERT #t1 ([version_number], [eff_dt], [end_dt]) VALUES (5, CAST(N'2021-05-14T15:52:50.847' AS DateTime), CAST(N'2021-05-20T17:14:55.860' AS DateTime))
GO
INSERT #t1 ([version_number], [eff_dt], [end_dt]) VALUES (4, CAST(N'2021-05-20T17:14:55.863' AS DateTime), CAST(N'2021-05-20T17:14:55.867' AS DateTime))
GO
INSERT #t1 ([version_number], [eff_dt], [end_dt]) VALUES (4, CAST(N'2021-05-11T15:36:25.283' AS DateTime), CAST(N'2021-05-14T15:52:50.843' AS DateTime))
GO
INSERT #t1 ([version_number], [eff_dt], [end_dt]) VALUES (4, CAST(N'2021-05-06T16:52:45.253' AS DateTime), CAST(N'2021-05-11T15:36:25.283' AS DateTime))
GO
INSERT #t1 ([version_number], [eff_dt], [end_dt]) VALUES (4, CAST(N'2021-05-06T16:17:23.130' AS DateTime), CAST(N'2021-05-06T16:52:45.250' AS DateTime))
GO
INSERT #t1 ([version_number], [eff_dt], [end_dt]) VALUES (3, CAST(N'2021-05-06T16:13:04.000' AS DateTime), CAST(N'2021-05-06T16:17:23.127' AS DateTime))
GO
INSERT #t1 ([version_number], [eff_dt], [end_dt]) VALUES (2, CAST(N'2021-05-06T16:00:59.990' AS DateTime), CAST(N'2021-05-06T16:13:03.997' AS DateTime))
GO
INSERT #t1 ([version_number], [eff_dt], [end_dt]) VALUES (1, CAST(N'2021-04-19T20:45:38.997' AS DateTime), CAST(N'2021-05-06T16:00:59.990' AS DateTime))
GO
INSERT #t1 ([version_number], [eff_dt], [end_dt]) VALUES (1, CAST(N'2021-04-16T18:35:06.370' AS DateTime), CAST(N'2021-04-19T20:45:38.993' AS DateTime))
GO
INSERT #t1 ([version_number], [eff_dt], [end_dt]) VALUES (0, CAST(N'2021-04-13T18:03:26.483' AS DateTime), CAST(N'2021-04-16T18:35:06.367' AS DateTime))
GO
SELECT DISTINCT t.[version_number] , eff_Table.eff_dt , end_Table.end_dt FROM #t1 t INNER JOIN
(SELECT t.version_number, t.eff_dt
,ROW_NUMBER() OVER (PARTITION BY t.version_number ORDER BY t.eff_dt) AS FirstID
FROM #t1 t ) AS eff_Table ON t.version_number = eff_Table.version_number
INNER JOIN (
SELECT t.version_number,
t.end_dt
,ROW_NUMBER() OVER (PARTITION BY t.version_number ORDER BY t.end_dt desc) AS SecondID
FROM #t1 t ) AS end_Table ON end_Table.version_number = t.version_number
WHERE eff_Table.FirstID = 1 AND end_Table.SecondID = 1
I wanted to count the time gap between two rows for the same id if the second is less than an hour after the first, and partition the count for the week.
Suppose given date with time is 2020-07-01 08:00
create table #Temp (
Id integer not null,
Time datetime not null
);
insert into #Temp values (1, '2020-07-01 08:00');
insert into #Temp values (1, '2020-07-01 08:01');
insert into #Temp values (1, '2020-07-01 08:06');
insert into #Temp values (1, '2020-07-01 08:30');
insert into #Temp values (1, '2020-07-08 09:35');
insert into #Temp values (1, '2020-07-15 16:10');
insert into #Temp values (1, '2020-07-15 16:20');
insert into #Temp values (1, '2020-07-17 06:40');
insert into #Temp values (1, '2020-07-17 06:41');
insert into #Temp values (2, '2020-07-01 08:30');
insert into #Temp values (2, '2020-07-01 09:26');
insert into #Temp values (2, '2020-07-01 10:25');
insert into #Temp values (2, '2020-07-09 08:30');
insert into #Temp values (2, '2020-07-09 09:26');
insert into #Temp values (2, '2020-07-09 10:25');
insert into #Temp values (3, '2020-07-21 08:30');
insert into #Temp values (3, '2020-07-21 09:26');
insert into #Temp values (3, '2020-07-21 10:25');
The week should extend up to the last date in the record. Here, the last date is
2020-07-21 10:25
Have to transform the output from this piece of code and divide the duration weekly.
select Id, sum(datediff(minute, Time, next_ts)) as duration_minutes
from (select t.*,
lead(Time) over (partition by id order by Time) as next_ts
from #Temp t
) t
where datediff(minute, Time, next_ts) < 60
group by Id;
Output:
id duration_minutes
1 41
2 230
3 115
The desired output should divide this duration on a weekly basis,
like Week 1, Week 2, Week 3, and so on.
Desired Output:
If the
start date is 2020-07-01 08:00
end date is 2020-07-21 10:25
id | Week 1 | Week 2 | Week 3
--------------------------------------
1 | 30 | 0 | 11
2 | 115 | 115 | 0
3 | 0 | 0 | 115
similarly, if the
start date is 2020-07-08 08:00
id | Week 1 | Week 2
---------------------------
1 | 11 | 0
2 | 115 | 0
3 | 0 | 115
Is this what you want?
select Id,
1 + datediff(second, '2020-07-01 06:00', time) / (24 * 60 * 60 * 7) as week_num,
sum(datediff(minute, Time, next_ts)) as duration_minutes
from (select t.*,
lead(Time) over (partition by id order by Time) as next_ts
from Temp t
) t
where datediff(minute, Time, next_ts) < 60
group by Id, datediff(second, '2020-07-01 06:00', time) / (24 * 60 * 60 * 7)
order by id, week_num;
Here is a db<>fiddle.
I am not able to understand the logic behind the week periods. Anyone, in the example below I am using the following code to set the week:
'Week ' + CAST(DENSE_RANK() OVER (ORDER BY DATEDIFF(DAY, #FirstDate, next_ts) / 7) AS VARCHAR(12))
You can adjust it to ignore the ours, be more precise or something else to match your real requirements.
Apart from that, you just need to perform a dynamic PIVOT. Here is the full working example:
DROP TABLE IF EXISTS #Temp;
create table #Temp (
Id integer not null,
Time datetime not null
);
insert into #Temp values (1, '2020-07-01 08:00');
insert into #Temp values (1, '2020-07-01 08:01');
insert into #Temp values (1, '2020-07-01 08:06');
insert into #Temp values (1, '2020-07-01 08:30');
insert into #Temp values (1, '2020-07-08 09:35');
insert into #Temp values (1, '2020-07-15 16:10');
insert into #Temp values (1, '2020-07-15 16:20');
insert into #Temp values (1, '2020-07-17 06:40');
insert into #Temp values (1, '2020-07-17 06:41');
insert into #Temp values (2, '2020-07-01 08:30');
insert into #Temp values (2, '2020-07-01 09:26');
insert into #Temp values (2, '2020-07-01 10:25');
insert into #Temp values (2, '2020-07-09 08:30');
insert into #Temp values (2, '2020-07-09 09:26');
insert into #Temp values (2, '2020-07-09 10:25');
insert into #Temp values (3, '2020-07-21 08:30');
insert into #Temp values (3, '2020-07-21 09:26');
insert into #Temp values (3, '2020-07-21 10:25');
DROP TABLE IF EXISTS #TEST
CREATE TABLE #TEST
(
[ID] INT
,[week_day] VARCHAR(12)
,[time_in_minutes] BIGINT
)
DECLARE #FirstDate DATE;
SELECT #FirstDate = MIN(Time)
FROM #Temp
INSERT INTO #TEST
select id
,'Week ' + CAST(DENSE_RANK() OVER (ORDER BY DATEDIFF(DAY, #FirstDate, next_ts) / 7) AS VARCHAR(12))
,datediff(minute, Time, next_ts)
from (select t.*,
lead(Time) over (partition by id order by Time) as next_ts
from #Temp t
) t
where datediff(minute, Time, next_ts) < 60
DECLARE #columns NVARCHAR(MAX);
SELECT #columns = STUFF
(
(
SELECT ',' + QUOTENAME([week_day])
FROM
(
SELECT DISTINCT CAST(REPLACE([week_day], 'Week ', '') AS INT)
,[week_day]
FROM #TEST
) DS ([rowID], [week_day])
ORDER BY [rowID]
FOR XML PATH(''), TYPE
).value('.', 'VARCHAR(MAX)')
,1
,1
,''
);
DECLARE #DanymicSQL NVARCHAR(MAX);
SET #DanymicSQL = N'
SELECT [ID], ' + #columns + '
FROM #TEST
PIVOT
(
SUM([time_in_minutes]) FOR [week_day] IN (' + #columns + ')
) PVT';
EXEC sp_executesql #DanymicSQL;
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.
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).
Need help with SQL to show concurrency by person for every minute in a day.
for a data set below:
drop table test
create table test (person varchar(2), caseid varchar(3), starttime datetime, endtime datetime)
insert into test values ('aa', '1', '01/01/2013 06:42', '01/01/2013 07:06')
insert into test values ('aa', '1', '01/01/2013 07:31', '01/01/2013 09:38')
insert into test values ('aa', '2', '01/01/2013 08:37', '01/01/2013 11:44')
insert into test values ('aa', '3','01/01/2013 09:39', '01/01/2013 11:31')
insert into test values ('aa', '4','01/01/2013 11:09', '01/01/2013 13:30')
insert into test values ('aa', '5','01/01/2013 12:05', '01/01/2013 15:38')
insert into test values ('aa', '6', '01/01/2013 13:58', '01/01/2013 14:13')
insert into test values ('aa', '7', '01/01/2013 15:53', '01/01/2013 16:14')
insert into test values ('bb', '8', '01/01/2013 08:42', '01/01/2013 09:06')
insert into test values ('bb', '8', '01/01/2013 10:31', '01/01/2013 19:38')
insert into test values ('bb', '8','01/01/2013 20:37', '01/01/2013 21:44')
insert into test values ('bb', '9', '01/01/2013 09:39', '01/01/2013 11:31')
insert into test values ('bb', '9', '01/01/2013 11:45', '01/01/2013 13:30')
insert into test values ('bb', '9', '01/01/2013 12:05', '01/01/2013 15:38')
insert into test values ('bb', '10', '01/01/2013 13:58', '01/01/2013 14:13')
insert into test values ('bb', '10', '01/01/2013 15:53', '01/01/2013 16:14')
the result needs to be similar to the following:
aa 01/01/2013 6:42 1
aa 01/01/2013 6:43 1
aa 01/01/2013 6:44 1
....
....
aa 01/01/2013 8:37 2
aa 01/01/2013 8:38 2
....
....
bb 01/01/2013 8:42 1
bb 01/01/2013 8:43 1
bb 01/01/2013 10:31 2
....
....
Thanks
You can do this with a correlated subquery:
Select t.*,
(Select count(*)
From t t2
Where t2. Start <= t.start and
T2.end >= t.end
) numoverlaps
From t
(Apologies for syntax errors; I'm on a mobile device)
This finds concurrency at every time in the input data. It does not do it for every minute of time.
This seems to work, but there may be a more elegant solution:
-- get range of days involved
declare #minDate date = (select MIN(starttime) from test)
declare #maxDate date = (select MAX(endtime) from test)
-- create table containing all days
if OBJECT_ID('tempdb..#days') is not null
drop table #days
create table #days (d date)
declare #day date = #minDate
while #day <= #maxDate
begin
insert #days (d) values (#day)
set #day = DATEADD(day, 1, #day)
end
-- create table containing all minutes in the day
if OBJECT_ID('tempdb..#minutes') is not null
drop table #minutes
create table #minutes (m int)
declare #minute int = 0
while #minute < 24*60
begin
insert #minutes (m) values (#minute)
set #minute = #minute + 1
end
select person, dateadd(minute, m, convert(datetime, startdate)), c from
(
select person, m, startdate, count(m) c from
(
-- cross join to select all days and minutes
select d.d, m.m from #days d cross join #minutes m
)
t0
inner join
(
select
person,
convert(date, starttime) startdate,
datediff(minute, convert(date, starttime), starttime) startmin,
datediff(minute, convert(date, endtime), endtime) endmin
from test
)
t1
on t0.m between t1.startmin and t1.endmin
and t0.d = t1.startdate
group by person, m, startdate
)
t2
order by person, startdate, m, c
Here is how I would approach it if using a database that supports CTE and inline views:
First a CTE to generate a two-col listing of the (24*60) minutes of the day for a specified date:
time1 time2
2013-02-12 00:00, 2013-02-12 00:01
.
.
.
2013-02-12 23:59, 2013-02-13 00:00
Left join that CTE to your cases table where cases.starttime between time1 and time2 or cases.endtime between time1 and time2. That brings back either nulls where no part of the case was ongoing during that minute or the caseid and personid when a case was ongoing during that minute.
Make the above an inline view. You end up with a set of all minutes in the day and the left-joined caseid and personid, or nulls:
time1, time2, caseid, personid
If you select from that inline view where caseid is not null you end up with the minutes where one or more cases was ongoing; if you then group by personid, time1 and count(caseid) you get the tally of cases per person in that particular one-minute time slot.