【问题标题】:Need T-SQL Query find all possible ways需要 T-SQL Query 找到所有可能的方法
【发布时间】:2014-04-19 05:57:43
【问题描述】:
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 美元

有什么帮助吗?

【问题讨论】:

  • 你想在t-sql中实现knapsack problem的解决方案吗?
  • 不清楚你是否想知道用 1000 美元可以购买多少种产品,或者用 1000 美元可以购买多少种产品以及哪些产品组合。
  • 我可以用 1000 美元购买多少和哪些产品组合。应该是所有产品的混合。
  • 还有其他限制吗?如果提出 100 支笔或 80 部电影的解决方案,这是否有效?
  • 为什么是 price varchar?

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


【解决方案1】:

您所指的问题也称为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 我猜@flup 在数据库中不这样做是对的 - 你可以在 CLR 程序 instaead msdn.microsoft.com/en-us/library/8bs2ecf4(v=vs.110).aspx
  • @vignesh 不确定,链接在这里工作正常。但关键是,即使是这个作者,在实现这个的时候,也认为 sql 不是要走的路。
  • @gotqn 是的,如果这不是一种智力练习,那么这似乎是解决此问题的最佳方法,但留在 sql server 中是绝对的限制。
  • +1 获取有关该问题的历史记录。顺便说一句,谢谢编辑。 :)
【解决方案2】:

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

在我的家庭系统上运行需要 2.6 到 2.8 秒。
在 SQLFiddle 上,前几次运行可能需要更多时间,然后稳定在 1.8 秒左右。

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 现在是一个临时表,它只计算一次
  • 各种产品CTEs 不见了,取而代之的是旋转的示例表
    • Products CTE 中的 CROSS JOIN 已消失,它们仍保留在主选择中,但少四个 CROSS JOIN 总是好的
  • TOP 消失了,WHERE 条件只负责显示完美的解决方案
  • 在主选择中,我们现在有LEFT JOIN... 它们仍然是CROSS JOIN,使用LEFT 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 的单位计数
  • 计数器使用 Unit 从 0 到 9999 计数,或者您想花费的较便宜的钱除以较便宜商品的价格所施加的限制。
  • 每件商品都有其 CTE,可以在您的首都内多次计算该商品的价格。
  • 产品 CTE 交叉连接,以检查金额限制内的每个组合。
  • TOP 10 之所以出现,是因为可以有不止一种组合使用确切的金额。

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

【讨论】:

    【解决方案3】:

    这是硬编码的,灵活性很小。我的系统运行了 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
    

    【讨论】:

      【解决方案4】:

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

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

      【讨论】:

      • 这是针对单一产品的.. 我已经问过我可以用 1000 美元购买多少以及哪些产品组合
      • 您可以考虑修改问题以澄清这一点。 “组合”这个词应该出现在某个地方。
      猜你喜欢
      • 2010-10-07
      • 1970-01-01
      • 1970-01-01
      • 2010-11-21
      • 1970-01-01
      • 2022-01-23
      • 2016-08-04
      • 2011-05-20
      相关资源
      最近更新 更多