Cumulative list operations on Rows in SQL - sql

I have a scenario where I need to compare the list of a values for a particular day with the previous day for same person. And for every day I need to maintain a difference of those two consecutive days.
This is data of sales people, every day they visit few houses from the whole list of houses in their town.
Below is the data I have:
Houses:
Select Distinct House_id from tbl_houses;
Visits:
Select sales_person_id, Date, visited_house_id;
Now, I have two different tables like this:
House_id
1
2
3
4
5
And, the sales persons visit's data is as follows:
sales_person_id Date visited_house
1 12/6/16 1
1 12/6/16 2
2 12/6/16 1
2 13/6/16 3
3 12/6/16 3
1 13/6/16 1
1 13/6/16 3
A sales person can visit a already visited house and more than one sales person can visit the same house on same day. No restrictions on this.
And, the desired output as follows:
Sales_person_Id Date Visited_Houses Not_Visited_Houses
1 12/6/16 [1,2] [3,4,5]
1 13/6/16 [1,2,3] [4,5]
2 12/6/16 [1] [2,3,4,5]
2 13/6/16 [1,3] [2,4,5]
3 12/6/16 [3] [1,2,4,5]
How can we achieve this in sql?

For SQL Server (you can alter the CTE at the top to use whatever date range you like):
WITH cteDates
AS
(
SELECT CAST(GETDATE() AS date) DT
UNION
SELECT CAST(GETDATE() - 1 AS date)
)
SELECT
SP.sales_person_id
, D.[DT] [Date]
, '[' + LEFT(V.Visited, LEN(V.Visited) - 1) + ']' Visited
, '[' + LEFT(NV.NotVisited, LEN(NV.NotVisited) - 1) + ']' Visited
FROM
cteDates D
CROSS JOIN (SELECT DISTINCT sales_person_id FROM tbl_visits) SP
OUTER APPLY
(
SELECT
CAST(
(
SELECT CAST(visited_house_id AS varchar) + ','
FROM tbl_visits
WHERE
sales_person_id = SP.sales_person_id
AND [Date] = D.DT
ORDER BY visited_house_id
FOR XML PATH ('')
)
AS varchar) Visited
) V
OUTER APPLY
(
SELECT
CAST(
(
SELECT CAST(House_id AS varchar) + ','
FROM tbl_houses
WHERE House_id NOT IN
(
SELECT visited_house_id
FROM tbl_visits
WHERE
sales_person_id = SP.sales_person_id
AND [Date] = D.DT
)
ORDER BY House_id
FOR XML PATH ('')
)
AS varchar) NotVisited
) NV
ORDER BY
sales_person_id
, [Date]

Please check below small piece of code which could help you :
SELECT DISTINCT
H.sales_person_id,
H.Date,
Visited_Houses = STUFF(
(
SELECT ','+CONVERT(NVARCHAR(MAX), houseid)
FROM history
WHERE [sales_person_id] = H.sales_person_id
AND [Date] = H.Date FOR XML PATH('')
), 1, 1, '[')+']',
Not_Visited_Houses = STUFF(
(
SELECT ','+CONVERT(NVARCHAR(MAX), TT.House_id)
FROM
(
SELECT HS1.House_id,
H1.sales_person_id
FROM house HS1
LEFT JOIN #history H1 ON H1.houseid = HS1.House_id
AND H1.sales_person_id = H.sales_person_id AND H1.Date = H.Date
) TT
WHERE TT.sales_person_id IS NULL FOR XML PATH('')
), 1, 1, '[')+']'
FROM house HS
INNER JOIN #history H ON H.houseid = HS.House_id;
Desired Output :
sales_person_id Date Visited_Houses Not_Visited_Houses
--------------- ---------- ---------------- -------------------
1 2016-06-12 [1,2] [3,4,5]
1 2016-06-13 [1,3] [2,4,5]
2 2016-06-12 [1] [2,3,4,5]
2 2016-06-13 [3] [1,2,4,5]
3 2016-06-12 [3] [1,2,4,5]
Note : The above output is as per your Data that you have given.
Hope, it will help you.

Related

Query with problem in the group by in SQL Server

