Как выбрать соответствующую запись вместе с агрегатной функцией с предложением - PullRequest
0 голосов
/ 22 сентября 2018

Допустим, у меня есть таблица заказов со столбцами customer_id, order_total и order_date.Я хотел бы создать отчет, который показывает всех клиентов, которые не разместили заказ в течение последних 30 дней, с колонкой для общей суммы, которую был их последний заказ.

Это получает всех клиентов, которые должны быть в отчете:

select customer, max(order_date), (select order_total from orders o2 where o2.customer = orders.customer order by order_date desc limit 1)
from orders
group by 1
having max(order_date) < NOW() - '30 days'::interval

Есть ли лучший способ сделать это, который не требует подзапроса, но вместо этого использует оконную функцию илидругой более эффективный метод для доступа к общей сумме из самого последнего заказа?Методы из Как выбрать идентификатор с максимальной датой для группы по категориям в PostgreSQL? связаны между собой, но дополнительное ограничение having, похоже, мешает мне использовать что-то вроде DISTINCT ON.

Ответы [ 2 ]

0 голосов
/ 22 сентября 2018

demo: db <> fiddle


Решение с оконной функцией row_number (https://www.postgresql.org/docs/current/static/tutorial-window.html)

SELECT 
    customer, order_date, order_total
FROM (
    SELECT
        *, 
        first_value(order_date) OVER w as last_order, 
        first_value(order_total) OVER w as last_total,
        row_number() OVER w as row_count
    FROM orders
    WINDOW w AS (PARTITION BY customer ORDER BY order_date DESC)
) s
WHERE row_count = 1 AND order_date < CURRENT_DATE - 30

Решение с DISTINCT ON (https://www.postgresql.org/docs/9.5/static/sql-select.html#SQL-DISTINCT):

SELECT
    customer, order_date, order_total
FROM (
    SELECT DISTINCT ON (customer)
        *, 
        first_value(order_date) OVER w as last_order, 
        first_value(order_total) OVER w as last_total
    FROM orders
    WINDOW w AS (PARTITION BY customer ORDER BY order_date DESC)
    ORDER BY customer, order_date DESC
) s
WHERE order_date < CURRENT_DATE - 30

Объяснение:

В обоих решениях я работаю с оконной функцией first_value. Рамка оконной функцииопределяется клиентами. Строки в группах клиентов упорядочены по убыванию по дате, что дает последнюю строку первой (last_value не работает, как ожидалось, каждый раз ). Таким образом, можно получить последнююorder_date и последние order_total этого порядка.

Разница между решениями заключается в фильтрации. Я показал обе версии, потому что иногда одна из них значительно быстрее

Стиль оконной функциисоздает количество строк в кадрах. Каждая первая строка может быть отфильтрована позже. Это достигается путем добавления оконной функции row_number. Преимущество этого решения проявляется, когда вы пытаетесь отфильтровать первые два или три набора данных.Вы просто чПопробуйте изменить фильтр с WHERE row_count = 1 на WHERE row_count = 2

Но если вы хотите, чтобы в каждой группе была только одна строка, вам просто нужно убедиться, что ожидаемая строка в каждой группе упорядочена как первая строка в группе.,Затем функция DISTINCT ON может удалить все последующие строки.DISTINCT ON (customer) дает первую (упорядоченную) строку для customer группы.

0 голосов
/ 22 сентября 2018

Попытка объединить таблицу сама по себе

select o1.customer, max(order_date),
from orders o1
join orders o2 on o1.id=o2.id
group by o1.customer
having max(o1.order_date) < NOW() - '30 days'::interval

Подзапросы в select - плохая идея, потому что DB выполнит запрос для каждой строки

Если вы используете postgres, вы также можете попробоватьиспользуйте CTE

https://www.postgresql.org/docs/9.6/static/queries-with.html

WITH t as (
select id, order_total from orders o2 where o2.customer = orders.customer 
order by order_date desc limit 1
) select o1.customer, max(order_date),
from orders o1
join t t.id=o2.id
group by o1.customer
having max(order_date) < NOW() - '30 days'::interval
...