正在运行的总列的复杂SQL查询

时间:2009-05-02 03:23:42

标签: sql sql-server

我正在尝试在SQL Server 2008中编写一个非常复杂的查询。我想在这里得到SQL专家的一些意见。

想象一下,我有一个带有这些字段的付款表:

PaymentID int, CustomerID int, PaymentDate日期时间, 十进制数

基本上,它是客户在特定日期付款的表格。需要注意的一点是,在某些情况下,付款金额可能是负值。因此,随着时间的推移,任何给定客户支付的总金额可能会上升或下降。

我们想要弄清楚的是用于计算每位客户支付总金额的高点的SQL。

所以,如果弗雷德支付了3笔款项:首先是5美元,第二是5美元,第三是3美元。该报告将显示弗雷德的最高支付总额为10美元(第二次支付),最终支付金额为7美元。

我们需要为十万名客户(他们每次可能支付一百到一千美元)运行此报告,因此必须快速。

是否有一种很好的方法来构建此查询而不将运行总计存储在数据库中?如果可能的话,我们希望避免存储预先计算的值。

5 个答案:

答案 0 :(得分:7)

你的问题似乎是这样的:

SELECT CustomerID, SUM(Ammount) FROM table WHERE Amount > 0 GROUP BY CustomerID
SELECT CustomerID, SUM(Ammount) FROM table GROUP BY CustomerID

但是,我认为你的意思是你想要一个看起来像这样的表

Customer  Payment  HighPoint  RunningTotal
123       5        5          5
123       5        10         10
123       -3       10         7

在这种情况下,我会使用上面的两个选项创建一个视图,以便视图类似于。

SELECT CusotmerID, 
  PaymentDate, 
  Ammount, 
  (SELECT SUM(Ammount) 
    FROM table as ALIAS 
    WHERE ALIAS.Amount > 0 
      AND ALIAS.PaymentDate <= PaymentDate 
      AND ALIAS.CustomerID = CustomerID), 
  (SELECT SUM(Ammount) 
    FROM table as ALIAS 
    WHERE ALIAS.CustomerID = CustomerID 
    AND ALIAS.PaymentDate <= PaymentDate)
FROM table

此外,您可以考虑在表的Amount列上使用非唯一索引来加速视图。

答案 1 :(得分:4)

该操作与每位客户的付款数量成线性关系。因此,您将不得不检查每笔付款,保持运行总额和高水位标记,并且在所有付款结束时,您将获得答案。无论你是在CLR存储过程中做到这一点(我立即跳到脑海中)还是使用游标或临时表或其他什么,它可能都不会很快。

如果你必须一遍又一遍地运行这个报告,你应该认真考虑保留一个高水位字段,并在付款时更新(或不更新)。这样,你的报告将是微不足道的 - 但是这个是数据集市的用途。

答案 2 :(得分:4)

作为子查询的替代方法,您可以使用正在运行的总查询。以下是我为此案例设置的方法。首先创建一些测试数据:

create table #payments (
    paymentid int identity,
    customerid int,
    paymentdate datetime,
    amount decimal
)

insert into #payments (customerid,paymentdate,amount) values (1,'2009-01-01',1.00)
insert into #payments (customerid,paymentdate,amount) values (1,'2009-01-02',2.00)
insert into #payments (customerid,paymentdate,amount) values (1,'2009-01-03',-1.00)
insert into #payments (customerid,paymentdate,amount) values (1,'2009-01-04',2.00)
insert into #payments (customerid,paymentdate,amount) values (1,'2009-01-05',-3.00)
insert into #payments (customerid,paymentdate,amount) values (2,'2009-01-01',10.00)
insert into #payments (customerid,paymentdate,amount) values (2,'2009-01-02',-5.00)
insert into #payments (customerid,paymentdate,amount) values (2,'2009-01-03',7.00)

现在您可以执行运行总计查询,该查询计算每次付款后每位客户的余额:

select cur.customerid, cur.paymentdate, sum(prev.amount)
from #payments cur
inner join #payments prev
    on cur.customerid = prev.customerid
    and cur.paymentdate >= prev.paymentdate
group by cur.customerid, cur.paymentdate

这会生成数据:

Customer  Paymentdate        Balance after payment
1         2009.01.01         1
1         2009.01.02         3
1         2009.01.03         2
1         2009.01.04         4
1         2009.01.05         1
2         2009.01.01         10
2         2009.01.02         5
2         2009.01.03         12

要查看最大值,您可以在运行的总查询中执行分组:

select customerid, max(balance)
from (
    select cur.customerid, cur.paymentdate, balance = sum(prev.amount)
    from #payments cur
    inner join #payments prev
        on cur.customerid = prev.customerid
        and cur.paymentdate >= prev.paymentdate
    group by cur.customerid, cur.paymentdate
) runningtotal
group by customerid

给出了:

Customer   Max balance
1          4
2          12

希望这很有用。

答案 3 :(得分:1)

list = list of amounts ordered by date
foreach in list as amount
  running += amount
  if running >= high
    high = running

为了保持快速,您需要在触发器上增加一个运行总计金额,并为每个客户提供一个高值(也可以通过触发器更新,以使重新查询更简单)。

我认为没有代码就可以做这种事情(存储过程就是代码)

答案 4 :(得分:1)

像Andomar的回答一样。您可以为每笔付款执行运行总计。然后找到最高峰付款......

with
rt as (
  select
    Payments.*,
    isnull(sum(p.Amount), 0) + Payments.Amount as running
  from
    Payments
    left outer join Payments p on Payments.CustomerID = p.CustomerID
      and p.PaymentDate <= Payments.PaymentDate
      and p.PaymentID < Payments.PaymentID
),
highest as
(
  select
    CustomerID, PaymentID, running as peak_paid
  from
    rt
  where
    PaymentID = (select top 1 rt2.PaymentID 
        from rt rt2 
        where rt2.CustomerID = rt.CustomerID
        order by rt2.running desc, rt2.PaymentDate, rt2.PaymentID)
)

select
  *,
  (select sum(amount) from Payments where Payments.CustomerID = highest.CustomerID) as total_paid  
from
  highest;
然而,由于您有大约100万笔付款,这可能会非常缓慢。像其他人一样,您希望将CustomerID,PaymentID和peak_paid存储在单独的表中。此表可以在每个付款插页上更新,也可以作为sqljob更新。

更新了查询以使用连接而不是子查询。由于PaymentDate没有时间,我会在同一天过滤掉PaymentId的多笔付款。