I have this query that works in MySQL:
SELECT
r.*, c.*
FROM
Rsv AS r
INNER JOIN
Clts AS c ON r.ClientID = c.ClientID
LEFT OUTER
Mes AS m ON r.MesaID = m.MesaID
WHERE
RHS IS NULL AND RC = 0
GROUP BY
r.ClientID;
I want this to work in SQL Server and I know that in SQL Server when you use GROUP BY, the elements in the SELECT need to be either in the GROUP BY or need to have an aggregate function. But I want to select more elements and I don't think I need aggregate functions on them because it doesn't matter which one it will retrieve the information from since only the MesaNum field is going to be different. How can I achieve this?
EDIT:
Rsv Table:
RsvID MesaID ClientID RsvTime RsvDate RHS RC
1 1 1 8:00 2018-09-17 null 0
2 2 1 8:00 2018-09-17 null 0
3 3 2 9:00 2018-09-17 null 0
Desired result:
RsvID MesaID ClientID RsvTime RsvDate RHS RC
1 1,2 1 8:00 2018-09-17 null 0
3 3 2 9:00 2018-09-17 null 0
(Sorry, couldn't figure out how to do tables here)
You can try to use STUFF function to do groupby_concat, then make row number by ClientID get rn = 1
SELECT * FROM (
SELECT RsvID,ClientID,RsvTime,RsvDate,RHS,RC,
STUFF((
SELECT ',' + CAST(tt.MesaID AS VARCHAR(5))
FROM Rsv tt
WHERE tt.ClientID = t1.ClientID
FOR XML PATH(''), TYPE).value('.', 'NVARCHAR(MAX)'), 1, 1, '') MesaID,
ROW_NUMBER() OVER(PARTITION BY ClientID ORDER BY RsvID) rn
FROM Rsv t1
) t1
where rn = 1
sqlfiddle

Zip/repeat join?

Let's say I have a simple table of documents with a type column:
Documents
Id Type
1 A
2 A
3 B
4 C
5 C
6 A
7 A
8 A
9 B
10 C
Users have permissions to access different types of documents:
Permissions
Type User
A John
A Jane
B Sarah
C Peter
C John
C Mark
And I need to distribute those documents among the users as tasks:
Tasks
Id T DocId UserId
1 A 1 John
2 A 2 Jane
3 B 3 Sarah
4 C 4 Peter
5 C 5 John
6 A 6 John
7 A 7 Jane
8 A 8 John
9 B 9 Sarah
10 C 10 Mark
How do I do that? How do I get the Tasks?
You can enumerate the rows and then use modulo arithmetic for the matching:
with d as (
select d.*,
row_number() over (partition by type order by newid()) as seqnum,
count(*) over (partition by type) as cnt
from documents d
),
u as (
select u.*,
row_number() over (partition by type order by newid()) as seqnum,
count(*) over (partition by type) as cnt
from users u
)
select d.*
from d join
u
on d.type = u.type and
u.seqnum = (d.seqnum % u.cnt) + 1
Great question.
This solution returns all possible distributions, ordered by priority which is determined by information such as number of user involved, minimum documents per user, standard deviation of tasks per user etc.
I'm not counting on document.id to be a sequence of numbers starting with 1, therfore the use of dense_rank.
The core of the solutions is the iterative CTE which generates the record sets of all possible distributions.
Execution time on my laptop is around 20 seconds, (the iterative part takes 5 seconds)
with doc_user as
(
select d."id" as docid
,p."user" as userid
,dense_rank () over (order by d."id") as doc_seq
from documents d
left join permissions p
on p.type = d.type
)
,it_cte as
(
select docid
,userid
,doc_seq
,cast (coalesce(userid,'') as varchar(max)) as path
,'A' as cte_part
from doc_user
where doc_seq = 1
union all
select r.docid
,r.userid
,du.doc_seq
,r.path + ',' + coalesce (du.userid,'')
,'B'
from it_cte as r
cross join doc_user as du
where du.doc_seq = r.doc_seq + 1
union all
select du.docid
,du.userid
,du.doc_seq
,r.path + ',' + coalesce (du.userid,'')
,'C'
from it_cte as r
cross join doc_user as du
where du.doc_seq = r.doc_seq + 1
and r.cte_part in ('A','C')
)
,result_sets as
(
select dense_rank () over (order by path) as set_id
,docid
,userid
from it_cte
where doc_seq = (select count(*) from documents)
)
,result_sets_stat as
(
select set_id
,count (distinct userid) as users_involved
from result_sets
group by set_id
)
,result_sets_users_stat as
(
select set_id
,min (doc) min_doc_per_user
,stdevp (doc) stdevp_doc_per_user
from (select set_id
,userid
,count (*) as doc
from result_sets
group by set_id
,userid
) t
group by set_id
)
select s.set_priority
,r.docid
,r.userid
,s.users_involved
,s.min_doc_per_user
,s.stdevp_doc_per_user
from (select s.set_id
,s.users_involved
,u.min_doc_per_user
,u.stdevp_doc_per_user
,row_number () over
(
order by s.users_involved desc
,u.min_doc_per_user desc
,u.stdevp_doc_per_user
,s.set_id
) as set_priority
from result_sets_stat as s
join result_sets_users_stat as u
on u.set_id =
s.set_id
) s
join result_sets as r
on r.set_id =
s.set_id
order by s.set_priority
,r.docid
option (merge join)
;

duplicate entry in union

I have three tables:
1. Flat Discount
2. Promotion
3. weeklyorder
When i join these table and take union i got 2 row with same data but one different .. how to merge it to show only one row.
Query:
SELECT skuMaster.SKU,
(skuMaster.MinimumStock - COUNT(*)) as ReorderQuantity,
'LowInventory' as descp
FROM SKUMaster skuMaster
JOIN InventoryMaster inventoryMaster ON skuMaster.SKU = inventoryMaster.SKU
GROUP BY skuMaster.sku, skuMaster.MinimumStock, skuMaster.Name
HAVING COUNT(*) < skuMaster.MinimumStock
UNION
SELECT WeeklyOrderList.SKU,
WeeklyOrderList.Quantity as ReorderQuantity,
'NoPO' as descp
FROM WeeklyOrderList
WHERE WeeklyOrderList.POCGen = 'true'
result :
SKU ReorderQuantity descp
1 1 LowInventory
2 2 LowInventory
2 2 NoPO
6 5 LowInventory
here 2nd And 3rd are alomost same only description is different.
can we combine them and show only one row with descp as lowinventory and NOPO
SKU ReorderQuantity descp
1 1 LowInventory
2 2 LowInventory NoPo
6 5 LowInventory
same as above suppose we have table below
SKU ReorderQuantity
1 1
2 5
2 10
6 5
here output should be Max reorder quantity of same sku
Result:
SKU ReorderQuantity
1 1
2 10
6 5
;WITH CTE AS
(
SELECT skuMaster.SKU,
(skuMaster.MinimumStock - COUNT(*)) as ReorderQuantity,
'LowInventory' as descp
FROM SKUMaster skuMaster
JOIN InventoryMaster inventoryMaster ON skuMaster.SKU = inventoryMaster.SKU
GROUP BY skuMaster.sku, skuMaster.MinimumStock, skuMaster.Name
HAVING COUNT(*) < skuMaster.MinimumStock
UNION
SELECT WeeklyOrderList.SKU,
WeeklyOrderList.Quantity as ReorderQuantity,
'NoPO' as descp
FROM WeeklyOrderList
WHERE WeeklyOrderList.POCGen = 'true'
)
SELECT DISTINCT
a.SKU,
a.ReorderQuantity,
descp = STUFF((SELECT ', ' + b.descp
FROM CTE b
WHERE b.ReorderQuantity = a.ReorderQuantity
FOR XML PATH('')), 1, 2, '')
FROM CTE a
just an example for above output mentioned
DECLARE #t TABLE
(
SKU INT,
ReorderQuantity INT
)
INSERT INTO #t (SKU,ReorderQuantity)
VALUES (1,1), (2,5), (2,10), (6,5)
SELECT t.SKU,tt.Qty
FROM #t t
INNER JOIN (SELECT MAX(ReorderQuantity)as Qty, SKU
FROM #t
GROUP BY SKU) tt
ON tt.SKU = t.SKU
GROUP BY t.SKU,tt.Qty

Group and tally values for each record in SQL [duplicate]

This question already has answers here:
How to use GROUP BY to concatenate strings in SQL Server?
(22 answers)
Closed 8 years ago.
Im trying to run a select statement to group records having similar IDs but also tally the values from another column for each master ID. So for example below. The result for each line will be the first instance unique ID and the 2 names shown from each record separated by semi colon. Thanks in advance.
Current set
ID Name Cnt
-------------------------------- ----------------- ---
0001D72BA5F664BE129B6AB5744E2BE0 Talati, Shilpa 1
0001D72BA5F664BE129B6AB5744E2BE0 Weaver, Larry 1
0007EAB7CE9A3F2F95D2D63D0BBD08A9 St-Hilaire, Edith 1
0007EAB7CE9A3F2F95D2D63D0BBD08A9 Talati, Shilpa 1
Result:
0001D72BA5F664BE129B6AB5744E2BE0 Talati, Shilpa; Weaver, Larry
The easiest way to solve this in SQL Server is:
select masterId, min(name) + '; ' + max(name)
from table t
group by masterId;
Here's one way using a recursive common table expression. Given a table like this:
create table dbo.Fizzbuzz
(
id int not null identity(1,1) primary key clustered ,
group_id int not null ,
name varchar(50) not null ,
cnt int not null ,
)
containing this data
id group_id name cnt
-- -------- ------ ---
1 1 Bob 3
2 1 Carol 5
3 1 Ted 6
4 1 Alice 16
5 2 Harold 72
6 2 Maude 28
This query
with recursive_cte as
(
select group_id = t.group_id ,
row = t.row ,
name = convert(varchar(8000),t.name) ,
cnt = t.cnt
from ( select * ,
row = row_number() over (
partition by group_id
order by id
)
from dbo.Fizzbuzz
) t
where t.row = 1
UNION ALL
select group_id = prv.group_id ,
row = nxt.row ,
name = convert(varchar(8000), prv.name + ' and ' + nxt.name ) ,
cnt = prv.cnt + nxt.cnt
from recursive_cte prv
join ( select * ,
row = row_number() over (
partition by group_id
order by id
)
from dbo.Fizzbuzz
) nxt on nxt.group_id = prv.group_id
and nxt.row = prv.row + 1
)
select group_id = t.group_id ,
total = t.cnt ,
names = t.name
from ( select * ,
rank = rank() over (
partition by group_id
order by row desc
)
from recursive_cte
) t
where rank = 1
order by group_id
produces the following output
group_id cnt name
-------- --- -------------------------------
1 30 Bob and Carol and Ted and Alice
2 100 Harold and Maude
One should note however, that the depth of recursion is bounded in SQL Server.
SELECT
t1.ID,
(SELECT Name + '; '
FROM yourtable t2
WHERE t1.ID = t2.ID
for xml path('')) as Name
FROM yourtable t1
GROUP BY t1.ID

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