繁体   English   中英

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

[英]Need T-SQL Query find all possible ways

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 中实现。

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

注意标题,他们也认为这是对数据库的不当使用。 我建议你用不同的语言解决这个问题。

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

在我的家庭系统上运行需要 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现在是一个临时表,它只计算一次
  • 各种产品CTE消失了,取而代之的是旋转的示例表
    • 产品 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 演示,总金额为 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

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

这是如何工作的

  • 基地作为单位的基地。
  • 单位数从 0 到 9
  • Counter 使用 Unit 从 0 到 9999 计数,或者您想花费的便宜钱除以便宜商品的价格所施加的限制。
  • 每件物品都有他的 CTE,可以在您的首都内多次计算该物品的价格。
  • 产品 CTE 交叉连接以检查货币范围内的每个组合。
  • TOP 10 之所以出现在这里,是因为可以使用不止一种组合来使用确切的金额。

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

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

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

这是硬编码的,几乎没有灵活性。 我的系统运行了 2 分钟。 但可能会有所帮助,抱歉,如果不是。 fnGenerate_Numbers 是一个表函数,它返回参数范围内的整数。 这样做的方法。

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM