需要T-SQL查找找到所有可能的方法

时间:2014-04-19 05:57:44

标签: sql sql-server sql-server-2008 tsql knapsack-problem

create table #sample (
    product varchar(100),
    Price float
) 

insert into #sample values ('Pen',10)
insert into #sample values ('DVD',29)
insert into #sample values ('Pendrive',45)
insert into #sample values ('Mouse',12.5)
insert into #sample values ('TV',49)

select * from #sample 

考虑这种情况......

我有1000美元,我想购买上面列出的东西。

我想花掉全部金额

所以我需要一个查询,它会显示所有产品中的单位成本为1000美元

任何帮助?

4 个答案:

答案 0 :(得分:3)

您所指的问题也称为knapsack problem。您可以使用一系列算法来解决此问题。最着名的是动态编程,它要求权重是整数,因此您必须以美分为单位。它们都不容易在t-sql中实现。

我实际上找到了某人在sql server中实现的链接:http://sqlinthewild.co.za/index.php/2011/02/22/and-now-for-a-completely-inappropriate-use-of-sql-server/

注意标题,他们也发现它不适合使用数据库。 我建议您用其他语言解决此问题。

答案 1 :(得分:1)

通过将当前项目的空间限制为尚未花费的资金,可以删除大量数据。

在我的家庭系统上运行需要2600毫秒到2800毫秒 在SQLFiddle上,前几次运行可能需要更多,然后它稳定在1800ms左右。

WITH Unit(N) AS (
  SELECT N
  FROM   (VALUES (0), (1), (2), (3), (4), (5), (6), (7), (8), (9)) t(N)
), Counter(N) AS (
  SELECT u.n + 10*te.n + 100*hu.n
  FROM   Unit u CROSS JOIN Unit te CROSS JOIN Unit hu
  WHERE  u.n + 10*te.n + 100*hu.n <= (SELECT 1000 / Min(Price) FROM Sample))
SELECT N INTO #Counter FROM Counter;

WITH Products AS (
  SELECT [Pen], [DVD], [PenDrive], [Mouse], [TV]
  FROM   (SELECT product, price FROM sample) s PIVOT
         (MAX(price) FOR product IN ([Pen], [DVD], [PenDrive], [Mouse], [TV])) p
)
SELECT cP.N Pen, cD.N DVD, cPe.N PenDrive, cM.N Mouse
     , CAST((1000 - p.pen * cP.N - p.DVD * cD.N 
           - p.PenDrive * cPe.N - p.Mouse * cM.N) / p.TV as INT) TV
     , Money = p.pen * cP.N + p.DVD * cD.N + p.PenDrive * cPe.N
             + p.Mouse * cM.N 
             + p.TV * CAST((1000 - p.pen * cP.N - p.DVD * cD.N 
                          - p.PenDrive * cPe.N - p.Mouse * cM.N) / p.TV as INT)
From   Products p
       LEFT  Join #Counter cP ON cP.N  <= (1000 / p.Pen)
       LEFT  Join #Counter cD ON cD.N  <= ((1000 - p.pen * cP.N) / p.DVD)
       LEFT  Join #Counter cPe 
       ON cPe.N <= ((1000 - p.pen * cP.N - p.DVD * cD.N) / p.PenDrive)
       LEFT  Join #Counter cM 
       ON cM.N  <= ((1000 - p.pen * cP.N - p.DVD * cD.N
                   - p.PenDrive * cPe.N) / p.Mouse)
WHERE  p.pen * cP.N + p.DVD * cD.N
     + p.PenDrive * cPe.N + p.Mouse * cM.N 
     + p.TV * CAST((1000 - p.pen * cP.N - p.DVD * cD.N - p.PenDrive * cPe.N
                  - p.Mouse * cM.N) / p.TV as INT) = 1000

改变了什么

  • #Counter现在是临时表,只计算一次
  • 各种商品CTE已消失,取而代之的是已旋转的样本表
    • 产品CTE中的CROSS JOIN消失了,它们保留在主要选择中,但少了CROSS JOIN总是好的
  • TOP已消失,WHERE合同只会显示完美的解决方案
  • 在主要选择中,我们现在LEFT JOIN ... nope 它们仍然是CROSS JOINLEFT JOIN被使用,因为CROSS JOIN没有用于限制行数的ON子句

工作原理
产品价格不透明,可以通过名称来获得产品价格。 (列名)。
FROM块的工作方式类似于4缩进FOR,其中(1000 - 已用完)/ price子句将计数器仅限制为不超过1000 $的值。
最后一个产品总是通过差异计算(多少$仍未花费/价格),完全放弃CROSS JOIN

SQLFiddle Demo 以1000美元作为总金额 提供的数据有3531解决方案


旧答案

如果你想让你的服务器一直在你的午餐运行,这是一个愚蠢的解决方案 请注意,这个解决方案可以探索问题的所有空间,因此性能最好是糟糕的。

WITH Base(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)
, Unit(N) AS (
  SELECT Row_Number() Over (ORDER BY (SELECT NULL)) - 1
  FROM   Base)
