Average Duration in Status - Gaps and Islands - sql

I'm trying to calculate the average turnover time of a piece of equipment in REPAIR status.
I was able to create a query containing a list of equipments with their snapshotted status on each day.
+-----------------+--------------+--------+----------------------+------------+------------------+
| equipmentNumber | snapshotDate | status | previousSnapshotDate | prevStatus | statusChangeFlag |
+-----------------+--------------+--------+----------------------+------------+------------------+
| 123456 | 2018-04-29 | ONHIRE | 2018-04-28 | AVAILABLE | 1 |
| 123456 | 2018-04-30 | ONHIRE | 2018-04-29 | ONHIRE | 0 |
| 123456 | 2018-05-01 | ONHIRE | 2018-04-30 | ONHIRE | 0 |
| 123456 | 2018-05-02 | REPAIR | 2018-05-01 | ONHIRE | 1 |
| 123456 | 2018-05-03 | REPAIR | 2018-05-02 | REPAIR | 0 |
| 123456 | 2018-05-04 | ONHIRE | 2018-05-03 | REPAIR | 1 |
| 654321 | 2018-04-30 | REPAIR | 2018-04-29 | AVAILABLE | 1 |
| 654321 | 2018-05-01 | REPAIR | 2018-04-30 | REPAIR | 0 |
| 654321 | 2018-05-02 | REPAIR | 2018-05-01 | REPAIR | 0 |
+-----------------+--------------+--------+----------------------+------------+------------------+
So, in this example, we have 2 equipments, "123456" was in REPAIR status 2 days on 5/2 and 5/3, and "654321" was in REPAIR status 3 days on 4/30, 5/1, and 5/2. That would be an average repair turnaround time of (2+3) / 2 = 2.5 days.
I tried this algorithm (Detect consecutive dates ranges using SQL) but it doesn't seem to be quite working for my needs.

I attempt to answer Gaps and Islands using an Incrementing ID column, create one if one doesn't exist, and the ROW_NUMBER window function
CREATE TABLE T1
([equipmentNumber] int, [snapshotDate] datetime, [status] varchar(6), [previousSnapshotDate] datetime, [prevStatus] varchar(9), [statusChangeFlag] int)
;
INSERT INTO T1
([equipmentNumber], [snapshotDate], [status], [previousSnapshotDate], [prevStatus], [statusChangeFlag])
VALUES
(123456, '2018-04-29 00:00:00', 'ONHIRE', '2018-04-28 00:00:00', 'AVAILABLE', 1),
(123456, '2018-04-30 00:00:00', 'ONHIRE', '2018-04-29 00:00:00', 'ONHIRE', 0),
(123456, '2018-05-01 00:00:00', 'ONHIRE', '2018-04-30 00:00:00', 'ONHIRE', 0),
(123456, '2018-05-02 00:00:00', 'REPAIR', '2018-05-01 00:00:00', 'ONHIRE', 1),
(123456, '2018-05-03 00:00:00', 'REPAIR', '2018-05-02 00:00:00', 'REPAIR', 0),
(123456, '2018-05-04 00:00:00', 'ONHIRE', '2018-05-03 00:00:00', 'REPAIR', 1),
(654321, '2018-04-30 00:00:00', 'REPAIR', '2018-04-29 00:00:00', 'AVAILABLE', 1),
(654321, '2018-05-01 00:00:00', 'REPAIR', '2018-04-30 00:00:00', 'REPAIR', 0),
(654321, '2018-05-02 00:00:00', 'REPAIR', '2018-05-01 00:00:00', 'REPAIR', 0)
;
;WITH cteX
AS(
SELECT
Id = ROW_NUMBER()OVER(ORDER BY T.equipmentNumber, T.snapshotDate)
,T.equipmentNumber
,T.snapshotDate
,T.[status]
,T.previousSnapshotDate
,T.prevStatus
,T.statusChangeFlag
FROM dbo.T1 T
),cteIsland
AS(
SELECT
Island = X.Id - ROW_NUMBER()OVER(ORDER BY X.Id)
,*
FROM cteX X
WHERE X.[status] = 'REPAIR'
)
SELECT * FROM cteIsland
Note the Island Column
Island Id equipmentNumber status
3 4 123456 REPAIR
3 5 123456 REPAIR
4 7 654321 REPAIR
4 8 654321 REPAIR
4 9 654321 REPAIR
Using the Island Column you can get the answer you need with this TSQL
;WITH cteX
AS(
SELECT
Id = ROW_NUMBER()OVER(ORDER BY T.equipmentNumber, T.snapshotDate)
,T.equipmentNumber
,T.snapshotDate
,T.[status]
,T.previousSnapshotDate
,T.prevStatus
,T.statusChangeFlag
FROM dbo.T1 T
),cteIsland
AS(
SELECT
Island = X.Id - ROW_NUMBER()OVER(ORDER BY X.Id)
,*
FROM cteX X
WHERE X.[status] = 'REPAIR'
)
SELECT
AvgDuration =SUM(Totals.IslandCounts) / (COUNT(Totals.IslandCounts) * 1.0)
FROM
(
SELECT
IslandCounts = COUNT(I.Island)
,I.equipmentNumber
FROM cteIsland I
GROUP BY I.equipmentNumber
) Totals
Answer
AvgDuration
2.50000000000000
Here's the SQLFiddle

