SQL Case Statement with Subqueries - sql

I am trying to write a query to report on how many times a part was used during a specific month. For example, take February as the reporting month. If P1 was installed on A1 until 2014-02-05 and was used within 2014-02-01 to 2014-02-05 then I would need to Sum that up. Also if P1 was installed on A2 then I would need to sum up the usage up to 2014-02-28.
| part | date_trans | transaction | unit |
---------------------------------------------
p1 2014-02-05 removed A1
p1 2014-02-07 installed A2
| unit | daily usage | date |
A1 3 2014-02-01
A1 2 2014-02-03
A2 2 2014-02-05
A2 4 2014-02-08
A2 2 2014-02-20
Notice for the record
| unit | daily usage | date |
A2 2 2014-02-05
We do not need to take account because P1 was not installed on A2 on 2014-02-05.
Here is my attempt so far..
SELECT
CASE
WHEN h.date_trans between '2014-01-31' and '2014-02-28' and transaction='removed' THEN...
END AS 'test'
FROM history h join acu on(acu.unit=h.unit)
WHERE h.part='P1'

You can try the following query, using CTE's:
with history_installed as
(select part,
case
when date_trans <= '2014-02-01' then '2014-02-01'
else date_trans
end date_trans
from history
where transaction = 'installed' and date_trans < '2014-03-01')
, history_removed as
(select part,
case
when date_trans >= '2014-02-28' then '2014-02-28'
else date_trans
end date_trans
from history
where transaction = 'removed' and date_trans >= '2014-02-01')
select acu.*
from acu
inner join history_installed on history_installed.date_trans <= acu.date
inner join history_removed on history_removed.date_trans >= acu.date

