选择a * b =总计<= X

时间:2019-02-01 22:26:59

标签: sql-server tsql

我遇到一种情况,即编写查询以查找表A中所有行的组合并将其插入表B中,条件是:

a x b =第1行的总数
c x d =第2行的总计...等其中count(total)<= X
商品的“ a”价格
“ b”个项目

想法是有所有组合等例子

有关$ 100美元,我可以买:

2 tshirt, 1 jacket, 1 pants  

1 tshirt, 2 jacket, 1 pants

...等

创建游标将帮助我为每一行运行查询,但是如何同时在col.quantity中拆分数字呢?

2 个答案:

答案 0 :(得分:1)

我将首先写下我的理解,

  • 我们将有一个项目表,每个项目都有一个价格,
  • 我们有很多钱,我们想购买尽可能多的钱 项目
  • 我们希望这些物品与两个示例的重量相同 提供了“ 2件T恤,1件夹克,1条裤子或1件T恤,2件夹克,1条裤子” 没有指定一项解决方案,而是尝试使用所有 项目。

那么如何确定每个项目的数量以利用我们拥有的大部分资金。

我认为可以用另一种方式更清楚地描述这一点,例如:-一个人去一家商店,想购买每种可用的物品,但是如果他还有钱,他想知道他还能用什么买其他东西。如果物品不多,钱不多,这很容易,但是如果物品多,钱也多,我可以看出这可能是个问题。所以让我们找到解决方案。

Declare @Items Table (
    Item varchar(250),Price decimal
)

insert into @Items values 
 ('tshirt',30)
,('jacket',30)
,('pants' ,10)
--,('shoe' ,15)   ---extra items for testing
--,('socks',5)    ---extra items for testing

Declare @total int=100 -- your X
Declare @ItemsCount int
Declare @flag int
Declare @ItemsSum decimal
Declare @AllItmsQty int
select @ItemsCount=count(*),@ItemsSum=sum(price),@flag=POWER(2,count(*)) From @Items

select @AllItmsQty=@total/cast(@ItemsSum as int)

;with Numbers(n) as (
    --generat numbers from 1,2,3,... @flag
    select 1 union all 
    select (n+1) n from Numbers where n<@flag
),ItemsWithQty as (
    select *,Price*n [LineTotal] from @Items,Numbers
),Combination as (
    select items.*,Numbers.n-1 [CombinationId] from @Items items,Numbers
),CombinationWithSeq as (
    select *
        ,ROW_NUMBER() over (Partition by [CombinationId] order by [CombinationId]) [seq] 
        from Combination
),CombinationWithSeqQty as (
    select *,case when (CombinationId & power(2,seq-1))>0 then 1 else 0 end +@AllItmsQty  [qty] 
    from CombinationWithSeq
),CombinationWithSeqQtySubTotal as (
    select *,Price*qty [SubTotal] from CombinationWithSeqQty
)
select 
    --CombinationId,
    sum(subtotal) [Total],
    replace(
        replace(
            STRING_AGG( 
            case when (Qty=0) then 'NA' else (cast(Qty as varchar(5))+' '+Item)
                end
            ,'+')
            ,'+NA','')
            ,'NA+','')  [Items] 
    from CombinationWithSeqQtySubTotal   
    group by CombinationId
    having sum(subtotal)<=@total

结果如下:-

Total   Items
=====   ===========================
100     2 tshirt+1 jacket+1 pants
100     1 tshirt+2 jacket+1 pants
80      1 tshirt+1 jacket+2 pants
70      1 tshirt+1 jacket+1 pants

如果我添加其他两项,我们将得到

Total   Items
=====   ===========================
100     1 tshirt+1 jacket+2 pants+1 shoe+1 socks
95      1 tshirt+1 jacket+1 pants+1 shoe+2 socks
90      1 tshirt+1 jacket+1 pants+1 shoe+1 socks

好吧,所以查询给出的最终结果不是表B,您描述的是将axb或商品价格乘以qty和sub total,如果我们选择过滤女巫组合,我们可以很容易地显示该表正在选择最接近金额的第一个,我们可以更改查询的最后一部分以显示所需的表B。

),CombinationWithSeqQtySubTotal as (
    select *,Price*qty [SubTotal] from CombinationWithSeqQty
),Results as (
select 
    CombinationId,
    sum(subtotal) [Total],
    replace(
        replace(
            STRING_AGG( 
            case when (Qty=0) then 'NA' else (cast(Qty as varchar(5))+' '+Item)
                end
            ,'+')
            ,'+NA','')
            ,'NA+','')  [Items] 
    from CombinationWithSeqQtySubTotal   
    group by CombinationId
    having sum(subtotal)<=@total
    --order by [Total] desc
)
select item, price, qty, SubTotal from CombinationWithSeqQtySubTotal t where t.CombinationId in
(select top(1) CombinationId from Results order by [Total] desc)

结果如下:-

item    price   qty SubTotal
=====   =====   === =======
tshirt  30      1   30
jacket  30      1   30
pants   10      2   20
shoe    15      1   15
socks   5       1   5

或者如果仅使用您提供的项目运行它,结果将如下所示:-

item    price   qty SubTotal
======  ===     === =======
tshirt  30      2   60
jacket  30      1   30
pants   10      1   10

如果我们不想使用'STRING_AGG'或没有它,我们可以通过添加一些可以完成相同工作的CTE来管理其相同的功能,因为'STRING_AGG'仅将结果合并为(qty + item +逗号),因此以下解决方案可能会有所帮助。

Declare @Items Table (Item varchar(250),Price decimal)

