Query to remove continuous eligibility records - sql

I need to get rid of the records whose eligibility already exists in another record of the memid. In the below example I need the output with only rows where I have mentioned Y. Table has memid,effdate,termdate. I have just prefixed Y to mention the record I need as output. How can we do this.Thanks.
MEMID EFFDATE TERMDATE
Y A1 2012-01-01 2078-12-31
A1 2012-02-01 2078-12-31
Y B1 2007-05-01 2008-12-31
Y B1 2009-10-01 2010-04-30
Y A2 1999-01-01 2078-12-31
A2 2006-01-01 2011-04-28
B2 1999-01-01 1999-10-01
Y B2 1999-01-01 2000-09-30
Y B2 2006-01-01 2006-01-01
Y B2 2009-08-01 2078-12-31
Y A3 2000-03-01 2009-01-31
A3 2002-04-01 2009-01-31
A3 2003-01-01 2006-06-30
A3 2006-01-01 2009-01-31
Y A3 2009-10-01 2010-07-31
Y A3 2011-06-01 2012-09-30
A3 2011-09-01 2012-09-30
Y A3 2013-06-01 2078-12-31
A3 2013-07-01 2078-12-31
B3 1999-01-01 2008-11-30
Y B3 1999-01-01 2078-12-31
B3 2006-01-01 2008-11-30

select all ranges where there is no covering bigger range with NOT EXISTS. Then remove duplicates with DISTINCT.
select distinct memid, effdate, termdate
from mytable
where not exists
(
select *
from mytable bigger
where bigger.memid = mytable.memid
and
(
(bigger.effdate <= mytable.effdate and bigger.termdate > mytable.termdate)
or
(bigger.effdate < mytable.effdate and bigger.termdate >= mytable.termdate)
)
);

