我正在尝试在SQL Server 2008中编写一个非常复杂的查询。我想在这里得到SQL专家的一些意见。
想象一下,我有一个带有这些字段的付款表:
PaymentID int, CustomerID int, PaymentDate日期时间, 十进制数
基本上,它是客户在特定日期付款的表格。需要注意的一点是,在某些情况下,付款金额可能是负值。因此,随着时间的推移,任何给定客户支付的总金额可能会上升或下降。
我们想要弄清楚的是用于计算每位客户支付总金额的高点的SQL。
所以,如果弗雷德支付了3笔款项:首先是5美元,第二是5美元,第三是3美元。该报告将显示弗雷德的最高支付总额为10美元(第二次支付),最终支付金额为7美元。
我们需要为十万名客户(他们每次可能支付一百到一千美元)运行此报告,因此必须快速。
是否有一种很好的方法来构建此查询而不将运行总计存储在数据库中?如果可能的话,我们希望避免存储预先计算的值。
答案 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)
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的多笔付款。