How to get rows having sum equal to given value

后端 未结 4 921
南旧
南旧 2020-12-19 10:19

There is table contain

ID     Qty
----------
1       2
2       4
3       1
4       5

Now if i had to choose rows where sum of Qty equals to

相关标签:
4条回答
  • 2020-12-19 11:05

    If u add always 3 numbers its like gbn said, if not then u have to check every combination of ur rows which gives u 2 to the number_of_rows power cobinations and i dont see how can it be done on sql in one query. If u use loop in sql sure its possible but u should find some good algorithm to finish this task.

    0 讨论(0)
  • 2020-12-19 11:06

    If you want it to be exactly always three numbers that add to 10, then this

    SELECT
      *
    FROM 
      MyTable t1
      JOIN
      MyTable t2 ON t1.ID <> t2.ID 
      JOIN
      MyTable t3 ON t1.ID <> t3.ID AND t2.ID <> t3.ID
    WHERE
      t1.Qty + t2.Qty + t3.Qty = 10
    

    If you want 2 or 4 or 5 numbers then you can't really do it in SQL

    Edit:

    • Updated to ignore by ID not Qty
    • And again: If you want 2 or 4 or 5 numbers then you can't really do it in SQL
    0 讨论(0)
  • 2020-12-19 11:15

    When you are dealing with such task in SQL you need to go to the cursors approach.

    Cursors let you perform row by row operations and that's what you need, for example:

    • You want to make groups of where the summed quantity is 10

    This are the tasks

    • select all the table into a temp table #TBL_ALL
    • loop row by row and when the rows sum up 10, delete from the temp table and insert into a final temp table #TBL_FINAL
    • do this until the #TBL_ALL sum cant be performed

    An example:

    Test table:

    /****** Object:  Table [dbo].[tblExample]    Script Date: 06/09/2011 11:25:27 ******/
    SET ANSI_NULLS ON
    GO
    
    SET QUOTED_IDENTIFIER ON
    GO
    
    CREATE TABLE [dbo].[tblExample](
        [Id] [int] IDENTITY(1,1) NOT NULL,
        [Qty] [int] NOT NULL,
     CONSTRAINT [PK_tblExample] PRIMARY KEY CLUSTERED 
    (
        [Id] ASC
    )WITH (PAD_INDEX  = OFF, STATISTICS_NORECOMPUTE  = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS  = ON, ALLOW_PAGE_LOCKS  = ON) ON [PRIMARY]
    ) ON [PRIMARY]
    
    GO
    

    Test Data:

    INSERT INTO tblExample SELECT 2;
    INSERT INTO tblExample SELECT 4;
    INSERT INTO tblExample SELECT 1;
    INSERT INTO tblExample SELECT 5;
    INSERT INTO tblExample SELECT 5;
    INSERT INTO tblExample SELECT 11;
    INSERT INTO tblExample SELECT 1;
    INSERT INTO tblExample SELECT 2;
    INSERT INTO tblExample SELECT 3;
    INSERT INTO tblExample SELECT 4;
    INSERT INTO tblExample SELECT 7;
    INSERT INTO tblExample SELECT 9;
    INSERT INTO tblExample SELECT 1;
    INSERT INTO tblExample SELECT 2;
    

    Store procedure:

    http://pastebin.com/EFeZcKXf

    Results:

    Grouped table:

    ids qty group
    12  9   1
    7   1   1
    11  7   2
    9   3   2
    4   5   3
    5   5   3
    2   4   4
    10  4   4
    14  2   4
    

    Numbers not used:

    id  qty 
    1   2
    8   2
    3   1
    13  1
    6   11
    

    Hope it helps


    SP for those who cant access PasteBin

    CREATE PROCEDURE getGroups
    (
        @groupByQty int, -- grouping number
        @numberRuns int  -- how many loops
    
        -- usage: getGroups 10, 10
    )
    AS
    
    SET NOCOUNT ON;
    
    -- declare all variables
    DECLARE  @rowId      int,
             @rowQty     int,
             @rowTotal   int,
             @groupId    int,
             @totalRuns  int,
             @continue   bit
    
    -- set up our final temporary table
    CREATE TABLE #TBL_COUNT
    (
      ids NVARCHAR(4000),
      qty int,
      [group] int
    )
    
    -- initializate variable
    SET @groupId = 1;
    SET @continue = 1;
    SET @totalRuns = 0;
    SELECT Id, Qty INTO #TBL_ALL FROM tblExample ORDER BY Qty DESC;
    
    WHILE @totalRuns <= @numberRuns 
    BEGIN
        -- declare the cursor
        DECLARE Product CURSOR FOR SELECT Id, Qty FROM #TBL_ALL ORDER BY Qty DESC;
    
        OPEN Product;
        FETCH Product INTO @rowId, @rowQty;
    
        PRINT ' ';
        PRINT '### Run: ' + CAST(@totalRuns AS nvarchar(10)) + ' #################################################################';
        PRINT 'Grouping Table by ' + CAST(@groupByQty AS nvarchar(10)) + ' | group id = ' + CAST(@groupId AS nvarchar(10));
    
        -- Retrieve and process the first row
    
        SELECT Top 1 @rowId = Id, @rowQty = Qty FROM #TBL_ALL ORDER BY Qty DESC;
        PRINT 'First Row: id = ' + CAST(@rowId AS nvarchar(10)) + ' | qty = ' + CAST(@rowQty AS nvarchar(10));
    
        -- sum it up and see if we have @groupByQty
        SELECT @rowTotal = ISNULL(SUM(qty),0) FROM #TBL_COUNT WHERE [group] = @groupId;
        PRINT 'Current sum in #TBL_COUNT: @groupId = '+ CAST(@groupId AS nvarchar(10)) +' | @rowTotal = ' + CAST(@rowTotal AS nvarchar(10)) + ' | (@rowTotal + @rowQty) = ' + CAST((@rowTotal + @rowQty) AS nvarchar(10));
    
        IF @rowQty > @groupByQty
        BEGIN
            PRINT '  x First row has an unused number';
        END
        ELSE
        BEGIN
          -- handle result
          IF (@rowTotal + @rowQty) = @groupByQty
          BEGIN
    
            PRINT '+++ Current sum is ' + CAST(@groupByQty AS nvarchar(10)) + ' +++';
    
            -- save number
            INSERT INTO #TBL_COUNT SELECT @rowId, @rowQty, @groupId;
            PRINT '### Inserted final # into #TBL_COUNT : id = ' + CAST(@rowId AS nvarchar(10)) + ' | qty = ' + CAST(@rowQty AS nvarchar(10)) + ' | group = ' + CAST(@groupId AS nvarchar(10));
    
            -- remove from table as we use it already
            DELETE FROM #TBL_ALL WHERE Id = @rowId;
    
            -- we got 10, let's change our Groupping
            SET @groupId = (@groupId + 1);
    
            PRINT 'New group id: ' + CAST(@groupId AS nvarchar(10));
    
          END
          ELSE 
          BEGIN   
            IF (@rowTotal + @rowQty) < @groupByQty
            BEGIN
                PRINT '### Inserted into #TBL_COUNT : id = ' + CAST(@rowId AS nvarchar(10)) + ' | qty = ' + CAST(@rowQty AS nvarchar(10)) + ' | group = ' + CAST(@groupId AS nvarchar(10));
    
                -- save number 
                INSERT INTO #TBL_COUNT SELECT @rowId, @rowQty, @groupId;
    
                -- remove from table as we use it already
                DELETE FROM #TBL_ALL WHERE Id = @rowId;
            END
            ELSE
            BEGIN
                PRINT '  x Unmatch number, will handle this latter';
            END
          END
        END 
    
        -- start the main processing loop
        WHILE @@Fetch_Status = 0
           BEGIN
    
              FETCH Product INTO @rowId, @rowQty;
              PRINT '@@Fetch_Status = ' + CAST(@@Fetch_Status AS nvarchar(100));
    
              IF @@Fetch_Status < 0
              BEGIN
                BREAK
              END
    
              -- we have the values of our row, let's use them
              PRINT 'Fetched Row: id = ' + CAST(@rowId AS nvarchar(10)) + ' | qty = ' + CAST(@rowQty AS nvarchar(10));
    
              -- sum it up and see if we have @groupByQty
              SELECT @rowTotal = ISNULL(SUM(qty),0) FROM #TBL_COUNT WHERE [group] = @groupId;
              PRINT 'Current sum in #TBL_COUNT: @groupId = '+ CAST(@groupId AS nvarchar(10)) +' | @rowTotal = ' + CAST(@rowTotal AS nvarchar(10)) + ' | (@rowTotal + @rowQty) = ' + CAST((@rowTotal + @rowQty) AS nvarchar(10));
    
              -- handle result
              IF (@rowTotal + @rowQty) = @groupByQty
              BEGIN
    
                PRINT '+++ Current sum is ' + CAST(@groupByQty AS nvarchar(10)) + ' +++';
    
                -- save number
                INSERT INTO #TBL_COUNT SELECT @rowId, @rowQty, @groupId;
                PRINT '### Inserted final # into #TBL_COUNT : id = ' + CAST(@rowId AS nvarchar(10)) + ' | qty = ' + CAST(@rowQty AS nvarchar(10)) + ' | group = ' + CAST(@groupId AS nvarchar(10));
    
                -- remove from table as we use it already
                DELETE FROM #TBL_ALL WHERE Id = @rowId;
    
                -- we got 10, let's change our Groupping
                SET @groupId = (@groupId + 1);
    
                PRINT 'New group id: ' + CAST(@groupId AS nvarchar(10));
    
                -- start again
                BREAK; 
    
              END
              ELSE 
              BEGIN   
                IF (@rowTotal + @rowQty) < @groupByQty
                BEGIN
                    PRINT '### Inserted into #TBL_COUNT : id = ' + CAST(@rowId AS nvarchar(10)) + ' | qty = ' + CAST(@rowQty AS nvarchar(10)) + ' | group = ' + CAST(@groupId AS nvarchar(10));
    
                    -- save number 
                    INSERT INTO #TBL_COUNT SELECT @rowId, @rowQty, @groupId;
    
                    -- remove from table as we use it already
                    DELETE FROM #TBL_ALL WHERE Id = @rowId;
                END
                ELSE
                BEGIN
                    PRINT '  x Unmatch number, will handle this latter';
                END
              END
    
           END -- END WHILE @@Fetch_Status = 0
    
           SET @totalRuns = @totalRuns + 1;
    
           -- Close and dealocate
           CLOSE Product;
           DEALLOCATE Product;
    
       END -- END WHILE totalRuns <= @numberRuns
    
       -- let's sum our last group and remove it if it's not @groupByQty
       SELECT @rowTotal = ISNULL(SUM(qty),0) FROM #TBL_COUNT WHERE [group] = @groupId;
       IF @rowTotal <> @groupByQty
       BEGIN
        SET IDENTITY_INSERT #TBL_ALL ON
        INSERT INTO #TBL_ALL (Id, Qty) SELECT Ids, Qty FROM #TBL_COUNT WHERE [group] = @groupId;
        DELETE FROM #TBL_COUNT WHERE [group] = @groupId;
       END
    
    SET NOCOUNT OFF;
    
    -- Show and Delete temp tables
    SELECT * FROM #TBL_COUNT;
    SELECT * FROM #TBL_ALL;
    DROP TABLE #TBL_COUNT;
    DROP TABLE #TBL_ALL;
    

    P.S. I'm not a SQL Professional, so bear with me if I did something weird, and keep in mind that this is a performance waste, maybe someone can use the the Loop without Cursors, more in this page

    0 讨论(0)
  • 2020-12-19 11:17

    The problem you want to solve is called the subset sum problem. Unfortunately, it is NP-complete.

    This means that, whether you use SQL or any other language to solve it, you will only be able to solve very small instances of the problem, i.e. ones with only a few entries in the table. Otherwise, the runtime will become excessive, since it grows exponentially with the number of rows in the table. The reason for this is that there is essentially no better way of finding the solution than to try all possible combinations.

    If an approximate solution is acceptable, there is a polynomial time algorithm, which is described on the Wikipedia page.

    0 讨论(0)
提交回复
热议问题