This is a variation of the Gaps and Islands problem. Since you have tagged both MySQL and SQL-Server, and have not responded to my question asking you to clarify which, I will give a solution for both.
Your first step is expand all your ranges into continuous data by joining with a numbers table. This will turn a single row into one row for every date in the range:
SQL Server
SELECT t.memid, Date = DATEADD(DAY, n.Number, t.EffDate)
FROM YourTable t
INNER JOIN Numbers n
ON n.Number BETWEEN 0 AND DATEDIFF(DAY, t.EffDate, t.TermDate);
MySQL
SELECT t.memid, DATE_ADD(t.EffDate, INTERVAL n.Number DAY) AS Date
FROM YourTable t
INNER JOIN Numbers n
ON n.Number BETWEEN 0 AND DATEDIFF(t.EffDate, t.TermDate);
This will turn this row:
memid EFFDATE TERMDATE
A3 2009-10-01 2009-10-05
Into
memid Date
A3 2009-10-01
A3 2009-10-02
A3 2009-10-03
A3 2009-10-04
A3 2009-10-05
If you don't have a numbers table, then you should probably create one. (In each of the SQL Fiddle's below I have created a numbers table, so you can find ways of doing so there.
Now you have your continuous ranges you can apply the appropriate gaps and islands solution.
If you are using SQL Server then you can use ranking functions to resolve this:
WITH ContinuousRange AS
( SELECT t.memid,
d.Date,
GroupingSet = DATEADD(DAY, -DENSE_RANK() OVER(PARTITION BY memid
ORDER BY d.Date), d.Date)
FROM T
INNER JOIN Numbers n
ON n.Number BETWEEN 0 AND DATEDIFF(DAY, t.EffDate, t.TermDate)
OUTER APPLY (SELECT Date = DATEADD(DAY, n.Number, t.EffDate)) d
)
SELECT cr.MemID,
EffDate = MIN(cr.Date),
TermDate = MAX(cr.Date)
FROM ContinuousRange cr
GROUP BY cr.MemID, cr.GroupingSet
ORDER BY cr.MemID, cr.GroupingSet;
Simplified Example on SQL Fiddle
This works on the basis that end number in a sequence minus it's order in the sequence will give a constant for a continuous range, e.g.:
Sequence | OrderInSequence | (Sequence - OrderInSequence)
---------+-----------------+------------------------------
1 | 1 | 0
2 | 2 | 0
3 | 3 | 0
5 | 4 | 1
6 | 5 | 1
As you can see, where there is a gap in the sequence (between 3 and 5) the value in the 3rd column changes, this is how the column GroupingSet is calculated:
GroupingSet = DATEADD(DAY, -DENSE_RANK() OVER(PARTITION BY memid ORDER BY d.Date), d.Date)
Then when you can use thisc column to get the minimum and maximum value of each consectutive sequence (or island).
Since MySQL does not have ranking functions you will need to use user defined variables to emulate them:
SELECT MemID,
MIN(Date) AS EffDate,
MAX(Date) AS TermDate
FROM ( SELECT t.memid,
#i:= CASE WHEN t.MemID = #m
AND DATE_ADD(t.EffDate, INTERVAL n.Number DAY)
<= DATE_ADD(#d, INTERVAL 1 DAY)
THEN #i
ELSE #i + 1
END AS GroupingSet,
#m:= t.memid,
#d:= DATE_ADD(t.EffDate, INTERVAL n.Number DAY) AS Date
FROM t
INNER JOIN Numbers n
ON n.Number BETWEEN 0 AND DATEDIFF(t.TermDate, t.EffDate)
CROSS JOIN (SELECT #M:= '', #i:= 0, #d:= NULL) i
ORDER BY t.memid, DATE_ADD(t.EffDate, INTERVAL n.Number DAY)
) t
GROUP BY MemID, GroupingSet;
Simplified Example on SQL Fiddle
Getting the Grouping Set here is a more iterative process. The data is ordered by MemID and the date, then at each row the value of the date and the memid is stored in the variables #d and #m respectively. If the memid in the new row is the same as #m (i.e still in the same group of memid's) and the date in the new row is 1 day ahead of, or the same as #d then the grouping set does not increment, if it is a new memid, or the date is further than 1 day ahead of the previous date, then it is a new 'island' and the grouping set increments.
EDIT
To help with memory issues then you can deal with different scenarios differently. The first step is to remove any records contained entirely within another, e.g.
MemID EffDate TermDate
A1 2012-01-01 2078-12-31
A1 2012-02-01 2078-12-31
With these two, the second row is not required since its date range is contained entirely within the first. So this can be removed (This is done in the CTE called Filtered in the below query).
The second way to help is to remove the range expansion where it is not required, so with the above example we are just left with:
MemID EffDate TermDate
A1 2012-01-01 2078-12-31
And this is the only row for A1, it is therefore not necessary to expand this out into all the days between the EffDate and TermDate, and then take a minimum and maximum, we can simply use the EffDate and TermDate as they are. This is the query below the UNION ALL in the query below.
WITH Filtered AS
( SELECT MemID, EffDate, TermDate
FROM T
WHERE NOT EXISTS
( SELECT 1
FROM T T2
WHERE T.MemID = T2.MemID
AND T2.EffDate < T.EffDate
AND T2.TermDate >= T.TermDate
)
), ContinuousRange AS
( SELECT t.memid,
d.Date,
GroupingSet = DATEADD(DAY, -DENSE_RANK() OVER(PARTITION BY memid
ORDER BY d.Date), d.Date)
FROM Filtered T
INNER JOIN Numbers n
ON n.Number BETWEEN 0 AND DATEDIFF(DAY, t.EffDate, t.TermDate)
OUTER APPLY (SELECT Date = DATEADD(DAY, n.Number, t.EffDate)) d
WHERE EXISTS
( SELECT 1
FROM Filtered T2
WHERE T.MemID = T2.MemID
AND ( (T2.EffDate > T.EffDate AND T2.EffDate < T.TermDate)
OR (T2.TermDate > T.EffDate AND T2.TermDate < T.TermDate)
)
)
)
SELECT cr.MemID,
EffDate = MIN(cr.Date),
TermDate = MAX(cr.Date), 1
FROM ContinuousRange cr
GROUP BY cr.MemID, cr.GroupingSet
UNION ALL
SELECT T.MemID, T.EffDate, T.TermDate, 0
FROM Filtered T
WHERE NOT EXISTS
( SELECT 1
FROM Filtered T2
WHERE T.MemID = T2.MemID
AND ( (T2.EffDate > T.EffDate AND T2.EffDate < T.TermDate)
OR (T2.TermDate > T.EffDate AND T2.TermDate < T.TermDate)
)
)
ORDER BY MemID, EffDate;
Example on SQL Fiddle

Related

Create membership timeseries from static table

I have a table (members) which shows users and any additions or deletions to a specific membership and on what date as below
User
Membership
Change
Date
1
100
A
01/01/1900
1
101
A
01/01/1990
1
100
D
01/01/2000
2
100
A
01/12/1990
2
101
A
01/01/1991
2
101
D
01/12/1991
2
100
D
01/01/1993
3
100
A
01/01/2000
I'm looking to find cases where one user is in multiple memberships in overlapping time periods, I'm using the below but it's pulling the wrong end dates when I run it
With membership As
(Select user, membership, date as Start_Date,
LEAD (date, 1, '31/12/9999') OVER (PARTITION BY membership ORDER BY
date) AS End_Date
FROM
(Select *, LAG(change,1,-1) OVER(PARTITION BY membership ORDER BY
date) AS Previous_change
From members) withprevious
Where change != previous_change),
MemberTimeSeries AS
(Select *
From membership
Where Start_Date IN
(Select a.Date
From members a
join membership b
on a.user = b.user
and a.membership = b.membership
Where a.change = 'A')),
DupeIDs AS
(Select Distinct a.user, a.membership, cast(a.start_date as date_ as
start_date, cast(a.end_date as date) as end_date
from membertimeseries a
join membertimeseries b
on a.user = b.user
and ((a.start_date >= b.start_date abd a.start_date < b.end_date)
Or (a.end_date > b.start_date and a.end_date <= b.end_date)
OR (b.start_date >= a.start_date abd b.start_date < a.end_date)
Or (b.end_date > a.start_date and b.end_date <= a.end_date)
I'm looking to see any user and membership combinations with respective start and end dates where it overlaps with another membership combination for the same user during any period of it's active membership. If there is no deletion in the table I want date to default to 31/12/9999
For example I want to see the below from my example
User
Membership
Start_Date
End_Date
1
100
01/01/1900
01/01/2000
1
101
01/01/1990
31/12/9999
2
100
01/12/1990
01/01/1993
2
101
01/01/1991
01/12/1991
After reviewing at your updated code, I decided it might be better to take a different approach.
You can combine the add and drop rows into ranges by first selecting the adds and then using an OUTER APPLY to look up the first following drop row with the same user and membership (if one exists).
If the above is wrapped up in a Common Table Expression (CTE), overlaps can then be identified by joining the ranges with themselves and limiting the join to those with the same user, overlapping date ranges, and excluding same row joins.
A standard test for overlapping date ranges is Start1 < End2 AND Start2 < End1. For exclusive end-dates (as appears to be the case here) < is used. If the ranges were defined with inclusive end dates <= would be used. Omitted end dates can be handled with an end-of-time default ISNULL(EndDate, '9999-12-31')'
To omit self matches, the following uses ROW_NUMBER() to assign distinct row IDs. You could alternately check for "not (all values equal)".
;WITH Ranges AS (
SELECT
A.[User], A.Membership, A.Date AS StartDate, D.Date AS EndDate,
ROW_NUMBER() OVER(ORDER BY (SELECT NULL)) AS RowId
FROM Memberships A
OUTER APPLY (
SELECT TOP 1 D.*
FROM Memberships D
WHERE D.Change = 'D'
AND D.[User] = A.[User]
AND D.Membership = A.Membership
AND D.Date > A.Date
ORDER BY D.Date
) D
WHERE A.Change = 'A'
)
SELECT DISTINCT R.[User], R.Membership, R.StartDate, R.EndDate
FROM Ranges R
JOIN Ranges R2
ON R2.[User] = R.[User]
AND R2.RowId <> R.RowId
AND R2.StartDate < ISNULL(R.EndDate, '2099-12-31')
AND R.StartDate < ISNULL(R2.EndDate, '2099-12-31')
ORDER BY R.[User], R.StartDate
In case of multiple overlaps, DISTINCT eliminates dups. An alternative would be to replace the JOIN with a WHERE EXISTS(...).
Results:
User
Membership
StartDate
EndDate
1
100
1900-01-01
2000-01-01
1
101
1990-01-01
null
2
100
1990-12-01
1993-01-01
2
101
1991-01-01
1991-12-01
5
100
2020-01-01
2020-02-01
5
102
2020-01-15
2020-02-15
5
101
2020-02-01
2020-03-01
See this db<>fiddle.

Create months between two dates Snowflake SQL

I just want to generate the months between data range using SQL Query.
example
You can use a table generator:
select '2022-07-04'::date +
row_number() over(partition by 1 order by null) - 1 GENERATED_DATE
from table(generator(rowcount => 365))
;
Just change the start date and the number of days into the series. You can use the datediff function to calculate the number of days between the start end end dates.
Edit: I just realized the generator table function requires a constant for the number of rows. That's easily solvable. Just set a higher number of rows than you'll need and specify the end of the series in a qualify clause:
set startdate = (select '2022-04-15'::date);
set enddate = (select '2022-07-04'::date);
select $startdate::date +
row_number() over(partition by 1 order by null) - 1 GENERATED_DATE
from table(generator(rowcount => 100000))
qualify GENERATED_DATE <= $enddate
;
You can use a table generator in the CTE, and then select from the CTE and cartesian join to your table with data and use a case statement to see if the date in the generator is between your start and to dates.
Then select from it:
select user_id, x_date
from (
with dates as (
select '2019-01-01'::date + row_number() over(order by 0) x_date
from table(generator(rowcount => 1500))
)
select d.x_date, t.*,
case
when d.x_date between t.from_date and t.to_date then 'Y' else 'N' end target_date
from dates d, my_table t --deliberate cartesian join
)
where target_date = 'Y'
order by 1,2
Output:
USER_ID X_DATE
1 2/20/2019
1 2/21/2019
1 2/22/2019
1 2/23/2019
2 2/22/2019
2 2/23/2019
2 2/24/2019
2 2/25/2019
2 2/26/2019
2 2/27/2019
2 2/28/2019
3 3/1/2019
3 3/2/2019
3 3/3/2019
3 3/4/2019
3 3/5/2019
=======EDIT========
Based on your comments below, you are actually looking for something different than your original screenshots. Ok, so here we are still using the table generator, and then we're truncating the month to the first day of the month where the x-date is YES.
select distinct t.user_id, t.from_date, t.to_date, date_trunc('MONTH', z.x_date) as trunc_month
from (
with dates as (
select '2019-01-01'::date + row_number() over(order by 0) x_date
from table(generator(rowcount => 1500))
)
select d.x_date, t.*,
case
when d.x_date between t.from_date and t.to_date then 'Y' else 'N' end target_date
from dates d, my_table t
)z
join my_table t
on z.user_id = t.user_id
where z.target_date = 'Y'
order by 1,2
Output (modified User ID 3 to span 2 months):
USER_ID FROM_DATE TO_DATE TRUNC_MONTH
1 2/20/2019 2/23/2019 2/1/2019
2 2/22/2019 2/28/2019 2/1/2019
3 2/25/2019 3/5/2019 2/1/2019
3 2/25/2019 3/5/2019 3/1/2019

SQL Server : Gap / Island, datetime, contiguous block 365 day block

I have a table that looks like this:-
tblMeterReadings
id meter period_start period_end amount
1 1 2014-01-01 00:00 2014-01-01 00:29:59 100.3
2 1 2014-01-01 00:30 2014-01-01 00:59:59 50.5
3 1 2014-01-01 01:00 2014-01-01 01:29:59 70.7
4 1 2014-01-01 01:30 2014-01-01 01:59:59 900.1
5 1 2014-01-01 02:00 2014-01-01 02:29:59 400.0
6 1 2014-01-01 02:30 2014-01-01 02:59:59 200.3
7 1 2014-01-01 03:00 2014-01-01 03:29:59 100.8
8 1 2014-01-01 03:30 2014-01-01 03:59:59 140.3
This is a tiny "contiguous block" from '2014-01-01 00:00' to '2014-01-01 3:59:59'.
In the real table there are "contiguous blocks" of years in length.
I need to find the the period_start and period_end of the most recent CONTINUOUS 365 COMPLETE DAYs (fileterd by meter column).
When I say COMPLETE DAYs I mean a day that has entries spanning 00:00 to 23:59.
When I say CONTINUOUS I mean there must be no days missing.
I would like to select all the rows that make up this block of CONTINUOUS COMPLETE DAYs.
I also need an output like:
block_start block_end total_amount_for_block
2013-02-26 00:00 2014-02-26 23:59:59 1034234.5
This is beyond me, so if someone can solve... I will be very impressed.
Since your granularity is 1 second, you need to expand your periods into all the date/times between the start and end at 1 second intervals. To do this you need to cross join with a numbers table (The numbers table is generated on the fly by ranking object ids from an arbitrary system view, I have limited it to TOP 86400 since this is the number of seconds in a day, and you have stated your time periods never span more than one day):
WITH Numbers AS
( SELECT TOP (86400)
Number = ROW_NUMBER() OVER(ORDER BY a.object_id) - 1
FROM sys.all_objects a
CROSS JOIN sys.all_objects b
ORDER BY a.object_id
)
SELECT r.ID, r.meter, dt.[DateTime]
FROM tblMeterReadings r
CROSS JOIN Numbers n
OUTER APPLY
( SELECT [DateTime] = DATEADD(SECOND, n.Number, r.period_start)
) dt
WHERE dt.[DateTime] <= r.Period_End;
You then have your continuous range in which to perform the normal gaps and islands grouping:
WITH Numbers AS
( SELECT TOP (86400)
Number = ROW_NUMBER() OVER(ORDER BY a.object_id) - 1
FROM sys.all_objects a
CROSS JOIN sys.all_objects b
ORDER BY a.object_id
), Grouped AS
( SELECT r.meter,
Amount = CASE WHEN Number = 1 THEN r.Amount ELSE 0 END,
dt.[DateTime],
GroupingSet = DATEADD(SECOND,
-DENSE_RANK() OVER(PARTITION BY r.Meter
ORDER BY dt.[DateTime]),
dt.[DateTime])
FROM tblMeterReadings r
CROSS JOIN Numbers n
OUTER APPLY
( SELECT [DateTime] = DATEADD(SECOND, n.Number, r.period_start)
) dt
WHERE dt.[DateTime] <= r.Period_End
)
SELECT meter,
PeriodStart = MIN([DateTime]),
PeriodEnd = MAX([DateTime]),
Amount = SUM(Amount)
FROM Grouped
GROUP BY meter, GroupingSet
HAVING DATEADD(YEAR, 1, MIN([DateTime])) < MAX([DateTime]);
N.B. Since the join to Number causes amounts to be duplicated, it is necessary to set all duplicates to 0 using CASE WHEN Number = 1 THEN r.Amount ELSE 0 END, i.e only include the amount for the first row for each ID
Removing the Having clause for your sample data will give:
meter | PeriodStart | PeriodEnd | Amount
------+---------------------+---------------------+----------
1 | 2014-01-01 00:00:00 | 2014-01-01 03:59:59 | 1963
Example on SQL Fiddle
You could try this:
Select MIN(period_start) as "block start"
, MAX(period_end) as "block end"
, SUM(amount) as "total amount"
FROM YourTable
GROUP BY datepart(year, period_start)
, datepart(month, period_start)
, datepart(day, period_start)
, datepart(year, period_end)
, datepart(month, period_end)
, datepart(day, period_end)
Having datepart(year, period_start) = datepart(year, period_end)
AND datepart(month, period_start) = datepart(month, period_end)
AND datepart(day, period_start) = datepart(day, period_end)
AND datepart(hour, MIN(period_start)) = 0
AND datepart(minute,MIN(period_start)) = 0
AND datepart(hour, MAX(period_end)) = 23
AND datepart(minute,MIN(period_end)) = 59

Adding a Date column based on the next row date value

Im using SQL Server 2005. From the tbl_temp table below, I would like to add an EndDate column based on the next row's StartDate minus 1 day until there's a change in AID and UID combination. This calculated EndDate will go to the row above it as the EndDate. The last row of the group of AID and UID will get the system date as its EndDate. The table has to be ordered by AID, UID, StartDate sequence. Thanks for the help.
-- tbl_temp
AID UID StartDate
1 1 2013-02-20
2 1 2013-02-06
1 1 2013-02-21
1 1 2013-02-27
1 2 2013-02-02
1 2 2013-02-04
-- Result needed
AID UID StartDate EndDate
1 1 2013-02-20 2013-02-20
1 1 2013-02-21 2013-02-26
1 1 2013-02-27 sysdate
1 2 2013-02-02 2013-02-03
1 2 2013-02-04 sysdate
2 1 2013-02-06 sysdate
The easiest way to do this is with a correlated subquery:
select t.*,
(select top 1 dateadd(day, -1, startDate )
from tbl_temp t2
where t2.aid = t.aid and
t2.uid = t.uid and
t2.startdate > t.startdate
) as endDate
from tbl_temp t
To get the current date, use isnull():
select t.*,
isnull((select top 1 dateadd(day, -1, startDate )
from tbl_temp t2
where t2.aid = t.aid and
t2.uid = t.uid and
t2.startdate > t.startdate
), getdate()
) as endDate
from tbl_temp t
Normally, I would recommend coalesce() over isnull(). However, there is a bug in some versions of SQL Server where it evaluates the first argument twice. Normally, this doesn't make a difference, but with a subquery it does.
And finally, the use of sysdate makes me think of Oracle. The same approach will work there too.
;WITH x AS
(
SELECT AID, UID, StartDate,
ROW_NUMBER() OVER(PARTITION BY AID, UID ORDER BY StartDate) AS rn
FROM tbl_temp
)
SELECT x1.AID, x1.UID, x1.StartDate,
COALESCE(DATEADD(day,-1,x2.StartDate), CAST(getdate() AS date)) AS EndDate
FROM x x1
LEFT OUTER JOIN x x2 ON x2.AID = x1.AID AND x2.UID = x1.UID
AND x2.rn = x1.rn + 1
ORDER BY x1.AID, x1.UID, x1.StartDate
SQL Fiddle example

Efficient join with a "correlated" subquery

Given three tables Dates(date aDate, doUse boolean), Days(rangeId int, day int, qty int) and Range(rangeId int, startDate date) in Oracle
I want to join these so that Range is joined with Dates from aDate = startDate where doUse = 1 whith each day in Days.
Given a single range it might be done something like this
SELECT rangeId, aDate, CASE WHEN doUse = 1 THEN qty ELSE 0 END AS qty
FROM (
SELECT aDate, doUse, SUM(doUse) OVER (ORDER BY aDate) day
FROM Dates
WHERE aDate >= :startDAte
) INNER JOIN (
SELECT rangeId, day,qty
FROM Days
WHERE rangeId = :rangeId
) USING (day)
ORDER BY day ASC
What I want to do is make query for all ranges in Range, not just one.
The problem is that the join value "day" is dependent on the range startDate to be calculated, wich gives me some trouble in formulating a query.
Keep in mind that the Dates table is pretty huge so I would like to avoid calculating the day value from the first date in the table, while each Range Days shouldn't be more than a 100 days or so.
Edit: Sample data
Dates Days
aDate doUse rangeId day qty
2008-01-01 1 1 1 1
2008-01-02 1 1 2 10
2008-01-03 0 1 3 8
2008-01-04 1 2 1 2
2008-01-05 1 2 2 5
Ranges
rangeId startDate
1 2008-01-02
2 2008-01-03
Result
rangeId aDate qty
1 2008-01-02 1
1 2008-01-03 0
1 2008-01-04 10
1 2008-01-05 8
2 2008-01-03 0
2 2008-01-04 2
2 2008-01-05 5
Try this:
SELECT rt.rangeId, aDate, CASE WHEN doUse = 1 THEN qty ELSE 0 END AS qty
FROM (
SELECT *
FROM (
SELECT r.*, t.*, SUM(doUse) OVER (PARTITION BY rangeId ORDER BY aDate) AS span
FROM (
SELECT r.rangeId, startDate, MAX(day) AS dm
FROM Range r, Days d
WHERE d.rangeid = r.rangeid
GROUP BY
r.rangeId, startDate
) r, Dates t
WHERE t.adate >= startDate
ORDER BY
rangeId, t.adate
)
WHERE
span <= dm
) rt, Days d
WHERE d.rangeId = rt.rangeID
AND d.day = GREATEST(rt.span, 1)
P. S. It seems to me that the only point to keep all these Dates in the database is to get a continuous calendar with holidays marked.
You may generate a calendar of arbitrary length in Oracle using following construction:
SELECT :startDate + ROWNUM
FROM dual
CONNECT BY
1 = 1
WHERE rownum < :length
and keep only holidays in Dates. A simple join will show you which Dates are holidays and which are not.
Ok, so maybe I've found a way. Someting like this:
SELECT irangeId, aDate + sum(case when doUse = 1 then 0 else 1) over (partionBy rangeId order by aDate) as aDate, qty
FROM Days INNER JOIN (
select irangeId, startDate + day - 1 as aDate, qty
from Range inner join Days using (irangeid)
) USING (aDate)
Now I just need a way to fill in the missing dates...
Edit: Nah, this way means that I'll miss the doUse vaue of the last dates...