Reputation: 7990
Sample data:
create table #temp (id int, qty int, checkvalue int)
insert into #temp values (1,1,3)
insert into #temp values (2,2,3)
insert into #temp values (3,1,3)
insert into #temp values (4,1,3)
According to data above, I would like to show exact number of lines from top to bottom where sum(qty) = checkvalue
. Note that checkvalue is same for all the records all the time. Regarding the sample data above, the desired output is:
Id Qty checkValue
1 1 3
2 2 3
Because 1+2=3 and no more data is needed to show. If checkvalue was 4, we would show the third record: Id:3 Qty:1 checkValue:4
as well.
This is the code I am handling this problem. The code is working very well.
declare @checkValue int = (select top 1 checkvalue from #temp);
declare @counter int = 0, @sumValue int = 0;
while @sumValue < @checkValue
begin
set @counter = @counter + 1;
set @sumValue = @sumValue + (
select t.qty from
(
SELECT * FROM (
SELECT
ROW_NUMBER() OVER (ORDER BY id ASC) AS rownumber,
id,qty,checkvalue
FROM #temp
) AS foo
WHERE rownumber = @counter
) t
)
end
declare @sql nvarchar(255) = 'select top '+cast(@counter as varchar(5))+' * from #temp'
EXECUTE sp_executesql @sql, N'@counter int', @counter = @counter;
However, I am not sure if this is the best way to deal with it and wonder if there is a better approach. There are many professionals here and I'd like to hear from them about what they think about my approach and how we can improve it. Any advice would be appreciated!
Upvotes: 1
Views: 1230
Reputation: 37440
Try this:
select id, qty, checkvalue from (
select t1.*,
sum(t1.qty) over (partition by t2.id) [sum]
from #temp [t1] join #temp [t2] on t1.id <= t2.id
) a where checkvalue = [sum]
Smart self-join is all you need :)
Upvotes: 1
Reputation: 95827
For SQL Server 2012, and onwards, you can easily achieve this using ROWS BETWEEN
in your OVER
clause and the use of a CTE:
WITH Running AS(
SELECT *,
SUM(qty) OVER (ORDER BY id
ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) AS RunningQty
FROM #temp t)
SELECT id, qty, checkvalue
FROM Running
WHERE RunningQty <= checkvalue;
Upvotes: 1
Reputation: 4146
For SQL 2008 you can use recursive cte
. Top 1 with ties
limits result with first combination. Remove it to see all combinations
with cte as (
select
*, rn = row_number() over (order by id)
from
#temp
)
, rcte as (
select
i = id, id, qty, sumV = qty, checkvalue, rn
from
cte
union all
select
a.id, b.id, b.qty, a.sumV + b.qty, a.checkvalue, b.rn
from
rcte a
join cte b on a.rn + 1 = b.rn
where
a.sumV < b.checkvalue
)
select
top 1 with ties id, qty, checkvalue
from (
select
*, needed = max(case when sumV = checkvalue then 1 else 0 end) over (partition by i)
from
rcte
) t
where
needed = 1
order by dense_rank() over (order by i)
Upvotes: 0
Reputation: 752
One basic improvement is to try & reduce the no. of iterations. You're incrementing by 1, but if you repurpose the logic behind binary searching, you'd get something close to this:
DECLARE @RoughAverage int = 1 -- Some arbitrary value. The closer it is to the real average, the faster things should be.
DECLARE @CheckValue int = (SELECT TOP 1 checkvalue FROM #temp)
DECLARE @Sum int = 0
WHILE 1 = 1 -- Refer to BREAK below.
BEGIN
SELECT TOP (@RoughAverage) @Sum = SUM(qty) OVER(ORDER BY id)
FROM #temp
ORDER BY id
IF @Sum = @CheckValue
BREAK -- Indicating you reached your objective.
ELSE
SET @RoughAverage = @CheckValue - @Sum -- Most likely incomplete like this.
END
Upvotes: 0