我正在使用的数据库有一个订货时间戳和订单金额如下:
paid_at | amount
---------------------+--------
2002-12-03 18:08:10 | 1000
2002-12-03 01:11:32 | 2000
2002-12-03 09:31:45 | 1000
2002-12-04 13:54:37 | 1000
2002-12-05 23:21:04 | 500
如何找出订单达到250000之类特定汇总金额所需的最短时间?
理想情况下,答案看起来与此相似:
start | end | total
----------------------------+----------------------------+--------
2004-12-28 16:28:11.017554 | 2005-03-15 21:18:23.320983 | 250000
答案 0 :(得分:0)
您可以使用窗口功能Sum() Over()
查找运行总计。然后使用条件聚合得到结果
查找总计
SELECT paid_at,
Sum(amount) OVER(ORDER BY paid_at) AS total
FROM Yourtable
使用上述运行总计来获取最小和最大日期
SELECT Min(paid_at) AS start_date,
Min(CASE WHEN total >= 250000 THEN paid_at END) AS End_date,
Min(CASE WHEN total >= 250000 THEN total END) AS total
FROM (SELECT paid_at,
Sum(amount) OVER(ORDER BY paid_at) AS total
FROM Yourtable) A
另一种方法是使用Correlated sub-query
(不推荐)
SELECT Min(paid_at) AS start_date,
Min(CASE WHEN total >= 250000 THEN paid_at END) AS End_date,
Min(CASE WHEN total >= 250000 THEN total END) AS total
FROM (SELECT paid_at,
(SELECT Sum(amount)
FROM Yourtable B
WHERE a.paid_at >= b.paid_at) AS total
FROM Yourtable A) A
答案 1 :(得分:0)
严格来说,您可以使用自联接来执行此操作:
select t1.paid_at, t2.paid_at, count(*), sum(total)
from t t1 join
t t2
on t1.paid_at <= t2.paid_at
group by t1.paid_at, t2.paid_at
having sum(total) >= 250000
order by count(*) asc
limit 1;
但是,如果你有超过几十条记录,这将不会很快。
答案 2 :(得分:0)
你可以试试这个:
SELECT Top 1
(SELECT MIN(Paid_at) FROM TABLE) AS START
, Paid_at AS [END]
, RunningSum AS TOTAL
FROM (
SELECT Paid_at
, Amount
, (SELECT SUM(Amount) FROM TABLE B WHERE B.Paid_at <= A.Paid_at) AS RunningSum
FROM TABLE A
) C
Where RunningSum >= 250000
Order by 1
答案 3 :(得分:0)
First of all, I'd like to say that this is a bad kind of problem to solve inside SQL. That's because there is no way to optimize the number of combinations you try until you get to the right value. You have to test all possible solutions.
The easiest way to code the solution that I could think of is using a cursor. I know it is highly recommended to avoid cursors, but it turns out that this is not an easy problem to solve without one. But, obviously, there are a few different ways to solve it; maybe creating some auxiliar function/proc would make the code simpler. Also, since I am using a cursor, I did not worry that much about performance. For best performance, I would leave this logic to the application tier, or implement a CLR.
PS: I haven't tested my solution that much, so you could run into some bugs.
Here is the code:
set nocount on
IF OBJECT_ID('tempdb..#tbPayments') IS NOT NULL
drop table #tbPayments
create table #tbPayments (
paid_at datetime,
amount float,
row_index int
)
insert into #tbPayments (paid_at, amount)
values ('2002-12-03 01:11:32', 2000),
('2002-12-03 09:31:45', 1000),
('2002-12-03 18:08:10', 1000),
('2002-12-04 13:54:37', 1000),
('2002-12-05 23:21:04', 500),
('2002-12-05 23:22:04', 2100)
;with cteRows as (
select paid_at, amount, row_index, ROW_NUMBER() over (order by paid_at) as new_index
from #tbPayments
)
update cteRows
set row_index = new_index
declare @count int
select @count = count(*)
from #tbPayments
declare @start_row int
declare @end_row int
set @start_row = 1
declare @best_start_row int
declare @best_end_row int
declare @c cursor
declare @amount float
declare @start_time datetime
declare @end_time datetime
declare @shortest_interval float
declare @new_interval float
set @shortest_interval = -1.0
declare @total_amount int
declare @limit int
set @limit = 2500
while @start_row <= @count
begin
set @c = cursor for
select paid_at, amount
from #tbPayments
where row_index >= @start_row
order by paid_at
open @c
set @total_amount = 0
set @end_row = @start_row - 1
fetch next from @c into @start_time, @amount
while @@FETCH_STATUS = 0 and (@total_amount < @limit)
begin
set @end_row = @end_row + 1
set @total_amount = @total_amount + @amount
fetch next from @c into @end_time, @amount
end
set @new_interval = DATEDIFF(s, @start_time, @end_time)
if ((@shortest_interval = -1.0) or (@new_interval < @shortest_interval)) and (@total_amount >= @limit)
begin
set @shortest_interval = @new_interval
set @best_start_row = @start_row
set @best_end_row = @end_row
end
close @c
set @start_row = @start_row + 1
end
deallocate @c
select @best_start_row as start_row, @best_end_row as end_row, @shortest_interval as [interval (sec)]