来自多行的SQL Deduct值

时间:2014-09-29 20:21:32

标签: sql sql-server sql-server-2008

我想为每位客户提供总共10.00美元的折扣。折扣应适用于多笔交易,直到所有使用的$ 10.00。 例如:

CustomerID  Transaction Amount      Discount       TransactionID
1           $8.00                   $8.00          1
1           $6.00                   $2.00          2
1           $5.00                   $0.00          3
1           $1.00                   $0.00          4
2           $5.00                   $5.00          5
2           $2.00                   $2.00          6
2           $2.00                   $2.00          7
3           $45.00                  $10.00         8
3           $6.00                   $0.00          9

3 个答案:

答案 0 :(得分:1)

下面的查询会跟踪运行总和,并根据运行金额是否大于或小于折扣金额来计算折扣。

select 
    customerid, transaction_amount, transactionid,
    (case when 10 > (sum_amount - transaction_amount)
    then (case when transaction_amount >= 10 - (sum_amount - transaction_amount)
    then 10 - (sum_amount - transaction_amount) 
    else transaction_amount end)
    else 0 end) discount
from (
    select customerid, transaction_amount, transactionid,
    sum(transaction_amount) over (partition by customerid order by transactionid) sum_amount
    from Table1
) t1 order by customerid, transactionid

http://sqlfiddle.com/#!6/552c2/7

使用自联接的相同查询应该适用于大多数数据库,包括mssql 2008

select 
    customerid, transaction_amount, transactionid,
    (case when 10 > (sum_amount - transaction_amount)
    then (case when transaction_amount >= 10 - (sum_amount - transaction_amount)
    then 10 - (sum_amount - transaction_amount) 
    else transaction_amount end)
    else 0 end) discount
from (
    select t1.customerid, t1.transaction_amount, t1.transactionid,
    sum(t2.transaction_amount) sum_amount
    from Table1 t1
    join Table1 t2 on t1.customerid = t2.customerid
    and t1.transactionid >= t2.transactionid
    group by t1.customerid, t1.transaction_amount, t1.transactionid
) t1 order by customerid, transactionid

http://sqlfiddle.com/#!3/552c2/2

答案 1 :(得分:0)

您可以使用递归公用表表达式执行此操作,尽管它并不特别漂亮。 SQL Server依赖于优化这些类型的查询。有关讨论,请参阅Sum of minutes between multiple date ranges

如果你想进一步采用这种方法,你可能需要制作x的临时表,这样你就可以在(customerid, rn)

上对其进行索引。
;with x as (
  select
    tx.*,
    row_number() over (
      partition by customerid 
      order by transaction_amount desc, transactionid
    ) rn
  from
    tx
), y as (
  select
    x.transactionid,
    x.customerid,
    x.transaction_amount,
    case 
      when 10 >= x.transaction_amount then x.transaction_amount
      else 10
    end as discount,
    case 
      when 10 >= x.transaction_amount then 10 - x.transaction_amount
      else 0
    end as remainder,
    x.rn as rn
  from
    x
  where
    rn = 1
  union all
  select
    x.transactionid,
    x.customerid,
    x.transaction_amount,
    case
      when y.remainder >= x.transaction_amount then x.transaction_amount
      else y.remainder
    end,
    case
      when y.remainder >= x.transaction_amount then y.remainder - x.transaction_amount
      else 0
    end,
    x.rn
  from
    y
        inner join
    x
        on y.rn = x.rn - 1 and y.customerid = x.customerid
  where
    y.remainder > 0
)
update
  tx
set
  discount = y.discount
from
  tx
    inner join
  y
    on tx.transactionid = y.transactionid;

Example SQLFiddle

答案 2 :(得分:0)

我通常喜欢为这些问题设置测试环境。我将使用本地临时表。请注意,我没有订购数据,因为现实生活中无法保证。

-- play table
if exists (select 1 from tempdb.sys.tables where name like '%transactions%')
drop table #transactions
go

-- play table
create table #transactions
(
  trans_id int identity(1,1) primary key,
  customer_id int,
  trans_amt smallmoney
)
go


-- add data
insert into #transactions
values
(1,$8.00),
(2,$5.00),  
(3,$45.00),     
(1,$6.00),   
(2,$2.00),     
(1,$5.00),   
(2,$2.00),   
(1,$1.00),        
(3,$6.00);      
go

我打算给你两个答案。

首先,在2014年,前面的行有新的窗口函数。这允许我们通过一个条目调整运行总计(rt)和rt。给出这两个值,我们可以确定是否已超过最大折扣。

-- Two running totals for 2014
;
with cte_running_total 
as
(
select 
  *,

   SUM(trans_amt) 
   OVER (PARTITION BY customer_id
   ORDER BY trans_id
   ROWS BETWEEN UNBOUNDED PRECEDING AND 
   0 PRECEDING) as running_tot_p0,

   SUM(trans_amt) 
   OVER (PARTITION BY customer_id
   ORDER BY trans_id
   ROWS BETWEEN UNBOUNDED PRECEDING AND 
   1 PRECEDING) as running_tot_p1
from 
  #transactions
)
select 
   *
   ,
    case 
        when coalesce(running_tot_p1, 0) <= 10 and running_tot_p0 <= 10 then
            trans_amt 
        when coalesce(running_tot_p1, 0) <= 10 and running_tot_p0 > 10 then
            10 - coalesce(running_tot_p1, 0)
        else 0
     end as discount_amt
from cte_running_total;

同样,上面的版本使用公共表表达式和高级窗口来获取总计。

不要烦恼!同样可以一直到SQL 2000。

第二个解决方案,我将使用order by子查询和临时表来存储通常在CTE中的信息。如果需要,可以在SQL 2008中切换CTE的临时表。

-- w/o any fancy functions - save to temp table
select *,
(
select count(*) from #transactions i
where i.customer_id = o.customer_id
and   i.trans_id <= o.trans_id
) as sys_rn,

(
select sum(trans_amt) from #transactions i
where i.customer_id = o.customer_id
and   i.trans_id <= o.trans_id
) as sys_tot_p0,

(
select sum(trans_amt) from #transactions i
where i.customer_id = o.customer_id
and   i.trans_id < o.trans_id
) as sys_tot_p1

into #results     
from #transactions o
order by customer_id, trans_id
go

-- report off temp table
select
    trans_id, 
    customer_id,
    trans_amt,
    case 
        when coalesce(sys_tot_p1, 0) <= 10 and sys_tot_p0 <= 10 then
            trans_amt 
        when coalesce(sys_tot_p1, 0) <= 10 and sys_tot_p0 > 10 then
            10 - coalesce(sys_tot_p1, 0)
        else 0
     end as discount_amt     
from #results
order by customer_id, trans_id
go

简而言之,您的答案显示在以下屏幕截图中。将代码剪切并粘贴到SSMS中并获得一些乐趣。

enter image description here