Something like this.
/*
create table #t1 (part char(2), date_trans date, trans char(10), unit char(2))
go
insert #t1 values
('p1', '2014-02-05', 'removed' , 'A1'),
('p1', '2014-02-07' , 'installed', 'A2')
go
create table #t2 (unit char(2), usage int, dt date)
go
insert #t2 values
( 'A1', 3, '2014-02-01'),
( 'A1', 2, '2014-02-03'),
( 'A2', 2, '2014-02-05'),
( 'A2', 4, '2014-02-08'),
( 'A2', 2, '2014-02-20')
*/
declare #min date = '20140201', #max date = '20140228'
;with x as (
select *,
isnull(case trans when 'removed' then lag(date_trans) over(partition by part order by date_trans) else date_trans end, #min) as date_start,
isnull(case trans when 'installed' then lead(date_trans) over(partition by part order by date_trans) else date_trans end, #max) as date_end
from #t1
)
select #t2.unit, x.part, sum(#t2.usage)
from #t2
inner join x on #t2.dt between x.date_start and x.date_end
and x.unit = #t2.unit
group by #t2.unit, x.part
-- drop table #t1, #t2
Here's the query for the older (pre-2012) versions:
;with x as (
select *, row_number() over(partition by part order by date_trans) as rn
from #t1
),
y as (
select x1.*,
case x1.trans when 'removed' then isnull(x2.date_trans, #min) else x1.date_trans end as date_start,
case x1.trans when 'installed' then isnull(x3.date_trans, #max) else x1.date_trans end as date_end
from x x1
left outer join x x2 on x1.rn = x2.rn+1
left outer join x x3 on x1.rn = x3.rn-1
)
select #t2.unit, y.part, sum(#t2.usage)
from #t2
inner join y on #t2.dt between y.date_start and y.date_end
and y.unit = #t2.unit
group by #t2.unit, y.part

Related

Calculate time correctly between 2 date range

I am working with the Datetime column where I am having trouble calculating the Time because of overlapping in time.
This is my sample data
Declare #T table (ID Int, InTime Datetime,OutTime Datetime)
Insert into #T values (1,'2020-08-23 09:26:07.000','2020-08-23 09:57:55.000')
Insert into #T values (1,'2020-08-23 14:09:08.000','2020-08-26 08:13:45.000')
Insert into #T values (1,'2020-08-24 11:14:37.000','2020-08-24 18:07:25.000')
Insert into #T values (1,'2020-08-25 09:19:38.000','2020-08-25 19:19:29.000')
Insert into #T values (1,'2020-08-26 08:13:50.000','2020-08-28 08:39:23.000')
Insert into #T values (1,'2020-08-27 08:42:16.000','2020-08-27 11:38:06.000')
Insert into #T values (1,'2020-08-27 09:51:14.000','2020-08-28 18:23:06.000')
Insert into #T values (1,'2020-08-29 09:51:14.000','2020-08-30 18:23:06.000')
My Expected Output:
+----+-------------------------+--------------------------+
| ID | InTime | OutTime |
+----+-------------------------+--------------------------+
| 1 | 2020-08-23 09:26:07.000 | 2020-08-23 09:57:55.000 |
| 1 | 2020-08-23 14:09:08.000 | 2020-08-26 08:13:45.000 |
| 1 | 2020-08-26 08:13:50.000 | 2020-08-28 08:39:23.000 |
| 1 | 2020-08-29 09:51:14.000 | 2020-08-30 18:23:06.000 |
+----+-------------------------+--------------------------+
If you see the 2nd entry in a table where my Intime is 2020-08-23 14:09:08.000 and outTime is 2020-08-26 08:13:45.000. So if there is any entry in a table between 23 and 26 we should skip that entry. so In out table we need to skip 24th and 25th entry.
Can someone please help me with this query. Any help will be appreciated
I tried this link but couldn't understand the logic Link
This is a classical case of "aggregating intervals". Snodgrass give a classical query to do that :
WITH
T0 AS
(SELECT PRE.id,
PRE.intime AS D1, PRE.outtime AS F1,
DER.intime AS D2, DER.outtime AS F2
FROM #T PRE
INNER JOIN #T DER
ON PRE.intime <= DER.outtime
AND PRE.id = DER.id)
SELECT DISTINCT id, D1 AS intime, F2 AS outtime
FROM T0 AS I
WHERE NOT EXISTS (SELECT *
FROM #T SI1
WHERE (SI1.intime < I.D1
AND I.D1 <= SI1.outtime
AND I.id = SI1.id )
OR (SI1.intime <= I.F2
AND I.F2 < SI1.outtime
AND I.id = SI1.id))
AND NOT EXISTS (SELECT *
FROM #T SI2
WHERE D1 < SI2.intime
AND SI2.intime <= F2
AND I.id = SI2.id
AND NOT EXISTS (SELECT *
FROM #T SI3
WHERE SI3.intime < SI2.intime
AND SI2.intime <= SI3.outtime
AND SI2.id = SI3.id ));
Chris Date give another version :
WITH T
AS (SELECT F.intime, L.outtime, F.id
FROM #T AS F
JOIN #T AS L
ON F.outtime <= L.outtime
AND F.id = L.id
INNER JOIN #T AS E
ON F.id = E.id
GROUP BY F.intime, L.outtime, F.id
HAVING COUNT(CASE
WHEN (E.intime < F.intime AND F.intime <= E.outtime)
OR (E.intime <= L.outtime AND L.outtime < E.outtime)
THEN 1
END) = 0)
SELECT id, intime, MIN(outtime) AS outtime
FROM T
GROUP BY id, intime;
And finally one I wrote :
WITH
T0 AS -- suprime les périodes incluses
(SELECT DISTINCT Tout.id, Tout.intime, Tout.outtime
FROM #T AS Tout
WHERE NOT EXISTS(SELECT *
FROM #T AS Tin
WHERE Tout.intime >= Tin.intime
AND Tout.outtime < Tin.outtime
AND Tout.id = Tin.id)),
T1 AS -- ancres : périodes de tête...
(SELECT Ta.*, 1 AS ITERATION
FROM T0 AS Ta
WHERE NOT EXISTS(SELECT *
FROM T0 AS Tb
WHERE Tb.outtime >= Ta.intime
AND Tb.outtime < Ta.outtime
AND Tb.id = Ta.id)
UNION ALL -- itération sur période dont le debut est inclus dans les bornes de la période ancre
SELECT T1.id, T1.intime, T0.outtime, T1.ITERATION + 1
FROM T1
INNER JOIN T0
ON T1.intime < T0.intime
AND T1.outtime >= T0.intime
AND T1.outtime < T0.outtime
AND T1.id = T0.id),
T2 AS
(SELECT *, ROW_NUMBER() OVER(PARTITION BY id, intime ORDER BY DATEDIFF(s, intime, outtime) DESC) AS N1,
ROW_NUMBER() OVER(PARTITION BY id, outtime ORDER BY DATEDIFF(s, intime, outtime) DESC) AS N2
FROM T1)
SELECT id, intime, outtime
FROM T2
WHERE N1 = 1 AND N2 = 1;
Which is a recursive Query
Itzik Ben Gan has done some more sophisticated and performant queries...

Update table for each date and add remaining sites from another table

I have a table 'test' like this-
ID Site Start Time End Time
1 A 30-12-2014 16:06:54 30-12-2014 16:39:52
2 B 30-12-2014 12:12:50 30-12-2014 12:13:52
3 C 31-12-2014 12:14:23 31-12-2014 12:15:22
4 A 01-01-2015 12:20:29 01-01-2015 12:23:32
5 B 01-01-2015 12:28:49 01-01-2015 12:29:47
I have another table 'list' with a listing of sites-
Site
A
B
C
I need an output table where for each date, all the sites from 'list' is included like this-
ID Site Start Time End Time
1 A 30-12-2014 16:06:54 30-12-2014 16:39:52
2 B 30-12-2014 12:12:50 30-12-2014 12:13:52
NULL C 30-12-2014 00:00:00 30-12-2014 00:00:00
NULL A 31-12-2014 00:00:00 31-12-2014 00:00:00
NULL B 31-12-2014 00:00:00 31-12-2014 00:00:00
3 C 31-12-2014 12:14:23 31-12-2014 12:15:22
4 A 01-01-2015 12:20:29 01-01-2015 12:23:32
5 B 01-01-2015 12:28:49 01-01-2015 12:29:47
NULL C 01-01-2015 00:00:00 01-01-2015 00:00:00
Till now I have been table to separate the 'test' table on each date into intermediate tables and select the non matching sites from 'list' table. I am stuck with the loop. Please help.
Here is my code-
ALTER TABLE [test] ADD [DATE] date;
update [test]
set [DATE] = CAST(Start Time] as Date)
select t1.[Site]
from list t1
left join test t2 on t1.[site]=t2.[site] where t2.site is null;
select distinct [DATE] into #Temp1 from [test]
order by [DATE];
select [DATE], row_number()over(order by ([Date])asc) as [Row] into #Temp2 from #Temp1;
drop table #Temp1;
GO
declare #row int
select #row = 0
while ( #row <= (select COUNT(*) from #Temp2))
begin
select #row = 1 + #row
select c.* into #temp3
from(
select a.* , b.[DATE] as b_date, b.[row]
from test a
inner join #Temp2 b
on a.[Date] = b.[Date] where b.[row] = #row
) c
End;
You can get the output you want using a select:
select t.id, l.site, coalesce(t.starttime, d.d) as starttime, coalesce(t.endtime, d.d) as endtime
from list l cross join
(select distinct cast(starttime as date) as d from test) d left join
test t
on t.site = l.site and cast(t.starttime as date) = d.d;
You can insert non-matching rows into the table with similar logic:
insert into test(id, site, starttime, endtime)
select t.id, l.site, d.d, d.d
from list l cross join
(select distinct cast(starttime as date) as d from test) d left join
test t
on t.site = l.site and cast(t.starttime as date) = d.d
where t.site is null;
Try this,
Declare #t table(ID int, Site varchar(50),StartTime datetime,EndTime datetime)
insert into #t values
(1, 'A', '12-30-2014 16:06:54','12-30-2014 16:39:52'),
(2 , 'B', '12-30-2014 12:12:50','12-30-2014 12:13:52'),
(3 , 'C', '12-31-2014 12:14:23','12-31-2014 12:15:22'),
(4 , 'A', '01-01-2015 12:20:29','01-01-2015 12:23:32'),
(5, 'B', '01-01-2015 12:28:49','01-01-2015 12:29:47')
dECLARE #lIST TABLE(Site varchar(50))
insert into #lIST values('A'),('B'),('C')
;WITH CTE AS
(
SELECT min(cast(StartTime as date)) st FROM #t
union all
SELECT dateadd(day,1, st) FROM CTE where
st<casT('01-01-2015 12:28:49' as date)--max date(can be dynamic)
)
,CTE1 as
(
select * from #lIST a
cross apply (select * from cte)b
)
,CTE2 as
(
select y.ID,x.Site
,ISNULL(y.StartTime,x.st)StartTime,ISNULL(y.EndTime,x.st)EndTime
from CTE1 x
left join #t y on x.site=y.site and
cast(x.st as date)=cast(y.StartTime as date)
)
SELECT * FROM CTE2

SQL Inner join not working to get change from previous date

I have a table with the following data :
TradeDate Stock BuySell DayClose
--------------------------------------
10-Dec-12 ABC 1 11
10-Dec-12 ABC 2 12
11-Dec-12 ABC 1 11.5
11-Dec-12 ABC 2 12.5
11-Dec-12 DEF 1 15
11-Dec-12 DEF 2 16
and I want to query on it for a particular date 11-Dec-2012 to get the following output :
Stock Buy Sell Mid Change
--------------------------------------
ABC 11.5 12.5 12.0 0.5
DEF 15 16 15.5
Since DEF does not have data for the previous date, change should be blank for it.
I have created the following query :
Select Stock,
AVG(CASE BuySell WHEN 1 THEN DayClose END) AS 'Buy',
AVG(CASE BuySell WHEN 2 THEN DayClose END) As 'Sell',
Sum(DayClose/2) as 'Mid',
Sum(Change/2) AS Change
FROM (
select t1.Stock, t1.BuySell, t1.DayClose, Sum(t1.DayClose - t2.DayClose) as Change
FROM #myTable as t1 inner join #myTable as t2 on
t1.Stock = t2.Stock
where
t1.TradeDate = '2012-12-11' AND
t2.TradeDate = (SELECT TOP 1 TradeDate FROM #myTable WHERE TradeDate < '2012-12-11' ORDER BY TradeDate DESC)
GROUP BY
t1.Stock, t1.buysell, t1.dayclose ) AS P1 GROUP BY stock
I created a temp table #mytable for this purpose :
drop table #mytable
CREATE TABLE #myTable
(
TradeDate datetime,
stock varchar(20),
buysell int,
dayclose decimal(10,2)
)
insert into #mytable values ('10-dec-2012', 'abc' , 1, 11)
insert into #mytable values ('10-dec-2012', 'abc' , 2, 12)
insert into #mytable values ('11-dec-2012', 'abc' , 1, 11.5)
insert into #mytable values ('11-dec-2012', 'abc' , 2, 12.5)
insert into #mytable values ('11-dec-2012', 'def' , 1, 15)
insert into #mytable values ('11-dec-2012', 'def' , 2, 16)
But I am not able to get the required output, rather getting
Stock Buy Sell Mid Change
--------------------------------------------------------------
abc 11.500000 12.500000 12.00000 1.00
Can someone tell me where am I going wrong. I seem to be lost in here.
Thanks,
Monika
Please try:
;WITH T1 as(
SELECT a.TradeDate
,a.stock
,SUM(CASE WHEN a.BuySell = 1 THEN a.DayClose ELSE 0 END) Buy
,SUM(CASE WHEN a.BuySell = 2 THEN a.DayClose ELSE 0 END) Sell
,SUM(a.DayClose) / 2 AS Mid
FROM #mytable a
GROUP BY a.TradeDate, a.stock
)SELECT t.*,
t.Mid - PR.Mid AS Change
FROM T1 t
LEFT JOIN
T1 PR ON
PR.TradeDate = DATEADD(DAY, -1, t.TradeDate)
AND PR.stock = t.stock
Try this:
SELECT a.TradeDate
,a.stock
,SUM(CASE WHEN a.BuySell = 1 THEN a.DayClose ELSE 0 END) Buy
,SUM(CASE WHEN a.BuySell = 2 THEN a.DayClose ELSE 0 END) Sell
,SUM(a.DayClose) / 2 AS Mid
INTO #temp
FROM #mytable a
GROUP BY a.TradeDate, a.stock
SELECT t.*,
t.Mid - previousRecord.Mid AS Change
FROM #temp t
LEFT JOIN
#temp previousRecord ON
previousRecord.TradeDate = DATEADD(DAY, -1, t.TradeDate)
AND previousRecord.stock = t.stock
DROP TABLE #temp
All you have to do now is to select the data for a date.
Select Stock,
AVG(CASE BuySell WHEN 1 THEN DayClose END) AS 'Buy',
AVG(CASE BuySell WHEN 2 THEN DayClose END) As 'Sell',
Sum(DayClose/2) as 'Mid',
Sum(Change/2) AS Change
FROM (
select t1.Stock, t1.BuySell, t1.DayClose, Sum( t1.DayClose - t2.DayClose ) as Change
FROM #myTable as t1 left join #myTable as t2 on t2.TradeDate = (SELECT TOP 1 TradeDate FROM #myTable WHERE TradeDate < t1.TradeDate ORDER BY TradeDate DESC)
and t1.Stock = t2.Stock and t1.buysell=t2.buysell
where
t1.TradeDate = '11-12-2012'

Creating groups of consecutive days meeting a given criteria

I have table the following data structure in SQL Server:
ID Date Allocation
1, 2012-01-01, 0
2, 2012-01-02, 2
3, 2012-01-03, 0
4, 2012-01-04, 0
5, 2012-01-05, 0
6, 2012-01-06, 5
etc.
What I need to do is get all consecutive day periods where Allocation = 0, and in the following form:
Start Date End Date DayCount
2012-01-01 2012-01-01 1
2012-01-03 2012-01-05 3
etc.
Is it possible to do this in SQL, and if so how?
In this answer, I'll assume that the "id" field numbers the rows consecutively when sorted by increasing date, like it does in the example data. (Such a column can be created if it does not exist).
This is an example of a technique described here and here.
1) Join the table to itself on adjacent "id" values. This pairs adjacent rows. Select rows where the "allocation" field has changed. Store the result in a temporary table, also keeping a running index.
SET #idx = 0;
CREATE TEMPORARY TABLE boundaries
SELECT
(#idx := #idx + 1) AS idx,
a1.date AS prev_end,
a2.date AS next_start,
a1.allocation as allocation
FROM allocations a1
JOIN allocations a2
ON (a2.id = a1.id + 1)
WHERE a1.allocation != a2.allocation;
This gives you a table having "the end of the previous period", "the start of the next period", and "the value of 'allocation' in the previous period" in each row:
+------+------------+------------+------------+
| idx | prev_end | next_start | allocation |
+------+------------+------------+------------+
| 1 | 2012-01-01 | 2012-01-02 | 0 |
| 2 | 2012-01-02 | 2012-01-03 | 2 |
| 3 | 2012-01-05 | 2012-01-06 | 0 |
+------+------------+------------+------------+
2) We need the start and end of each period in the same row, so we need to combine adjacent rows again. Do this by creating a second temporary table like boundaries but having an idx field 1 greater:
+------+------------+------------+
| idx | prev_end | next_start |
+------+------------+------------+
| 2 | 2012-01-01 | 2012-01-02 |
| 3 | 2012-01-02 | 2012-01-03 |
| 4 | 2012-01-05 | 2012-01-06 |
+------+------------+------------+
Now join on the idx field and we get the answer:
SELECT
boundaries2.next_start AS start,
boundaries.prev_end AS end,
allocation
FROM boundaries
JOIN boundaries2
USING(idx);
+------------+------------+------------+
| start | end | allocation |
+------------+------------+------------+
| 2012-01-02 | 2012-01-02 | 2 |
| 2012-01-03 | 2012-01-05 | 0 |
+------------+------------+------------+
** Note that this answer gets the "internal" periods correctly but misses the two "edge" periods where allocation = 0 at the beginning and allocation = 5 at the end. Those can be pulled in using UNION clauses but I wanted to present the core idea without that complication.
Following would be one way to do it. The gist of this solution is
Use a CTE to get a list of all consecutive start and enddates with Allocation = 0
Use the ROW_NUMBER window function to assign rownumbers depending on both start- and enddates.
Select only those records where both ROW_NUMBERS equal 1.
Use DATEDIFFto calculate the DayCount
SQL Statement
;WITH r AS (
SELECT StartDate = Date, EndDate = Date
FROM YourTable
WHERE Allocation = 0
UNION ALL
SELECT r.StartDate, q.Date
FROM r
INNER JOIN YourTable q ON DATEDIFF(dd, r.EndDate, q.Date) = 1
WHERE q.Allocation = 0
)
SELECT [Start Date] = s.StartDate
, [End Date ] = s.EndDate
, [DayCount] = DATEDIFF(dd, s.StartDate, s.EndDate) + 1
FROM (
SELECT *
, rn1 = ROW_NUMBER() OVER (PARTITION BY StartDate ORDER BY EndDate DESC)
, rn2 = ROW_NUMBER() OVER (PARTITION BY EndDate ORDER BY StartDate ASC)
FROM r
) s
WHERE s.rn1 = 1
AND s.rn2 = 1
OPTION (MAXRECURSION 0)
Test script
;WITH q (ID, Date, Allocation) AS (
SELECT * FROM (VALUES
(1, '2012-01-01', 0)
, (2, '2012-01-02', 2)
, (3, '2012-01-03', 0)
, (4, '2012-01-04', 0)
, (5, '2012-01-05', 0)
, (6, '2012-01-06', 5)
) a (a, b, c)
)
, r AS (
SELECT StartDate = Date, EndDate = Date
FROM q
WHERE Allocation = 0
UNION ALL
SELECT r.StartDate, q.Date
FROM r
INNER JOIN q ON DATEDIFF(dd, r.EndDate, q.Date) = 1
WHERE q.Allocation = 0
)
SELECT s.StartDate, s.EndDate, DATEDIFF(dd, s.StartDate, s.EndDate) + 1
FROM (
SELECT *
, rn1 = ROW_NUMBER() OVER (PARTITION BY StartDate ORDER BY EndDate DESC)
, rn2 = ROW_NUMBER() OVER (PARTITION BY EndDate ORDER BY StartDate ASC)
FROM r
) s
WHERE s.rn1 = 1
AND s.rn2 = 1
OPTION (MAXRECURSION 0)
Alternative way with CTE but without ROW_NUMBER(),
Sample data:
if object_id('tempdb..#tab') is not null
drop table #tab
create table #tab (id int, date datetime, allocation int)
insert into #tab
select 1, '2012-01-01', 0 union
select 2, '2012-01-02', 2 union
select 3, '2012-01-03', 0 union
select 4, '2012-01-04', 0 union
select 5, '2012-01-05', 0 union
select 6, '2012-01-06', 5 union
select 7, '2012-01-07', 0 union
select 8, '2012-01-08', 5 union
select 9, '2012-01-09', 0 union
select 10, '2012-01-10', 0
Query:
;with cte(s_id, e_id, b_id) as (
select s.id, e.id, b.id
from #tab s
left join #tab e on dateadd(dd, 1, s.date) = e.date and e.allocation = 0
left join #tab b on dateadd(dd, -1, s.date) = b.date and b.allocation = 0
where s.allocation = 0
)
select ts.date as [start date], te.date as [end date], count(*) as [day count] from (
select c1.s_id as s, (
select min(s_id) from cte c2
where c2.e_id is null and c2.s_id >= c1.s_id
) as e
from cte c1
where b_id is null
) t
join #tab t1 on t1.id between t.s and t.e and t1.allocation = 0
join #tab ts on ts.id = t.s
join #tab te on te.id = t.e
group by t.s, t.e, ts.date, te.date
Live example at data.SE.
Using this sample data:
CREATE TABLE MyTable (ID INT, Date DATETIME, Allocation INT);
INSERT INTO MyTable VALUES (1, {d '2012-01-01'}, 0);
INSERT INTO MyTable VALUES (2, {d '2012-01-02'}, 2);
INSERT INTO MyTable VALUES (3, {d '2012-01-03'}, 0);
INSERT INTO MyTable VALUES (4, {d '2012-01-04'}, 0);
INSERT INTO MyTable VALUES (5, {d '2012-01-05'}, 0);
INSERT INTO MyTable VALUES (6, {d '2012-01-06'}, 5);
GO
Try this:
WITH DateGroups (ID, Date, Allocation, SeedID) AS (
SELECT MyTable.ID, MyTable.Date, MyTable.Allocation, MyTable.ID
FROM MyTable
LEFT JOIN MyTable Prev ON Prev.Date = DATEADD(d, -1, MyTable.Date)
AND Prev.Allocation = 0
WHERE Prev.ID IS NULL
AND MyTable.Allocation = 0
UNION ALL
SELECT MyTable.ID, MyTable.Date, MyTable.Allocation, DateGroups.SeedID
FROM MyTable
JOIN DateGroups ON MyTable.Date = DATEADD(d, 1, DateGroups.Date)
WHERE MyTable.Allocation = 0
), StartDates (ID, StartDate, DayCount) AS (
SELECT SeedID, MIN(Date), COUNT(ID)
FROM DateGroups
GROUP BY SeedID
), EndDates (ID, EndDate) AS (
SELECT SeedID, MAX(Date)
FROM DateGroups
GROUP BY SeedID
)
SELECT StartDates.StartDate, EndDates.EndDate, StartDates.DayCount
FROM StartDates
JOIN EndDates ON StartDates.ID = EndDates.ID;
The first section of the query is a recursive SELECT, which is anchored by all rows that are allocation = 0, and whose previous day either doesn't exist or has allocation != 0. This effectively returns IDs: 1 and 3 which are the starting dates of the periods of time you want to return.
The recursive part of this same query starts from the anchor rows, and finds all subsequent dates that also have allocation = 0. The SeedID keeps track of the anchored ID through all the iterations.
The result so far is this:
ID Date Allocation SeedID
----------- ----------------------- ----------- -----------
1 2012-01-01 00:00:00.000 0 1
3 2012-01-03 00:00:00.000 0 3
4 2012-01-04 00:00:00.000 0 3
5 2012-01-05 00:00:00.000 0 3
The next sub query uses a simple GROUP BY to filter out all the start dates for each SeedID, and also counts the days.
The last sub query does the same thing with the end dates, but this time the day count isn't needed as we already have this.
The final SELECT query joins these two together to combine the start and end dates, and returns them along with the day count.
Give it a try if it works for you
Here SDATE for your DATE remains same as your table.
SELECT SDATE,
CASE WHEN (SELECT COUNT(*)-1 FROM TABLE1 WHERE ID BETWEEN TBL1.ID AND (SELECT MIN(ID) FROM TABLE1 WHERE ID > TBL1.ID AND ALLOCATION!=0)) >0 THEN(
CASE WHEN (SELECT SDATE FROM TABLE1 WHERE ID =(SELECT MAX(ID) FROM TABLE1 WHERE ID >TBL1.ID AND ID<(SELECT MIN(ID) FROM TABLE1 WHERE ID > TBL1.ID AND ALLOCATION!=0))) IS NULL THEN SDATE
ELSE (SELECT SDATE FROM TABLE1 WHERE ID =(SELECT MAX(ID) FROM TABLE1 WHERE ID >TBL1.ID AND ID<(SELECT MIN(ID) FROM TABLE1 WHERE ID > TBL1.ID AND ALLOCATION!=0))) END
)ELSE (SELECT SDATE FROM TABLE1 WHERE ID = (SELECT MAX(ID) FROM TABLE1 WHERE ID > TBL1.ID ))END AS EDATE
,CASE WHEN (SELECT COUNT(*)-1 FROM TABLE1 WHERE ID BETWEEN TBL1.ID AND (SELECT MIN(ID) FROM TABLE1 WHERE ID > TBL1.ID AND ALLOCATION!=0)) <0 THEN
(SELECT COUNT(*) FROM TABLE1 WHERE ID BETWEEN TBL1.ID AND (SELECT MAX(ID) FROM TABLE1 WHERE ID > TBL1.ID )) ELSE
(SELECT COUNT(*)-1 FROM TABLE1 WHERE ID BETWEEN TBL1.ID AND (SELECT MIN(ID) FROM TABLE1 WHERE ID > TBL1.ID AND ALLOCATION!=0)) END AS DAYCOUNT
FROM TABLE1 TBL1 WHERE ALLOCATION = 0
AND (((SELECT ALLOCATION FROM TABLE1 WHERE ID=(SELECT MAX(ID) FROM TABLE1 WHERE ID < TBL1.ID))<> 0 ) OR (SELECT MAX(ID) FROM TABLE1 WHERE ID < TBL1.ID)IS NULL);
A solution without CTE:
SELECT a.aDate AS StartDate
, MIN(c.aDate) AS EndDate
, (datediff(day, a.aDate, MIN(c.aDate)) + 1) AS DayCount
FROM (
SELECT x.aDate, x.allocation, COUNT(*) idn FROM table1 x
JOIN table1 y ON y.aDate <= x.aDate
GROUP BY x.id, x.aDate, x.allocation
) AS a
LEFT JOIN (
SELECT x.aDate, x.allocation, COUNT(*) idn FROM table1 x
JOIN table1 y ON y.aDate <= x.aDate
GROUP BY x.id, x.aDate, x.allocation
) AS b ON a.idn = b.idn + 1 AND b.allocation = a.allocation
LEFT JOIN (
SELECT x.aDate, x.allocation, COUNT(*) idn FROM table1 x
JOIN table1 y ON y.aDate <= x.aDate
GROUP BY x.id, x.aDate, x.allocation
) AS c ON a.idn <= c.idn AND c.allocation = a.allocation
LEFT JOIN (
SELECT x.aDate, x.allocation, COUNT(*) idn FROM table1 x
JOIN table1 y ON y.aDate <= x.aDate
GROUP BY x.id, x.aDate, x.allocation
) AS d ON c.idn = d.idn - 1 AND d.allocation = c.allocation
WHERE b.idn IS NULL AND c.idn IS NOT NULL AND d.idn IS NULL AND a.allocation = 0
GROUP BY a.aDate
Example

How to limit the selection in SQL Server by sum of a column?

Can I limit rows by sum of a column in a SQL Server database?
For example:
Type | Time (in minutes)
-------------------------
A | 50
B | 10
C | 30
D | 20
E | 70
...
And I want to limit the selection by sum of time. For example maximum of 100 minutes. Table must look like this:
Type | Time (in minutes)
-------------------------
A | 50
B | 10
C | 30
Any ideas? Thanks.
DECLARE #T TABLE
(
[Type] CHAR(1) PRIMARY KEY,
[Time] INT
)
INSERT INTO #T
SELECT 'A',50 UNION ALL
SELECT 'B',10 UNION ALL
SELECT 'C',30 UNION ALL
SELECT 'D',20 UNION ALL
SELECT 'E',70;
WITH RecursiveCTE
AS (
SELECT TOP 1 [Type], [Time], CAST([Time] AS BIGINT) AS Total
FROM #T
ORDER BY [Type]
UNION ALL
SELECT R.[Type], R.[Time], R.Total
FROM (
SELECT T.*,
T.[Time] + Total AS Total,
rn = ROW_NUMBER() OVER (ORDER BY T.[Type])
FROM #T T
JOIN RecursiveCTE R
ON R.[Type] < T.[Type]
) R
WHERE R.rn = 1 AND Total <= 100
)
SELECT [Type], [Time], Total
FROM RecursiveCTE
OPTION (MAXRECURSION 0);
Or if your table is small
SELECT t1.[Type],
t1.[Time],
SUM(t2.[Time])
FROM #T t1
JOIN #T t2
ON t2.[Type] <= t1.[Type]
GROUP BY t1.[Type],t1.[Time]
HAVING SUM(t2.[Time]) <=100