SQL Server地图付款到产品

时间:2016-12-17 20:36:06

标签: sql sql-server

我们的购物车可能包含产品和付款。

由于这些付款将用于购物车,因此产品与付款之间不存在任何关系,除非它们位于同一购物车中。

有些情况下,即使这些产品位于同一购物车中,也会单独开具发票。

要为产品创建发票,我们需要付款详细信息,因此我们必须将产品映射到付款。

这些是我们的表格:

create table Products 
(
     ItemId int primary key, 
     CartId int not null, 
     ItemAmount smallmoney not null 
)

create table Payments 
(
     PaymentId int primary key, 
     CartId int not null, 
     PaymentAmount smallmoney not null 
)

create table MappedTable 
(
     ItemId int not null, 
     PaymentId int not null, 
     Amount smallmoney not null 
)

INSERT INTO Products (ItemId, CartId, ItemAmount) 
VALUES  (1, 1, 143.49), (2, 1, 143.49), (3, 1, 143.49),  (4, 2, 50.00), (5, 3, 75.00), (6, 3, 75.00) 

INSERT INTO Payments (PaymentId, CartId, PaymentAmount) 
VALUES  (1, 1, 376.47), (2, 1, 54.00), (3, 2, 60.00), (4, 3, 140.00)

--select * from Products
--select * from Payments
--DROP TABLE Products
--DROP TABLE Payments
--DROP TABLE MappedTable

产品

ItemId | CartId | ItemAmount
------ | ------ | ----------
     1 |      1 |     143.49
     2 |      1 |     143.49
     3 |      1 |     143.49
     4 |      2 |      50.00
     5 |      3 |      75.00
     6 |      3 |      75.00

付款

PaymentId | CartId | PaymentAmount
--------- | ------ | -------------
        1 |      1 |        376.47
        2 |      1 |         54.00
        3 |      2 |         60.00
        4 |      3 |        140.00

产品和付款的顺序可能不同。

我们需要输出看起来像这样:

MappingTable

ItemId | PaymentId | MappedAmount
------ | --------- | ------------
     1 |         1 |     143.49
     2 |         1 |     143.49
     3 |         1 |      89.49
     3 |         2 |      54.00
     4 |         3 |      50.00 (Remaining 10.00 from Payment 3 will be ignored)
     5 |         4 |      75.00
     6 |         4 |      65.00 (Missing 10.00 from Payment 4 will be ignored)
  • 购物车1:付款总额=产品成本总和
  • 购物车2:付款总额>产品成本之和。只占总产品成本。忽略剩余的10.00
  • 购物车3:付款总额<产品成本之和。收取所有款项,忽略付款时间为10.00的事实。

我认为像下面这样的查询可以解决问题,但没有运气。

insert into MappedTable
    select 
        prd.ItemId, pay.PaymentId, 
        (Case 
            when prd.ItemAmount - isnull((select sum(m.Amount) 
                                          from MappedTable m 
                                          where m.ItemId = prd.ItemId), 0) <= pay.PaymentAmount - isnull((select sum(m.Amount) from MappedTable m where m.PaymentId = pay.PaymentId), 0) 
               then prd.ItemAmount - isnull((select sum(m.Amount) from MappedTable m where m.ItemId = prd.ItemId), 0)
            else pay.PaymentAmount - isnull((select sum(m.Amount) from MappedTable m where m.PaymentId = pay.PaymentId), 0)                 
        end)
    from 
        Products prd 
    inner join 
        Payments pay on pay.CartId = prd.CartId
    where 
        prd.ItemAmount > isnull((select sum(m.Amount) from MappedTable m where m.ItemId = prd.ItemId), 0)
        and pay.PaymentAmount > isnull((select sum(m.Amount) from MappedTable m where m.PaymentId = pay.PaymentId), 0)

我已经读过CTE(通用表格表格)和基于集合的方法,但我无法处理这个问题。

这样可以不使用游标或while循环吗?

1 个答案:

答案 0 :(得分:0)

通常,这种任务被称为&#34; knapsack problem&#34;,众所周知,没有任何解决办法比强制执行所有可能的组合更有效。但是,在您的情况下,您有其他条件,即订购的商品和付款组合,因此实际上可以使用&#34;重叠间隔&#34;技术

我们的想法是生成物品和付款范围(每个购物车一对范围),然后按顺序查看哪些付款与哪些商品重叠。

对于任何项目付款组合,有3种可能的情况:

  1. 付款涵盖项目范围的开头(可能完全涵盖该项目);
  2. 付款完全在项目内;
  3. 付款涵盖项目的结尾,因此&#34;关闭&#34;它。
  4. 因此,所需要的只是,对于每个项目,找到符合上述标准的所有合适的付款,并按其标识符进行排序。这是一个查询:

    with cte as (
        -- Project payment ranges, per cart
        select pm.*, sum(pm.PaymentAmount) over(partition by pm.CartId order by pm.PaymentId) as [RT]
        from @Payments pm
    )
    select q.ItemId, q.PaymentId,
      -- Calculating the amount from payment that goes for this item
        case q.Match
            when 1 then q.PaymentRT - (q.ItemRT - q.ItemAmount)
            when 2 then q.PaymentAmount
            when 3 then case
                -- Single payment spans over several items
                when q.PaymentRT >= q.ItemRT and q.PaymentRT - q.PaymentAmount <= q.ItemRT - q.ItemAmount then q.ItemAmount
                -- Payment is smaller than item
                else q.ItemRT - (q.PaymentRT - q.PaymentAmount)
            end
        end as [Amount]
      --, q.*
    from (
        select
            sq.ItemId, pm.PaymentId, sq.ItemAmount, sq.RT as [ItemRT],
            pm.PaymentAmount, pm.RT as [PaymentRT],
            row_number() over(partition by sq.CartId, sq.ItemId, pm.PaymentId order by pm.RT) as [RN],
            pm.Match
            --, sq.CartId
        from (
            select pr.*, sum(pr.ItemAmount) over(partition by pr.CartId order by pr.ItemId) as [RT]
            from @Products pr
        ) sq
            outer apply (
                -- First payment to partially cover this item
                select top (1) c.*, 1 as [Match] from cte c where c.CartId = sq.CartId
                    and c.RT > sq.RT - sq.ItemAmount and c.RT < sq.RT
                order by sq.RT
                union all
                -- Any payments that cover this item only
                select c.*, 2 as [Match] from cte c where c.CartId = sq.CartId
                    and c.RT - c.PaymentAmount > sq.RT - sq.ItemAmount
                    and c.RT < sq.RT
                union all
                -- Last payment that covers this item
                select top (1) c.*, 3 as [Match] from cte c where c.CartId = sq.CartId
                    and c.RT >= sq.RT
                order by sq.RT
            ) pm
        ) q
    where q.RN = 1;
    

    outer apply部分是我获得与每件商品相关的付款的地方。唯一的问题是,如果付款涵盖整个项目,则会多次列出。为了删除这些重复项,我使用row_number()对匹配进行了排序,并添加了一个额外的包装级别 - 带有q别名的子查询 - 其中我通过过滤行号值来切断任何重复的行。

    P.S。如果您的SQL Server版本早于2012年,则需要使用Internet上提供的众多方法之一来计算运行总计,因为sum() over(order by ...)仅适用于2012及更高版本。