SQL query to return aggregations in a constant format, even when the categories are missing - sql

Is it possible with SQL Server to return table with a constant format?
Let's say we have the following raw data:
DATE | CATEGORY | VALUE
---------------------------------
01.01.2022 | Category 1 | 10
01.01.2022 | Category 1 | 20
01.01.2022 | Category 1 | 33
01.01.2022 | Category 3 | 15
03.01.2022 | Category 1 | 10
03.01.2022 | Category 2 | 20
03.01.2022 | Category 3 | 50
(...)
And the desired output would be:
DATE | CATEGORY | VALUE
---------------------------------
01.01.2022 | Category 1 | 63
01.01.2022 | Category 2 | 0
01.01.2022 | Category 3 | 15
02.01.2022 | Category 1 | 0
02.01.2022 | Category 2 | 0
02.01.2022 | Category 3 | 0
03.01.2022 | Category 1 | 10
03.01.2022 | Category 2 | 20
03.01.2022 | Category 3 | 50
(...)
Please notice that in the desired outcome there's a date present that's missing in the raw data, as well as sum of VALUE are 0 when the category is not present for a given date in the raw data.

-- Contiguous dates table
DECLARE #dates TABLE(dt date) ;
DECLARE #dateFrom date;
DECLARE #dateTo date;
select #dateFrom = (Select DateAdd(day, -1, Min(date)) from Agg);
select #dateTo = (Select Max(date) from Agg);
-- Query:
WHILE(#dateFrom < #dateTo)
BEGIN
SELECT #dateFrom = DATEADD(day, 1,#dateFrom)
INSERT INTO #dates
SELECT #dateFrom
END
-- Category table
DECLARE #categories TABLE(category nvarchar(20)) ;
insert into #categories values ('Category 1'),('Category 2'),('Category 3');
-- This cte helps in creating the constant output required
with cte1 as (
select dt, category from
#dates cross join #categories
)
select cte1.dt as [Date], cte1.category, Sum(coalesce(yourTableName.value,0)) as Value
from cte1 left join yourTableName
on cte1.dt = yourTableName.[Date] and cte1.category = yourTableName.category
group by cte1.dt, cte1.category
order by cte1.dt, cte1.category

WITH ctedate AS
(
SELECT d= v2.d * 10 + v1.d
FROM (VALUES (0), (1), (2), (3), (4), (5), (6), (7), (8), (9)) v1(d)
CROSS JOIN (VALUES (0), (1), (2), (3), (4), (5), (6), (7), (8), (9)) v2(d)
)
Select b.date, a.category, sum(coalesce(a.value,0)) from
yourtablename a
cross join
(SELECT DATEADD(DAY, ctedate.d, '2022-01-01') date
FROM ctedate
ORDER BY ctedate.d) b
group by b.date, a.category

Here is a script with solution using cte:
Calculate the min date and the max date
then make a calendar (cte1) with all dates between (min date) and (max date) using recursivity
get the list of all category => cte2
make a cross join between cte1 and cte2 ==> cte3
make a left join between cte3 and the table data (#mytable), replace Null values by 0
declare #mytable as table (date date,category varchar(50),Value int)
insert into #mytable values
('01/01/2022','Category 1',10),
('01/01/2022','Category 1',20),
('01/01/2022','Category 1',33),
('01/01/2022','Category 3',15),
('01/03/2022','Category 1',10),
('01/03/2022','Category 2',20),
('01/03/2022','Category 3',50);
declare #mindate as date, #maxdate as date
select #mindate=min(date),#maxdate=max(date) from #mytable;
with
cte1 as (select #mindate mydate union all select dateadd(day,1,mydate) from cte1 where dateadd(day,1,mydate) <= #maxdate),
cte2 as (select distinct(category) from #mytable),
cte3 as (select mydate,category from cte1 cross join cte2),
cte4 as (select mydate date ,cte3.category,isnull(value,0) value from cte3 left outer join #mytable t on cte3.mydate=t.date and cte3.category=t.category )
select * from cte4

Related

SQL: Repeat patterns between date range

DECLARE
#startDate date = '2020-07-03'
#endDate date = 2020-07-06'
I have a tabe as below
---------------------------------------------------------
|EmployeeID | EmpName |Pattern | Frequency |
---------------------------------------------------------
| 11 | X | 1,2,3 | 1 |
| 12 | Y | 4,5 | 1 |
| 13 | Y | 1,2 | 3 |
| 14 | Z | 1,2 | 2 |
---------------------------------------------------------
AND I want to generate dates between given date range.
WANT result table as bellows:
--------------------------------
| EmpId | Dates | Pattern |
--------------------------------
| 11 |2020-07-03 | 1 |
| 11 |2020-07-04 | 2 |
| 11 |2020-07-05 | 3 |
| 11 |2020-07-06 | 1 |
| 12 |2020-07-03 | 4 |
| 12 |2020-07-04 | 5 |
| 12 |2020-07-05 | 4 |
| 12 |2020-07-06 | 5 |
| 13 |2020-07-03 | 1 |
| 13 |2020-07-04 | 1 |
| 13 |2020-07-05 | 1 |
| 13 |2020-07-06 | 2 |
| 14 |2020-07-03 | 1 |
| 14 |2020-07-04 | 1 |
| 14 |2020-07-05 | 2 |
| 14 |2020-07-06 | 2 |
Generate the dates as per given date range for each employee and repeat the pattern for each employee as per their pattern and frequency(days).
means as per frequency(days) pattern will change.
What I have acheived :
Able to generate the records for each employees between the given date range.
What I am not able to get:
I am not able to repeat the pattern based on the frequency for each employee between the date range.
I am able achieve everything but need little help while repeating the pattern based on frequency.*
Note:
Data are storing in this way only.. now I won't change existing schema...
I've came up with this. It's basically a splitter, a tally table and some logic.
Joining (Frequency)-Amount of Tally-datasets with the splitted pattern for the correct amount of pattern-values. Sorting them by their position in the pattern-string.
Join everything together and repeat the pattern by using modulo.
DECLARE #t TABLE( EmployeeID INT
, EmpName VARCHAR(20)
, Pattern VARCHAR(255)
, Frequency INT )
DECLARE #startDate DATE = '2020-07-03'
DECLARE #endDate DATE = '2020-07-09'
INSERT INTO #t
VALUES (11, 'X', '1,2,3', 1),
(12, 'Y', '4,5', 1),
(13, 'Y', '1,2', 3),
(14, 'Z', '1,2', 2)
DECLARE #delimiter CHAR(1) = ',';
WITH split(Txt
, i
, elem
, EmployeeID)
AS (SELECT STUFF(Pattern, 1, CHARINDEX(#delimiter, Pattern+#delimiter+'~'), '')
, 1
, CAST(LEFT(Pattern, CHARINDEX(#delimiter, Pattern+#delimiter+'~')-1) AS VARCHAR(MAX))
, EmployeeID
FROM #t
UNION ALL
SELECT STUFF(Txt, 1, CHARINDEX(#delimiter, Txt+#delimiter+'~'), '')
, i + 1
, CAST(LEFT(Txt, CHARINDEX(#delimiter, Txt+#delimiter+'~')-1) AS VARCHAR(MAX))
, EmployeeID
FROM split
WHERE Txt > ''),
E1(N) AS (SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL
SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL
SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL
SELECT 1), --10E+1 or 10 rows
E2(N) AS (SELECT 1 FROM E1 AS a, E1 AS b), --10E+2 or 100 rows
E4(N) AS (SELECT 1 FROM E2 AS a, E2 AS b), --10E+4 or 10,000 rows
E8(N) AS (SELECT 1 FROM E4 AS a , E4 AS b), --10E+8 or 100,000,000 rows
PatternXFrequency(EmployeeID
, Sort
, elem)
AS (SELECT split.EmployeeID
, ROW_NUMBER() OVER(PARTITION BY split.EmployeeID ORDER BY i) - 1
, elem
FROM split
INNER JOIN #t AS t ON t.EmployeeID = split.EmployeeID
CROSS APPLY (SELECT TOP (t.Frequency) 1
FROM E8
) AS Freq(Dummy))
SELECT EmployeeID
, DATEADD(DAY, i_count, #startDate) AS Dates
, elem
FROM (SELECT DATEDIFF(DAY, #startDate, #endDate) + 1) AS t_datediff(t_days)
CROSS APPLY (SELECT TOP (t_days) ROW_NUMBER() OVER(ORDER BY (SELECT 0) ) - 1 FROM E8
) AS t_dateadd(i_count)
CROSS APPLY (SELECT PatternXFrequency.*
FROM (SELECT DISTINCT EmployeeID FROM #t) AS t(EmpID)
CROSS APPLY (SELECT COUNT(Sort)
FROM PatternXFrequency
WHERE EmployeeID = EmpID
) AS EmpPattern(sortCount)
CROSS APPLY (SELECT *
FROM PatternXFrequency
WHERE EmployeeID = EmpID
AND Sort = ((i_count % sortCount))
) AS PatternXFrequency
) AS t
ORDER BY t.EmployeeID
, Dates
This isn't particularly pretty, but it avoids the recursion of a rCTE, so should provide a faster experience. As STRING_SPLIT still doesn't know what ordinal position means, we have to use something else here; I use DelimitedSplit8k_LEAD.
I also assume your expected results are wrong, as they stop short of your end date (20200709). This results in the below:
CREATE TABLE dbo.YourTable (EmployeeID int,
EmpName char(1),
Pattern varchar(8000), --This NEEDS fixing
Frequency tinyint);
INSERT INTO dbo.YourTable
VALUES(11,'X','1,2,3',1),
(12,'Y','4,5',1),
(13,'Y','1,2',3),
(14,'Z','1,2',2);
GO
DECLARE #StartDate date = '20200703',
#EndDate date = '20200709';
WITH CTE AS(
SELECT *,
MAX(ItemNumber) OVER (PARTITION BY EmployeeID) AS MaxItemNumber
FROM dbo.YourTable YT
CROSS APPLY dbo.DelimitedSplit8K_LEAD(YT.Pattern,',') DS),
N AS(
SELECT N
FROM (VALUES(NULL),(NULL),(NULL),(NULL),(NULL),(NULL),(NULL),(NULL),(NULL),(NULL))N(N)),
Tally AS(
SELECT TOP (SELECT DATEDIFF(DAY,#startDate, #EndDate)+1)
ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) - 1 AS I
FROM N N1, N N2, N N3) --1000 Rows
SELECT C.EmployeeID,
DATEADD(DAY,T.I, #StartDate),
C.Item
FROM CTE C
JOIN Tally T ON ISNULL(NULLIF((T.I +1) % C.MaxItemNumber,0),C.MaxItemNumber) = C.ItemNumber
ORDER BY EmployeeID,
T.I;
GO
DROP TABLE dbo.YourTable;
Like mentioned in the comments fix your data model.
Your output pattern is a little bit strange.
But is it something like this you are looking for?
DECLARE #startDate date = '2020-07-03'
DECLARE #endDate date = '2020-07-09'
DECLARE #Dates TABLE([Date] Date)
;WITH seq(n) AS
(
SELECT 0 UNION ALL SELECT n + 1 FROM seq
WHERE n < DATEDIFF(DAY, #StartDate, #endDate)
)
INSERT INTO #Dates ([Date])
SELECT DATEADD(Day,n, cast(GetDate() as date)) Date
FROM seq
ORDER BY n
OPTION (MAXRECURSION 0);
SELECT e.EmployeeId, d.Date, x.Value Pattern
FROM Employee e
CROSS APPLY STRING_SPLIT(e.Pattern, ',') x
INNER JOIN #Dates d on 1=1
-- Correct for the first iteration of the pattern
AND DATEDIFF(DAY, DATEADD(DAY, -1, #StartDate), d.Date) = x.Value

SQL # of days between different date ranges

How can I count the number of days between a start and end date in SQL?
ID | START | END
1 |2018-1-1 |2018-1-3
2 |2018-1-1 |2018-1-4
3 |2018-1-1 |2018-1-5
Ideally would return:
DATE | COUNT
2018-1-1 | 3
2018-1-2 | 3
2018-1-3 | 3
2018-1-4 | 2
2018-1-5 | 1
One option is to generate all dates between min start and max end with a recursive cte and then count them.
with dates(dt1,dt2) as (select min([start]),max([end])
from tbl
union all
select dateadd(day,1,dt1),dt2
from dates
where dt1 < dt2
)
select d.dt1,count(*)
from dates d
join tbl t on d.dt1 between t.[start] and t.[end]
group by d.dt1
Similar approach worked out with a complete example:
DECLARE #range TABLE
(
id INT NOT NULL IDENTITY(1,1),
s_date DATETIME NOT NULL,
e_date DATETIME NOT NULL
);
INSERT INTO #range
(s_date, e_date)
VALUES
('2018-1-1','2018-1-3'),
('2018-1-1','2018-1-4'),
('2018-1-1','2018-1-5');
DECLARE #date TABLE
(
date DATETIME NOT NULL
);
INSERT INTO #date
(date)
VALUES
('2018-1-1'), ('2018-1-1'), ('2018-1-1'),
('2018-1-2'), ('2018-1-2'), ('2018-1-2'),
('2018-1-3'), ('2018-1-3'), ('2018-1-3'),
('2018-1-4'), ('2018-1-4'),
('2018-1-5');
SELECT d.date, COUNT(DISTINCT r.id)
FROM #range r
JOIN #date d ON d.date BETWEEN r.s_date AND r.e_date
GROUP BY d.date
I post a solution to fill gaps with a simple generator:
Check it at SQL Fiddle
MS SQL Server 2017 Schema Setup:
create table d
( ID int, fSTART date, fEND date );
insert into d values
(1, '2018-1-1' ,'2018-1-3'),
(2, '2018-1-1' ,'2018-1-4'),
(3, '2018-1-1' ,'2018-1-5');
Query 1:
;WITH Nbrs_3( n ) AS ( SELECT 1 UNION SELECT 0 ),
Nbrs_2( n ) AS ( SELECT 1 FROM Nbrs_3 n1 CROSS JOIN Nbrs_3 n2 ),
Nbrs_1( n ) AS ( SELECT 1 FROM Nbrs_2 n1 CROSS JOIN Nbrs_2 n2 ),
Nbrs_0( n ) AS ( SELECT 1 FROM Nbrs_1 n1 CROSS JOIN Nbrs_1 n2 ),
Nbrs ( n ) AS ( SELECT 1 FROM Nbrs_0 n1 CROSS JOIN Nbrs_0 n2 ),
ns (n) as (SELECT ROW_NUMBER() OVER (ORDER BY n) FROM Nbrs )
select distinct dateadd( day, n-1,fSTART )
from d inner join ns on dateadd( day, n-1, fSTART ) between fStart and fend
order by 1
Results:
| |
|------------|
| 2018-01-01 |
| 2018-01-02 |
| 2018-01-03 |
| 2018-01-04 |
| 2018-01-05 |
I used cross apply to get your results:
I am assuming you want to know how many times that date is between a start and end date of the other table.
Setup:
declare #s table
( ID int, fSTART date, fEND date );
insert into #s values
(1, '2018-1-1' ,'2018-1-3'),
(2, '2018-1-1' ,'2018-1-4'),
(3, '2018-1-1' ,'2018-1-5');
declare #d table
(dte date)
insert into #d
values
('1/1/2018')
,('1/2/2018')
,('1/3/2018')
,('1/4/2018')
,('1/5/2018')
The Query:
select d.dte
,ct = sum(case when d.dte between s.fstart and s.fend then 1 else 0 end)
from #d d
cross apply #s s
group by dte
results:
dte ct
2018-01-01 3
2018-01-02 3
2018-01-03 3
2018-01-04 2
2018-01-05 1

Select invoices based on quantity needed

I have a table that looks like this:
+---------------+---------------+------------------+--------------+
| InvoiceNumber | ProductNumber | ReceivedQuantity | ReceivedDate |
+---------------+---------------+------------------+--------------+
| INV001 | P001 | 500 | 09/01/2015 |
| INV002 | P001 | 600 | 09/02/2015 |
| INV003 | P001 | 700 | 09/03/2015 |
+---------------+---------------+------------------+--------------+
When a product is ordered. System needs to know which invoice it gets it from. First in first out.
For example I need 1000 quantity of product number P001. It should select the following invoices. It does not display the last invoice since 500 + 600 is already sufficient quantity
+---------------+---------------+------------------+--------------+
| InvoiceNumber | ProductNumber | ReceivedQuantity | ReceivedDate |
+---------------+---------------+------------------+--------------+
| INV001 | P001 | 500 | 09/01/2015 |
| INV002 | P001 | 600 | 09/02/2015 |
+---------------+---------------+------------------+--------------+
I can replicate this by making a cursor and looping through the table but looking for the best way to achieve this. Any nudge to the right direction would help a lot.
I think you can use a query like this:
;WITH t As (
SELECT *
, ROW_NUMBER() OVER (ORDER BY ReceivedDate, InvoiceNumber) As RowNo
FROM yourTable
), firstOverflow AS (
SELECT TOP(1)
t1.RowNo
FROM t t1
LEFT JOIN
t t2 ON t1.ProductNumber = t2.ProductNumber AND t1.ReceivedDate >= t2.ReceivedDate
GROUP BY t1.RowNo, t1.InvoiceNumber, t1.ProductNumber, t1.ReceivedQuantity, t1.ReceivedDate
HAVING SUM(t2.ReceivedQuantity) >= 1000
ORDER BY SUM(t2.ReceivedQuantity) - 1000)
SELECT *
FROM t
JOIN
firstOverflow ON t.RowNo <= firstOverflow.RowNo;
A better solution is this:
DECLARE #value int = 1000;
WITH t As (
SELECT *
, ROW_NUMBER() OVER (ORDER BY ReceivedDate, InvoiceNumber) As seq
FROM yourTable
), s As (
SELECT t.InvoiceNumber, t.ProductNumber, t.ReceivedQuantity, t.ReceivedDate, SUM(tt.ReceivedQuantity) As currentTotal
FROM t
LEFT JOIN
t tt ON t.ProductNumber = tt.ProductNumber AND t.seq >= tt.seq
GROUP BY t.InvoiceNumber, t.ProductNumber, t.ReceivedQuantity, t.ReceivedDate
), st As (
SELECT *
, ROW_NUMBER() OVER (ORDER BY (CASE WHEN s.currentTotal > #value THEN -currentTotal ELSE Null END) DESC) As seq
FROM s)
SELECT st.InvoiceNumber, st.ProductNumber, st.ReceivedQuantity, st.ReceivedDate
FROM st
WHERE currentTotal < #value
UNION ALL
SELECT st.InvoiceNumber, st.ProductNumber, st.ReceivedQuantity, st.ReceivedDate
FROM st
WHERE currentTotal >= #value AND st.seq = 1;
Try this query and give some feedback:
DECLARE #table TABLE (InvoiceNumber nvarchar(100),
ProductNumber nvarchar(100),
ReceivedQuantity int)
INSERT INTO #table VALUES ('inv001', 'p001', 500)
INSERT INTO #table VALUES ('inv002', 'p001', 600)
INSERT INTO #table VALUES ('inv003', 'p001', 600)
INSERT INTO #table VALUES ('inv004', 'p001', 600)
SQL 2012:
SELECT v.* FROM
(
SELECT t.*,
SUM(ReceivedQuantity) OVER (PARTITION BY ProductNumber ORDER BY InvoiceNumber) AS sum
FROM #table t
) v
WHERE sum <= 1000
SQL 2008:
SELECT v.* FROM
(
SELECT
a.InvoiceNumber
, a.ProductNumber
, SUM(b.ReceivedQuantity) AS sum
FROM
#table a
INNER JOIN #table b
ON a.InvoiceNumber >= b.InvoiceNumber AND a.ProductNumber = b.ProductNumber
GROUP BY
a.InvoiceNumber
, a.ProductNumber
) v
WHERE sum <= 1000

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

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