, Counter(N) AS (
  SELECT u.n + 10*te.n + 100*hu.n + 1000*th.n
  FROM   Unit u
         CROSS JOIN Unit te --tens
         CROSS JOIN Unit hu --hundreds
         CROSS JOIN Unit th --thousands
  WHERE  u.n + 10*te.n + 100*hu.n + 1000*th.n <= (SELECT 1000 / Min(Price) 
                                                  FROM Sample))
, Pens AS (
  SELECT product, Price = price * N, Quantity = N
  FROM   sample CROSS JOIN Counter
  WHERE  product = 'Pen' AND  N <= 1000 / Price)
, DVDs AS (
  SELECT product, Price = price * N, Quantity = N
  FROM   sample CROSS JOIN Counter
  WHERE  product = 'DVD' AND  N <= 1000 / Price)
, Pendrives AS (
  SELECT product, Price = price * N, Quantity = N
  FROM   sample CROSS JOIN Counter
  WHERE  product = 'Pendrive' AND  N <= 1000 / Price)
, Mouses AS (
  SELECT product, Price = price * N, Quantity = N
  FROM   sample CROSS JOIN Counter
  WHERE  product = 'Mouse' AND  N <= 1000 / Price)
, TVs AS (
  SELECT product, Price = price * N, Quantity = N
  FROM   sample CROSS JOIN Counter
  WHERE  product = 'TV' AND  N <= 1000 / Price
  )
SELECT TOP 10
       Pen = p.Quantity
     , DVD = d.Quantity
     , Pendrive = pe.Quantity
     , Mouse = m.Quantity
     , TV = t.Quantity
     , Price = p.Price + d.price + pe.price + m.price + t.price
FROM   pens p
       CROSS JOIN DVDs d
       CROSS JOIN Pendrives pe
       CROSS JOIN Mouses m
       CROSS JOIN TVs t
WHERE  p.Price + d.price + pe.price + m.price + t.price <= 1000
ORDER BY p.Price + d.price + pe.price + m.price + t.price DESC

SQLFiddle Demo 以100美元作为总金额(运行约需2秒)
SQLFiddle Demo 以200美元作为总金额(运行约需6秒)
1000美元的演示可能会导致超时

这项工作如何

  • 基地作为单位的基础。
  • 单位数从0到9
  • 计数器使用单位从0到9999计数,或者由您想要花费的更便宜的钱所施加的限制除以更便宜的物品的价格。
  • 每件物品都有他的CTE,可以在您的首都内任意次数计算该物品的价格。
  • 产品CTE交叉连接以检查金额限制内的每个组合。
  • TOP 10在这里是因为可以有多个组合使用确切的金额。

只是说是的,你可以在SQLServer中设计一个解决方案,它甚至不是那么困难,但这并不意味着你应该这样做。

答案 2 :(得分:0)

如果我正确理解了问题陈述,那么这是一个非常简单的查询:

select product, price, floor(1000 / price) as QtyToBuy

答案 3 :(得分:0)

这是硬编码的,几乎没有灵活性。我的系统运行2分钟。但可能会有所帮助,如果不是,那就很抱歉。 fnGenerate_Numbers是一个表函数,它返回参数范围内的整数。 Ways to do that.

DECLARE @Max INT,
        @Pens money,
        @Dvds money,
        @Pendrives money,
        @Mouses money,
        @Tvs money

SELECT @Max = 1000,
       @Pens = 10,
       @Dvds = 29,
       @Pendrives = 45,
       @Mouses = 12.5,
       @Tvs = 49    


;WITH Results AS
(
    SELECT p.n pens, d.n dvds, pd.n pendrives, m.n mouses, t.n tvs, tot.cost
    FROM fnGenerate_Numbers(0, @Max/@Pens) p -- Pens
        CROSS JOIN fnGenerate_Numbers(0, @Max/@Dvds) d -- DVDs
        CROSS JOIN fnGenerate_Numbers(0, @Max/@Pendrives) pd -- Pendrives
        CROSS JOIN fnGenerate_Numbers(0, @Max/@Mouses) m -- Mouses
        CROSS JOIN fnGenerate_Numbers(0, @Max/@Tvs) t -- Tvs
        CROSS APPLY (SELECT p.n * @Pens + d.n * @Dvds + pd.n * + @Pendrives + m.n * @Mouses + t.n * @Tvs cost) tot
    WHERE tot.cost < @Max
), MaxResults AS
(
    SELECT 
        MAX(pens) pens,
        dvds,
        pendrives,
        mouses,
        tvs
    FROM Results
    GROUP BY
        dvds,
        pendrives,
        mouses,
        tvs
)
SELECT mr.*, r.cost FROM MaxResults mr
    INNER JOIN Results r ON mr.pens = r.pens 
        AND mr.dvds = r.dvds
        AND mr.pendrives = r.pendrives
        AND mr.mouses = r.mouses
        AND mr.tvs = r.tvs
ORDER BY cost