insert into @Items values 
 ('tshirt',30)
,('jacket',30)
,('pants' ,10)
--,('shoes' ,15)   ---extra items for testing
--,('socks',5)    ---extra items for testing

Declare @total int=100 -- your X
Declare @ItemsCount int
Declare @flag int
Declare @ItemsSum decimal
Declare @AllItmsQty int
select @ItemsCount=count(*),@ItemsSum=sum(price),@flag=POWER(2,count(*)) From @Items

select @AllItmsQty=@total/cast(@ItemsSum as int)

;with Numbers(n) as (
    --generat numbers from 1,2,3,... @flag
    select 1 union all 
    select (n+1) n from Numbers where n<@flag
),ItemsWithQty as (
    select *,Price*n [LineTotal] from @Items,Numbers
),Combination as (
    select items.*,Numbers.n-1 [CombinationId] from @Items items,Numbers
),CombinationWithSeq as (
    select *,ROW_NUMBER() over (Partition by [CombinationId] order by [CombinationId]) [seq] from Combination
),CombinationWithSeqQty as (
    select *,case when (CombinationId & power(2,seq-1))>0 then 1 else 0 end +@AllItmsQty  [qty] from CombinationWithSeq
),CombinationWithSeqQtySubTotal as (
    select *,Price*qty [SubTotal] from CombinationWithSeqQty
),CombinationWithTotal as (
--to find only the combinations that are less or equal to the Total
    select 
        CombinationId,
        sum(subtotal) [Total]
        from CombinationWithSeqQtySubTotal   
        group by CombinationId
        having sum(subtotal)<=@total
),DetailAnswer as (
    select s.*,t.Total,cast(s.qty as varchar(20))+' ' +s.Item QtyItem from CombinationWithTotal t
    inner join CombinationWithSeqQtySubTotal s on s.CombinationId=t.CombinationId
),DetailAnswerFirst as (
    select *,cast(QtyItem as varchar(max)) ItemList from DetailAnswer t where t.seq=1
    union all
    select t.*,cast((t.QtyItem+'+'+x.ItemList) as varchar(max)) ItemList from DetailAnswer t
        inner join DetailAnswerFirst x on x.CombinationId=t.CombinationId and x.seq+1=t.seq
)
select CombinationId,Total,ItemList from DetailAnswerFirst where seq=@ItemsCount order by Total desc
--select * from DetailAnswer --remark the above line and unremark this one for the details that you want to go in Table B

如果任何假设是错误的,或者您需要一些描述,我将很乐意为您提供帮助。

答案 1 :(得分:0)

也许得到的可能组合的最简单的方法是通过自连接,并加入到数字。

如果想要3个的组合,则使用3个自联接。
每个联接的“项目”表都有3个联接到数字表或CTE。

ON标准中所使用的方式,是最小化所有的,加入的影响。

您也可以采取从COMBOS CTE的SQL,并用它来第一次将其插入到一个临时表。

<强>例如

declare @PriceLimit decimal(10,2) = 100;

WITH COMBOS AS
(
    SELECT 
     i1.id as id1, i2.id as id2, i3.id as id3, 
     n1.n as n1, n2.n as n2, n3.n as n3,
     (n1.n + n2.n + n3.n) AS TotalItems,
     (i1.Price * n1.n + i2.Price * n2.n + i3.Price * n3.n) as TotalCost
    FROM Items i1
    JOIN Items i2 ON i2.id > i1.id AND i2.Price < @PriceLimit
    JOIN Items i3 ON i3.id > i2.id AND i3.Price < @PriceLimit
    JOIN Nums n1 
      ON n1.n between 1 and FLOOR(@PriceLimit/i1.Price)
     AND (i1.Price * n1.n) < @PriceLimit
    JOIN Nums n2 
      ON n2.n between 1 and FLOOR(@PriceLimit/i2.Price)
     AND (i1.Price * n1.n + i2.Price * n2.n) < @PriceLimit
    JOIN Nums n3 
      ON n3.n between 1 and FLOOR(@PriceLimit/i3.Price) 
     AND (i1.Price * n1.n + i2.Price * n2.n + i3.Price * n3.n) <= @PriceLimit
     AND (i1.Price * n1.n + i2.Price * n2.n + i3.Price * (n3.n+1)) > @PriceLimit
    WHERE i1.Price < @PriceLimit
)
SELECT 
 c.TotalItems, c.TotalCost,
 CONCAT (c.n1,' ',item1.Name,', ',c.n2,' ',item2.Name,', ',c.n3,' ',item3.Name) AS ItemList
FROM COMBOS c
LEFT JOIN Items item1 ON item1.id = c.id1
LEFT JOIN Items item2 ON item2.id = c.id2
LEFT JOIN Items item3 ON item3.id = c.id3
ORDER BY c.TotalCost desc, c.TotalItems desc, c.id1, c.id2, c.id3;

db <>小提琴here

的测试

测试结果:

 TotalItems | TotalCost | ItemList                   
 ---------- | --------- | ---------------------------
          7 | 100.00    | 1 pants, 1 tshirt, 5 socks 
          6 | 100.00    | 1 jacket, 1 tshirt, 4 socks
          6 | 100.00    | 1 pants, 2 tshirt, 3 socks 
          5 | 100.00    | 1 jacket, 1 pants, 3 socks 
          5 | 100.00    | 1 jacket, 2 tshirt, 2 socks
          5 | 100.00    | 1 pants, 3 tshirt, 1 socks 
          5 | 100.00    | 2 pants, 1 tshirt, 2 socks 
          3 | 90.00     | 1 jacket, 1 pants, 1 tshirt