2016-01-06 3 views
1

У меня есть эта таблица в моей базе данных PostgreSQL:Сумма последнего значения от пользователей

purchase 

userid | date | price 
--------------------------- 
    1 | 2016-01-06 | 10 
    1 | 2016-01-05 |  5 
    2 | 2016-01-06 | 12 
    2 | 2016-01-05 | 15 

Я хочу, чтобы сумма последней цены покупки всех пользователей. Для пользователя 1 последняя покупка находится на 2016-01-06, а цена - 10. Для пользователя 2 последняя покупка находится на 2016-01-06, а цена - 12. Таким образом, результат SQL-запроса должен быть равен 22.

Как я могу это сделать в SQL?

+0

Может ли пользователь иметь более одной покупки в день? – Elad

+0

Да, я могу, но нормально, у меня также есть время в столбце даты. Так что это не должно быть проблемой. – cheb1k4

ответ

1

Все предлагаемые решения хороши и работают, но поскольку моя таблица содержит миллионы записей, мне нужно было найти более эффективный способ делать то, что я хочу.И кажется, что лучший способ - использовать внешний ключ между таблицами purchase и user (о чем я не упоминал в своем вопросе, мои извинения), который является purchase.user -> user.id. Зная это, я могу сделать следующий запрос:

select sum(t.price) from (
    select (select price from purchase p where p.userid = u.id order by date desc limit 1) as price 
    from user u 
) t; 

EDIT

Чтобы ответить на @a_horse_with_no_name здесь является explain analyse verbose для своих и моих решений:
Его решение:

Aggregate (cost=64032401.30..64032401.31 rows=1 width=4) (actual time=566101.129..566101.129 rows=1 loops=1) 
    Output: sum(purchase.price) 
    -> Unique (cost=62532271.89..64032271.89 rows=10353 width=16) (actual time=453849.494..566087.948 rows=12000 loops=1) 
      Output: purchase.userid, purchase.price, purchase.date 
      -> Sort (cost=62532271.89..63282271.89 rows=300000000 width=16) (actual time=453849.492..553060.789 rows=300000000 loops=1) 
       Output: purchase.userid, purchase.price, purchase.date 
       Sort Key: purchase.userid, purchase.date 
       Sort Method: external merge Disk: 7620904kB 
       -> Seq Scan on public.purchase (cost=0.00..4910829.00 rows=300000000 width=16) (actual time=0.457..278058.430 rows=300000000 loops=1) 
         Output: purchase.userid, purchase.price, purchase.date 
Planning time: 0.076 ms 
Execution time: 566433.215 ms 

Мои раствор:

Aggregate (cost=28366.33..28366.34 rows=1 width=4) (actual time=53914.690..53914.690 rows=1 loops=1) 
    Output: sum((SubPlan 1)) 
    -> Seq Scan on public.user2 u (cost=0.00..185.00 rows=12000 width=4) (actual time=0.021..3.816 rows=12000 loops=1) 
      Output: u.id, u.name 
    SubPlan 1 
     -> Limit (cost=0.57..2.35 rows=1 width=12) (actual time=4.491..4.491 rows=1 loops=12000) 
      Output: p.price, p.date 
      -> Index Scan Backward using purchase_user_date on public.purchase p (cost=0.57..51389.67 rows=28977 width=12) (actual time=4.490..4.490 rows=1 loops=12000) 
        Output: p.price, p.date 
        Index Cond: (p.userid = u.id) 
Planning time: 0.115 ms 
Execution time: 53914.730 ms 

Моя таблица содержит 300 миллионов записей.
Я не знаю, имеет ли это значение, но у меня также есть индекс на purchase (userid, date).

+0

Я удивлен, что это работает лучше, чем 'distinct on()' solution. Было бы интересно увидеть планы выполнения ('explain (analzye, verbose)') для обоих –

4

Вы можете использовать оконные функции, чтобы получить номер ранга, а затем использовать нормальную агрегацию с SUM:

WITH cte AS 
(
    SELECT *, RANK() OVER(PARTITION BY userid ORDER BY "date" DESC) AS r 
    FROM purchase 
) 
SELECT SUM(price) AS total 
FROM cte 
WHERE r = 1; 

SqlFiddleDemo

Имейте в виде, что это решение вычисляет связь. Чтобы получить только одну покупку на одного пользователя, вам нужен столбец, который отличается для каждой группы (например, datetime). Но все же это возможность получить связи.

EDIT:

Handling связи:

CREATE TABLE purchase(
    userid INTEGER NOT NULL 
    ,date timestamp NOT NULL 
    ,price INTEGER NOT NULL 
); 
INSERT INTO purchase(userid,date,price) VALUES 
(1, timestamp'2016-01-06 12:00:00',10), 
(1,timestamp'2016-01-05',5), 
(2,timestamp'2016-01-06 13:00:00',12), 
(2,timestamp'2016-01-05',15), 
(2,timestamp'2016-01-06 13:00:00',1000)' 

Обратите внимание на разницу RANK() против ROW_NUMBER:

SqlFiddleDemo_RANK SqlFiddleDemo_ROW_NUMBER SqlFiddleDemo_ROW_NUMBER_2

Выход:

╔════════╦══════════════╦══════════════╗ 
║ RANK() ║ ROW_NUMBER() ║ ROW_NUMBER() ║ 
╠════════╬══════════════╬══════════════╣ 
║ 1022 ║   22 ║   1010 ║ 
╚════════╩══════════════╩══════════════╝ 

Без UNIQUE индекс userid/date всегда есть возможность (возможно, небольшой) для галстука. Любые решения, основанные на ORDER BY, должны работать стабильно.

+0

Он также работает. Спасибо. – cheb1k4

+1

@ cheb1k4 У вас есть 'userid/date' PK/UNIQUE? Если вы не прочитали мое добавление – lad2025

+1

Вы настолько правы. Спасибо за это просвещение. – cheb1k4

3

Чтобы получить «последнюю» цена, которую вы можете использовать distinct on() в Postgres:

select distinct on (userid) userid, date, price 
from the_table 
order by userid, date desc 

Теперь вам нужно только суммировать все цены, возвращаемые выше заявление:

select sum(price) 
from (
    select distinct on (userid) userid, price 
    from the_table 
    order by userid, date desc 
) t; 
+0

Что со связями? – lad2025

+1

@ lad2025: good point - cheb1k4 не указал это –

+0

Он работает. Спасибо. – cheb1k4

1

Вы можете использовать Боковое присоединиться в этом случае:

SELECT sum(price) 
FROM (
    select distinct userid FROM purchase 
) u, 
LATERAL (
    SELECT price FROM purchase p 
    WHERE p.userid = u.userid 
    ORDER BY date DESC LIMIT 1 
) x 

демо: http://sqlfiddle.com/#!15/5569b/5

+0

Другое рабочее решение. Спасибо. Теперь мне нужно проверить, какой из них быстрее :) – cheb1k4

+0

Вы можете создать многоколоночный индекс в столбцах (userid, date DESC), чтобы ускорить эти запросы. – krokodilko

+0

Сравнить: [Demo1] (http://sqlfiddle.com/#!15/ 3d5c8/3/0) vs [Demo2] (http://sqlfiddle.com/#!15/a7b9a/2/0). Если идентификатор пользователя/дата не уникален, вы не можете получить ** стабильный вид **. – lad2025

Смежные вопросы