That method should work to identify the repair periods:
select equipmentNumber, min(snapshotDate), max(snapshotDate)
from (select t.*,
row_number() over (partition by equipmentNumber order by snapshotDate) as seqnum
from t
) t
where status = 'REPAIR'
group by equipmentNumber, dateadd(day, - seqnum, snapshotDate);
You can get the average using a subquery:
select avg(datediff(day, minsd, maxsd) * 1.0)
from (select equipmentNumber, min(snapshotDate) as minsd, max(snapshotDate) as maxsd
from (select t.*,
row_number() over (partition by equipmentNumber order by snapshotDate) as seqnum
from t
) t
where status = 'REPAIR'
group by equipmentNumber, dateadd(day, - seqnum, snapshotDate)
) e;

Related

Can Not Group By on DATE from Timestamp

I am unable to group by on date from a timestamp column in below query:
CHG_TABLE
+----+--------+----------------+-----------------+-------+-----------+
| Key|Seq_Num | Start_Date | End_Date | Value |Record_Type|
+----+--------+----------------+-----------------+-------+-----------+
| 1 | 1 | 5/25/2019 2.05 | 12/31/9999 00.00| 800 | Insert |
| 1 | 1 | 5/25/2019 2.05 | 5/31/2019 11.12 | 800 | Update |
| 1 | 2 | 5/31/2019 11.12| 12/31/9999 00.00| 900 | Insert |
| 1 | 2 | 5/31/2019 11.12| 6/15/2019 12.05 | 900 | Update |
| 1 | 3 | 6/15/2019 12.05| 12/31/9999 00.00| 1000 | Insert |
| 1 | 3 | 6/15/2019 12.05| 6/25/2019 10.20 | 1000 | Update |
+---+---------+----------------+-----------------+-------+-----------+
RESULT:
+-----+------------------+----------------+-----------+----------+
| Key | Month_Start_Date | Month_End_Date |Begin_Value|End_Value |
+---- +------------------+----------------+-----------+----------+
| 1 | 6/1/2019 | 6/30/2019 | 1700 | 1000 |
| 1 | 7/1/2019 | 7/31/2019 | 1000 | 1000 |
+-----+------------------+----------------+-----------+----------+
Begin_Value : Sum(Value) for Max(Start_Date) < Month_Start_Date -> Should pick up latest date from last month
End_Value : Sum(Value) for Max(Start_Date) <= Month_End_Date -> Should pick up the latest date
SELECT k.key,
dd.month_start_date,
dd.month_end_date,
gendata.value first_value,
gendata.next_value last_value
FROM dim_date dd CROSS JOIN dim_person k
JOIN (SELECT ct.key,
dateadd('day',1,last_day(ct.start_date)) start_date ,
SUM(ct.value),
lead(SUM(ct.value)) OVER(ORDER BY ct.start_date) next_value
FROM (SELECT key,to_char(start_Date,'MM-YYYY') MMYYYY, max(start_Date) start_date
FROM CHG_TABLE
GROUP BY to_char(start_Date,'MM-YYYY'), key
) dt JOIN CHG_TABLE ct ON
dt.start_date = ct.start_date AND
dt.key = ct.key
group by ct.key, to_char(start_Date,'MM-YYYY')
) gendata ON
to_char(dd.month_end_date,'MM-YYYY') = to_char(to_char(start_Date,'MM-YYYY')) AND
k.key = gendata.key;
Error:
start_Date is not a valid group by expression
Related post:
Monthly Snapshot using Date Dimension
Hoping, I understood your question correctly.
You can check below query
WITH chg_table ( key, seq_num, start_date, end_date, value, record_type ) AS
(
SELECT 1,1,TO_DATE('5/25/2019 2.05','MM/DD/YYYY HH24.MI'),TO_DATE('12/31/9999 00.00','MM/DD/YYYY HH24.MI'), 800, 'Insert' FROM DUAL UNION ALL
SELECT 1,1,TO_DATE('5/25/2019 2.05','MM/DD/YYYY HH24.MI'),TO_DATE('5/31/2019 11.12','MM/DD/YYYY HH24.MI'), 800, 'Update' FROM DUAL UNION ALL
SELECT 1,2,TO_DATE('5/31/2019 11.12','MM/DD/YYYY HH24.MI'),TO_DATE('12/31/9999 00.00','MM/DD/YYYY HH24.MI'), 900, 'Insert' FROM DUAL UNION ALL
SELECT 1,2,TO_DATE('5/31/2019 11.12','MM/DD/YYYY HH24.MI'),TO_DATE('6/15/2019 12.05','MM/DD/YYYY HH24.MI'), 900, 'Update' FROM DUAL UNION ALL
SELECT 1,3,TO_DATE('6/15/2019 12.05','MM/DD/YYYY HH24.MI'),TO_DATE('12/31/9999 00.00','MM/DD/YYYY HH24.MI'), 1000, 'Insert' FROM DUAL UNION ALL
SELECT 1,3,TO_DATE('6/15/2019 12.05','MM/DD/YYYY HH24.MI'),TO_DATE('6/25/2019 10.20','MM/DD/YYYY HH24.MI'), 1000, 'Update' FROM DUAL
)
select key , new_start_date Month_Start_Date , new_end_date Month_End_Date , begin_value ,
nvl(lead(begin_value) over(order by new_start_date),begin_value) end_value
from
(
select key , new_start_date , new_end_date , sum(value) begin_value
from
(
select key, seq_num, start_date
, value, record_type ,
trunc(add_months(start_date,1),'month') new_start_date ,
trunc(add_months(start_date,2),'month')-1 new_end_date
from chg_table
where record_type = 'Insert'
)
group by key , new_start_date , new_end_date
)
order by new_start_date
;
Db Fiddle link: https://dbfiddle.uk/?rdbms=oracle_18&fiddle=c77a71afa82769b48f424e1c0fa1c0b6
I am assuming that you are getting an "ORA-00979: not a GROUP BY expression" and this is due to your use of the TO_CHAR(timestamp_col,'DD-MM-YYYY') in the GROUP BY clause.
Adding the TO_CHAR(timestamp_col,'DD-MM-YYYY') to the select side of your statement should resolve this and provide the results you are expecting.
a, b, dateadd('day',1,last_day(timestamp_col)) start_date, TO_CHAR(timestamp_col,'DD-MM-YYYY'), ...```

Group by data to get count between the datetime range

Let's say I've a table like below
start_time end_time user_name
2019-01-01 00:00:05 2019-01-01 00:05:05 user1
2019-01-01 00:01:35 2019-01-01 00:06:05 user2
2019-01-01 00:02:05 2019-01-01 00:07:05 user3
2019-01-01 00:03:05 2019-01-01 00:08:05 user1
2019-01-01 00:04:05 2019-01-01 00:09:05 user2
My objective is find out how many users were logged in for a MINUTE. Say like below
time active no of users
2019-01-01 00:00:00 1
2019-01-01 00:01:00 2
2019-01-01 00:02:00 3
2019-01-01 00:03:00 3
2019-01-01 00:04:00 3
Now I first tried to round of time for a new column dateadd(mi, datediff(mi, 0, dateadd(s, 30, start_time)), 0). So, I will receive like above table time column
Next I tried to find the count for rounded datetime like below
SELECT
dateadd(mi, datediff(mi, 0, dateadd(s, 30, start_time)), 0) as RoundedDateTime,
(
SELECT count(distinct(user_name))
FROM entrytable sh
WHERE (sh.end_time > dateadd(mi, datediff(mi, 0, dateadd(s, 30, t.start_time)), 0)
and sh.start_time <= dateadd(mi, datediff(mi, 0, dateadd(s, 30, t.start_time)), 0))
) as usercounter
FROM entrytable t
But, above SQL query is running for longer time and goes to not responding mode.
I could not fix the issue. Can someone help?
Thanks in advance!
The most trivial solution is this:
DECLARE #t TABLE (start_time datetime, end_time datetime, user_name varchar(10));
INSERT INTO #t VALUES
('2019-01-01 00:00:05', '2019-01-01 00:05:05', 'user1'),
('2019-01-01 00:01:35', '2019-01-01 00:06:05', 'user2'),
('2019-01-01 00:02:05', '2019-01-01 00:07:05', 'user3'),
('2019-01-01 00:03:05', '2019-01-01 00:08:05', 'user1'),
('2019-01-01 00:04:05', '2019-01-01 00:09:05', 'user2');
SELECT dt AS date_time, SUM(SUM(val)) OVER (ORDER BY dt) AS active_count
FROM (
SELECT start_time, +1 FROM #t UNION ALL
SELECT end_time, -1 FROM #t
) cte1(dt, val)
GROUP BY dt
This will give you the number of active users whenever there was a change (someone logged in or logged out). Result:
| date_time | active_count |
|-------------------------|--------------|
| 2019-01-01 00:00:05.000 | 1 |
| 2019-01-01 00:01:35.000 | 2 |
| 2019-01-01 00:02:05.000 | 3 |
| 2019-01-01 00:03:05.000 | 4 |
| 2019-01-01 00:04:05.000 | 5 |
| 2019-01-01 00:05:05.000 | 4 |
| 2019-01-01 00:06:05.000 | 3 |
| 2019-01-01 00:07:05.000 | 2 |
| 2019-01-01 00:08:05.000 | 1 |
| 2019-01-01 00:09:05.000 | 0 |
Be advised that the result does not contain the "in-between" dates.
This question was originally tagged for SQL Server 2012, so this answer is for SQL Server.
One method is to generate a list of minutes and then:
with minutes as (
select cast('2019-01-01 00:00:00' as datetime) as mm
union all
select dateadd(minute, 1, minute)
from cte
where mm < '2019-01-01 00:00:05'
)
select m.*,
(select count(*)
from entrytable et
where et.start_time <= m.mm and
et.end_time > m.mm
) as num_actives
from minutes m;

Deriving continuous history from overlapping from/to periods, with gaps [duplicate]

I have a table with 200.000 rows in a SQL Server 2014 database looking like this:
CREATE TABLE DateRanges
(
Contract VARCHAR(8),
Sector VARCHAR(8),
StartDate DATE,
EndDate DATE
);
INSERT INTO DateRanges (Contract, Sector, StartDate, Enddate)
SELECT '111', '999', '01-01-2014', '03-31-2014'
union
SELECT '111', '999', '04-01-2014', '06-30-2014'
union
SELECT '111', '999', '07-01-2014', '09-30-2014'
union
SELECT '111', '999', '10-01-2014', '12-31-2014'
union
SELECT '111', '888', '08-01-2014', '08-31-2014'
union
SELECT '111', '777', '08-15-2014', '08-31-2014'
union
SELECT '222', '999', '01-01-2014', '03-31-2014'
union
SELECT '222', '999', '04-01-2014', '06-30-2014'
union
SELECT '222', '999', '07-01-2014', '09-30-2014'
union
SELECT '222', '999', '10-01-2014', '12-31-2014'
union
SELECT '222', '666', '11-01-2014', '11-30-2014'
UNION
SELECT '222', '555', '11-15-2014', '11-30-2014';
As you can see there can be multiple overlaps for each contract and what I would like to have is the result like this
Contract Sector StartDate EndDate
---------------------------------------------
111 999 01-01-2014 07-31-2014
111 888 08-01-2014 08-14-2014
111 777 08-15-2014 08-31-2014
111 999 09-01-2014 12-31-2014
222 999 01-01-2014 10-31-2014
222 666 11-01-2014 11-14-2014
222 555 11-15-2014 11-30-2014
222 999 12-01-2014 12-31-2014
I can not figure out how this can be done and the examples i have seen on this site quite do not fit my problem.
This answer makes use of a few different techniques. The first is a recursive-cte that creates a table with every relevant cal_date which then gets cross apply'd with unique Contract values to get every combination of both values. The second is window-functions such as lag and row_number to determine a variety of things detailed in the comments below. Lastly, and probably most importantly, gaps-and-islands to determine when one Contract/Sector combination ends and the next begins.
Answer:
--determine range of dates
declare #bgn_dt date = (select min(StartDate) from DateRanges)
, #end_dt date = (select max(EndDate) from DateRanges)
--use a recursive CTE to create a record for each day / Contract
; with dates as
(
select #bgn_dt as cal_date
union all
select dateadd(d, 1, a.cal_date) as cal_date
from dates as a
where a.cal_date < #end_dt
)
select d.cal_date
, c.Contract
into #contract_dates
from dates as d
cross apply (select distinct Contract from DateRanges) as c
option (maxrecursion 0)
--Final Select
select f.Contract
, f.Sector
, min(f.cal_date) as StartDate
, max(f.cal_date) as EndDate
from (
--Use the sum-over to obtain the Island Numbers
select dr.Contract
, dr.Sector
, dr.cal_date
, sum(dr.IslandBegin) over (partition by dr.Contract order by dr.cal_date asc) as IslandNbr
from (
--Determine if the record is the start of a new Island
select a.Contract
, a.Sector
, a.cal_date
, case when lag(a.Sector, 1, NULL) over (partition by a.Contract order by a.cal_date asc) = a.Sector then 0 else 1 end as IslandBegin
from (
--Determine which Contract/Date combinations are valid, and rank the Sectors that are in effect
select cd.cal_date
, dr.Contract
, dr.Sector
, dr.EndDate
, row_number() over (partition by dr.Contract, cd.cal_date order by dr.StartDate desc) as ConractSectorRnk
from #contract_dates as cd
left join DateRanges as dr on cd.Contract = dr.Contract
and cd.cal_date between dr.StartDate and dr.EndDate
) as a
where a.ConractSectorRnk = 1
and a.Contract is not null
) as dr
) as f
group by f.Contract
, f.Sector
, f.IslandNbr
order by f.Contract asc
, min(f.cal_date) asc
Output:
+----------+--------+------------+------------+
| Contract | Sector | StartDate | EndDate |
+----------+--------+------------+------------+
| 111 | 999 | 2014-01-01 | 2014-07-31 |
| 111 | 888 | 2014-08-01 | 2014-08-14 |
| 111 | 777 | 2014-08-15 | 2014-08-31 |
| 111 | 999 | 2014-09-01 | 2014-12-31 |
| 222 | 999 | 2014-01-01 | 2014-10-31 |
| 222 | 666 | 2014-11-01 | 2014-11-14 |
| 222 | 555 | 2014-11-15 | 2014-11-30 |
| 222 | 999 | 2014-12-01 | 2014-12-31 |
+----------+--------+------------+------------+

modify output from 1 query

need Your suggestion Guy's. I don't know what the title of my question. but I has 1 query which give an ouput like this picture :
and this is my query :
select to_char(aa.DATE_AWAL, 'dd/mm/yyyy hh24:mi') DATE_AWAL, to_char(aa.DATE_AKHIR, 'dd/mm/yyyy hh24:mi') DATE_AKHIR,
to_char(aa.DATE_AWAL, 'hh24:mi') TIME_AWAL, to_char(aa.DATE_AKHIR, 'hh24:mi') TIME_AKHIR,
cc.NAMARUANG,aa.IDMEETING from TMEETING_ROOM aa
inner join MMEETING_TYPE bb on aa.IDTYPE=bb.IDMEETING
inner join MMEETING_ROOM cc on aa.IDMEETINGROOM = cc.IDMEETINGROOM
inner join HR.VWKARYAWAN dd on aa.IDPENGUSUL=dd.IDKARYAWAN
inner join HR.MLOKASI ee on aa.IDLOKASI = ee.IDLOKASI
where aa.IS_DELETE IS NULL
and aa.IDCANCEL IS NULL
and (
wm_overlaps (
wm_period(aa.DATE_AWAL, aa.DATE_AKHIR),
wm_period(
TO_DATE(TO_CHAR(trunc(sysdate) + 08/24, 'yyyy-mm-dd hh24:mi'), 'yyyy-mm-dd hh24:mi'),
TO_DATE(TO_CHAR(trunc(sysdate) + 23/24, 'yyyy-mm-dd hh24:mi'), 'yyyy-mm-dd hh24:mi')
)
) = 1
) and aa.idlokasi = 'I' order by cc.NAMARUANG asc, aa.DATE_AWAL asc;
Can any body give me suggestion how to make from this query can like this picture:
I'm newbie using oracle SQL
Note: the time and room are dynamic.
Here is an example of how you might achieve a "generic" pivot table in MySQL.
The technique used requires row numbering (and in v8 of MySQL there will be an easier way to do this) but for now it requires using #variables.
Then, with each row number, we "transform" rows to columns using case expressions inside he max() function (conditional aggregates).
You will need to decide how many columns you need, and note that the order by inside the subquery t is vital to successfully arranging the data.
SQL Fiddle
MySQL 5.6 Schema Setup:
CREATE TABLE YourQueryHere
(`id` int, `date_column` datetime, `code_column` varchar(7), `data_for_cells1` varchar(5), `data_for_cells2` varchar(5))
;
INSERT INTO YourQueryHere
(`id`, `date_column`, `code_column`, `data_for_cells1`, `data_for_cells2`)
VALUES
(1, '2017-11-14 00:00:00', 'Bintang', '09:00', '10:30'),
(2, '2017-11-14 00:00:00', 'Bintang', '11:00', '12:30'),
(3, '2017-11-14 00:00:00', 'Bintang', '14:00', '17:00'),
(4, '2017-11-14 00:00:00', 'Sapporo', '11:30', '14:00'),
(5, '2017-11-14 00:00:00', 'Sapporo', '14:30', '15:00'),
(6, '2017-11-14 00:00:00', 'Tiger', '08:00', '09:30'),
(7, '2017-11-14 00:00:00', 'Tiger', '11:00', '12:00')
;
Query 1:
select
code_column
, max(case when RowNumber = 1 then concat(data_for_cells1, ' ', data_for_cells2) end) as pivcol1
, max(case when RowNumber = 2 then concat(data_for_cells1, ' ', data_for_cells2) end) as pivcol2
, max(case when RowNumber = 3 then concat(data_for_cells1, ' ', data_for_cells2) end) as pivcol3
, max(case when RowNumber = 4 then concat(data_for_cells1, ' ', data_for_cells2) end) as pivcol4
from (
select *
, #counter :=IF(#prev=code_column,#counter+1,1)AS RowNumber
, #prev := code_column
from YourQueryHere
cross join (select #counter:=0, #prev:= '') vars
order by
code_column, date_column
) t
group by
code_column
order by
code_column
;
Results:
| code_column | pivcol1 | pivcol2 | pivcol3 | pivcol4 |
|-------------|-------------|-------------|-------------|---------|
| Bintang | 09:00 10:30 | 11:00 12:30 | 14:00 17:00 | (null) |
| Sapporo | 11:30 14:00 | 14:30 15:00 | (null) | (null) |
| Tiger | 08:00 09:30 | 11:00 12:00 | (null) | (null) |

Taking values from other rows matching by date

I have one problem with SQL query. I have the following table in SQL server 2014.
declare #t table (STORE_ID int, INDEX_ID int, START_DATE datetime,
END_DATE datetime,
GROSS_SALES_PRICE decimal(10,2),
NET_SALES_PRICE INT
);
insert into #t
values (3,22,'2014-08-01 00:00:00.000', '2014-09-30 23:59:59.000', 29.99,25),
(3,22,'2014-10-01 00:00:00.000', '2014-12-31 23:59:59.000', NULL,NULL),
(3,22,'2015-01-01 00:00:00.000', '2015-09-30 23:59:59.000', 39.99,28),
(4,22,'2016-01-01 00:00:00.000', '2016-07-31 23:59:59.000', 29.99,25),
(4,22,'2016-08-01 00:00:00.000', '2016-12-31 23:59:59.000', NULL,NULL),
(4,22,'2017-01-01 00:00:00.000', '2018-09-30 23:59:59.000', 39.99,28),
(1,22,'2014-08-30 00:00:00.000', '2014-10-31 23:59:59.000', 39.99,28),
(1,22,'2014-11-01 00:00:00.000', '2016-09-30 23:59:59.000', 20.99,15)
As you can see there are some NULLS in GROSS_SALES_PRICEand NET_SALES_PRICE in some rows. The case is to take this prices from the rows where STORE_ID equals 1. For example if you have NULL values in second row you can take prices from this peroid from Store number 1. But in this peroid you have 2 diferent prices. So you have to split that NULL line into two lines and take 2 diferent prices. If there is no maching part of the peroid the part of the row should be left as NULL. All dates are separable. The result should look like this.
declare #t2 table (STORE_ID int, INDEX_ID int, START_DATE datetime,
END_DATE datetime,
GROSS_SALES_PRICE decimal(10,2),
NET_SALES_PRICE INT
);
insert into #t2
values (3,22,'2014-08-01 00:00:00.000', '2014-09-30 23:59:59.000', 29.99,25),
(3,22,'2014-10-01 00:00:00.000', '2014-10-31 23:59:59.000', 39.99,28),
(3,22,'2014-11-01 00:00:00.000', '2014-12-31 23:59:59.000', 20.99,15),
(3,22,'2015-01-01 00:00:00.000', '2015-09-30 23:59:59.000', 39.99,28),
(4,22,'2016-01-01 00:00:00.000', '2016-07-31 23:59:59.000', 29.99,25),
(4,22,'2016-08-01 00:00:00.000', '2016-09-30 23:59:59.000', 20.99,15),
(4,22,'2016-10-01 00:00:00.000', '2016-12-31 23:59:59.000', NULL,NULL),
(4,22,'2017-01-01 00:00:00.000', '2018-09-30 23:59:59.000', 39.99,28),
(1,22,'2014-08-30 00:00:00.000', '2014-10-31 23:59:59.000', 39.99,28),
(1,22,'2014-11-01 00:00:00.000', '2016-09-30 23:59:59.000', 20.99,15)
A calendar/dates table can simplify this, but we can also use a query to generate a temporary dates table using a common table expression.
Joining to the prices for each index_id from store_id 1 cross joined with a dates table lets us join and aggregate to get the prices for the missing values. Then using union all to return the rows with a price, and rows where we tried to fill in the price:
/* -- dates --*/
declare #fromdate datetime, #thrudate datetime;
select #fromdate = min(start_date), #thrudate = max(end_date) from #t;
;with n as (select n from (values(0),(1),(2),(3),(4),(5),(6),(7),(8),(9)) t(n))
, dates as (
select top (datediff(day, #fromdate, #thrudate)+1)
[Date]=convert(datetime,dateadd(day,row_number() over(order by (select 1))-1,#fromdate))
, [End_Date]=convert(datetime,dateadd(second,-1,dateadd(day,row_number() over(order by (select 1)),#fromdate)))
from n as deka cross join n as hecto cross join n as kilo
cross join n as tenK cross join n as hundredK
order by [Date]
)
/* -- default price -- */
, cte as (
select
d.index_id
, start_date = d.date
, end_date = d.end_date
, t.gross_sales_price
, t.net_sales_price
from (
select dates.*
, i.index_id
from dates
cross join (select distinct index_id from #t) i
) d
left join (select * from #t where store_id = 1) t
on d.date >= t.start_date
and d.date <= t.end_date
and d.index_id = t.index_id
)
/* -- rows with a price */
select
t.store_id
, t.index_id
, start_date
, end_date
, gross_sales_price
, net_sales_price
from #t t
where t.Gross_Sales_Price is not null
union all
/* -- rows with with a null price */
select
t.store_id
, t.index_id
, start_date = min(d.start_date)
, end_date = max(d.end_date)
, gross_sales_price = d.gross_sales_price
, net_sales_price = d.net_sales_price
from #t t
left join cte d
on t.index_id = d.index_id
and d.start_date >= t.start_date
and d.end_date <= t.end_date
where t.Gross_Sales_Price is null
group by
t.store_id, t.index_id, d.gross_sales_price, d.net_sales_price
order by store_id, index_id
rextester demo: http://rextester.com/QXDNF59094
returns:
+----------+----------+-------------------------+-------------------------+-------------------+-----------------+
| store_id | index_id | start_date | end_date | gross_sales_price | net_sales_price |
+----------+----------+-------------------------+-------------------------+-------------------+-----------------+
| 1 | 22 | 2014-08-30 00:00:00.000 | 2014-10-31 23:59:59.000 | 39.99 | 28 |
| 1 | 22 | 2014-11-01 00:00:00.000 | 2016-09-30 23:59:59.000 | 20.99 | 15 |
| 3 | 22 | 2014-11-01 00:00:00.000 | 2014-12-30 23:59:59.000 | 20.99 | 15 |
| 3 | 22 | 2014-10-01 00:00:00.000 | 2014-10-31 23:59:59.000 | 39.99 | 28 |
| 3 | 22 | 2014-08-01 00:00:00.000 | 2014-09-30 23:59:59.000 | 29.99 | 25 |
| 3 | 22 | 2015-01-01 00:00:00.000 | 2015-09-30 23:59:59.000 | 39.99 | 28 |
| 4 | 22 | 2016-01-01 00:00:00.000 | 2016-07-31 23:59:59.000 | 29.99 | 25 |
| 4 | 22 | 2017-01-01 00:00:00.000 | 2018-09-30 23:59:59.000 | 39.99 | 28 |
| 4 | 22 | 2016-10-01 00:00:00.000 | 2016-12-30 23:59:59.000 | NULL | NULL |
| 4 | 22 | 2016-08-01 00:00:00.000 | 2016-09-30 23:59:59.000 | 20.99 | 15 |
+----------+----------+-------------------------+-------------------------+-------------------+-----------------+
calendar and numbers tables reference:
Generate a set or sequence without loops 2- Aaron Bertrand
Creating a Date Table/Dimension in SQL Server 2008 - David Stein
Calendar Tables - Why You Need One - David Stein
Creating a date dimension or calendar table in SQL Server - Aaron Bertrand