如何获得总和等于给定值的行

时间:2011-06-09 07:12:56

标签: sql sql-server

有包含

的表格
ID     Qty
----------
1       2
2       4
3       1
4       5

现在,如果我必须选择数量总和等于10的行, 我怎么能这样做?

喜欢2 + 4 + 1 = 7 但如果我加5然后12

所以忽略2,然后 4 + 1 + 5 = 10

我怎样才能实现这个目标?

编辑:

我想要任何可能的组合,总结得到价值。 假设7然后任何总计最多7行 如果8,那么任何行总计达到8

希望行/行的组合等于给定值。

4 个答案:

答案 0 :(得分:5)

您要解决的问题称为subset sum问题。不幸的是,它是NP-complete

这意味着,无论您使用SQL还是其他任何语言来解决问题,您都只能解决问题的非常小的实例,即表中只有少数条目的实例。否则,运行时将变得过多,因为它随着表中的行数呈指数增长。这样做的原因是找到解决方案基本上没有比尝试所有可能的组合更好的方法。

如果可以接受近似解,那么有一个多项式时间算法,在维基百科页面上有描述。

答案 1 :(得分:2)

如果你想要它总是三个加到10的数字,那么这个

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

如果你想要2或4或5个数字,那么你无法在SQL中真正做到这一点

编辑:

  • 已更新为忽略ID而非数量
  • 再次:如果您想要2个或4个或5个数字,那么您无法在SQL中真正做到这一点

答案 2 :(得分:1)

当您在SQL中处理此类任务时,您需要使用游标方法。

游标允许您逐行执行操作,这就是您所需要的,例如:

  • 您希望制作总计数量为10的小组

这是任务

  • 将所有表格选择为临时表#TBL_ALL
  • 逐行循环,当行总计10时,从临时表中删除并插入最终临时表#TBL_FINAL
  • 执行此操作直到#TBL_ALL总和不能执行

一个例子:

测试表:

/****** 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

测试数据:

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;

商店程序:

  

http://pastebin.com/EFeZcKXf

结果:

分组表:

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

未使用的数字:

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

希望有所帮助


SP,适用于无法访问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;

PS 我不是SQL专业人员,如果我做了一些奇怪的事情,请不要理我这么做,并记住这是一个性能浪费,也许有人可以使用没有游标的循环, more in this page

答案 3 :(得分:0)

如果你总是添加3个数字,就像gbn所说的那样,如果没有那么你必须检查ur行的每个组合,这给你的number_of_rows功率组合提供了u 2,我不知道在一个查询中如何在sql上完成。如果你在sql中使用循环确定它可能,但你应该找到一些好的算法来完成这个任务。