0

I have Table A with the following data:

Id  Value
1   100
2   63
4   50
6   24
7   446

I want to select the first rows with a SUM(value) <= 200.

So the desired output should be:

Id Value
1   100
4   50
6   24
cimmanon
  • 67,211
  • 17
  • 165
  • 171
user1753385
  • 105
  • 10
  • 2
    Which version of sql-server? In 2012 you can use ROWS UNBOUNDED PRECEDING to get the cumulative sum. http://stackoverflow.com/questions/2120544/how-to-get-cumulative-sum – Tim Schmelter Aug 12 '15 at 09:04
  • 1
    @TimSchmelter, seems OP wants to get `Value` less then or equal to 200 only, not cumulative sum. – Maciej Los Aug 12 '15 at 09:08
  • `SELECT Id, Value FROM TableName WHERE Value <=200` – Maciej Los Aug 12 '15 at 09:08
  • I think you need to add few more values into your data sample to make it more clear. now it is confusing. – Bulat Aug 12 '15 at 09:13
  • 1
    i mean that the total of the values is <=200 not just value<=200 – user1753385 Aug 12 '15 at 09:13
  • Will the 6 people that up voted this please read [Should I be careful when upvoting questions](http://meta.stackoverflow.com/a/302331/578411) – rene Aug 12 '15 at 12:08

4 Answers4

8

SIMPLE ANSWER

You need to find the Cumulative Sum for each row, and since you want as many rows as possible, you need to start with the lowest value (ORDER BY Value):

WITH Data AS
(   SELECT  Id, 
            Value,
            CumulativeValue = SUM(Value) OVER(ORDER BY Value, Id)
    --FROM  (VALUES (1, 100), (2, 300), (4, 50), (6, 24), (7, 446)) AS t (Id, Value)
    FROM    TableA AS t
)
SELECT  d.Id, d.Value
FROM    Data AS d
WHERE   d.CumulativeValue <= 200
ORDER BY d.Id;  

COMPLETE ANSWER

If you want to be more selective about which rows you choose that have a sum of less than 200 then it gets a bit more complicated for example, in your new sample data:

Id  Value
1   100
2   63
4   50
6   24
7   446

There are 3 different combinations that allow for a total of less than 200:

Id  Value
1   100
2   63
6   24

--> 187

Id  Value
2   63
4   50
6   24

--> 137

Id  Value
1   100
4   50
6   24

--> 174

The only way to do this is to get all combinations that have a sum less than 200, then choose the combination that you want, in order to do this you will need to use a recursive common table expression to get all combinations:

WITH TableA AS
(   SELECT  Id, Value
    FROM    (VALUES (1, 100), (2, 63), (4, 50), (6, 24), (7, 446)) t (Id, Value)
), CTE AS
(   SELECT  Id,
            IdList = CAST(Id AS VARCHAR(MAX)), 
            CumulativeValue = Value,
            ValueCount = 1
    FROM    TableA AS t
    UNION ALL
    SELECT  T.ID, 
            IdList = CTE.IDList + ',' + CAST(t.ID AS VARCHAR(MAX)),
            CumulativeValue = CTE.CumulativeValue + T.Value,
            ValueCount = CTE.ValueCount + 1
    FROM    CTE
            INNER JOIN TableA AS T
                ON ',' + CTE.IDList + ',' NOT LIKE '%,' + CAST(t.ID AS VARCHAR(MAX)) + ',%'
                AND CTE.ID < T.ID
    WHERE   T.Value + CTE.CumulativeValue <= 200
)
SELECT  *
FROM    CTE
ORDER BY ValueCount DESC, CumulativeValue DESC;

This outputs (with single rows removed)

Id  IdList  CumulativeValue ValueCount
-------------------------------------
6   1,2,6       187         3
6   1,4,6       174         3
6   2,4,6       137         3
2   1,2         163         2
4   1,4         150         2
6   1,6         124         2
4   2,4         113         2
6   2,6         87          2
6   4,6         74          2

So you need to choose which combination of rows best meets your requirements, for example if you wanted, as described before, the most number of rows with a value as close to 200 as possible, then you would need to choose the top result, if you wanted the lowest total, then you would need to change the ordering.

Then you can get your original output by using EXISTS to get the records that exist in IdList:

WITH TableA AS
(   SELECT  Id, Value
    FROM    (VALUES (1, 100), (2, 63), (4, 50), (6, 24), (7, 446)) t (Id, Value)
), CTE AS
(   SELECT  Id,
            IdList = CAST(Id AS VARCHAR(MAX)), 
            CumulativeValue = Value,
            ValueCount = 1
    FROM    TableA AS t
    UNION ALL
    SELECT  T.ID, 
            IdList = CTE.IDList + ',' + CAST(t.ID AS VARCHAR(MAX)),
            CumulativeValue = CTE.CumulativeValue + T.Value,
            ValueCount = CTE.ValueCount + 1
    FROM    CTE
            INNER JOIN TableA AS T
                ON ',' + CTE.IDList + ',' NOT LIKE '%,' + CAST(t.ID AS VARCHAR(MAX)) + ',%'
                AND CTE.ID < T.ID
    WHERE   T.Value + CTE.CumulativeValue <= 200
), Top1 AS
(   SELECT  TOP 1 IdList, CumulativeValue
    FROM    CTE
    ORDER BY ValueCount DESC, CumulativeValue DESC -- CHANGE TO MEET YOUR NEEDS
)
SELECT  *
FROM    TableA AS t
WHERE   EXISTS
        (   SELECT  1
            FROM    Top1
            WHERE   ',' + Top1.IDList + ',' LIKE '%,' + CAST(t.ID AS VARCHAR(MAX)) + ',%'
        );

This is not very efficient, but I can't see a better way at the moment.

This returns

Id  Value
1   100
2   63
6   24

This is the closest to 200 you can get with the most possible rows. Since there are multiple ways of achieving "x number of rows that have a sum of less than 200", there are also multiple ways of writing the query. You would need to be more specific about what your preference of combination is in order to get the exact answer you need.

GarethD
  • 68,045
  • 10
  • 83
  • 123
  • Your code seem to work fine but this part FROM (VALUES (1, 100), (2, 300), (4, 50), (6, 24), (7, 446)) AS t (Id, Value) i dont know how to select – user1753385 Aug 12 '15 at 09:20
  • @user1753385: that's just sample data. Replace it with your real table. – Tim Schmelter Aug 12 '15 at 09:21
  • Why `ORDER BY Value, Id` and not just `ORDER BY Id` ? OP wants ti get all rows where the cumulative sum is <= 200 ordered by the ID. At least that is how i understood the question(_"select the first rows with a sum <=200"_). – Tim Schmelter Aug 12 '15 at 09:26
  • WITH Data AS ( SELECT Id, Value, CumulativeValue = SUM(Value) OVER(ORDER BY Value, Id) FROM (select id,value from TableA) ) SELECT d.Id, d.Value FROM Data AS d WHERE d.CumulativeValue <= 200 ORDER BY d.Id; i get this error Msg 102, Level 15, State 1, Line 6 Incorrect syntax near ')'. – user1753385 Aug 12 '15 at 09:28
  • @TimSchmelter The cumulative totals in order of ID are `100, 163, 213, 237, 683`, therefore a filter of `<= 200` would only return the first two rows, and not add the third row of 24 in, and this does not match the OPs expected output. – GarethD Aug 12 '15 at 09:41
  • @user1753385 I have updated the answer to remove the reference to sample data, and replaced it with `TableA` – GarethD Aug 12 '15 at 09:42
4

This should return expected result:

WITH TableWithTotals AS
(SELECT 
 id, 
 value,
 SUM (Value) OVER (ORDER BY Value, Id) as total
FROM myTable)
SELECT * FROM TableWithTotals
WHERE total <=200;

This code will maximise the number of records that fit under 200 limit as running total is calculated over ordered values.

SQL Fiddle

Bulat
  • 6,869
  • 1
  • 29
  • 52
1

As per i understood your requirement, i think below query should be helpful to you.

WITH FinalResult AS
(SELECT 
 id, 
 value,
 SUM (Value) OVER (ORDER BY Value, Id) as ValueCount
FROM TableA)
SELECT * FROM FinalResult
WHERE ValueCount <=200;
Chiragkumar Thakar
  • 3,616
  • 5
  • 37
  • 49
-1

you should try this:-

    SELECT Id, Value FROM TableName WHERE Value <=200
david sam
  • 521
  • 1
  • 8
  • 25