您如何查询等于总和的最短时间?

时间:2016-03-03 02:12:06

标签: sql postgresql

我正在使用的数据库有一个订货时间戳和订单金额如下:

       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

4 个答案:

答案 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)]