Как я могу получить максимальную сумму от суммы для разных продуктов в месяц в Postgresql? - PullRequest
1 голос
/ 08 октября 2019

Я только недавно начал использовать Postgresql. У меня есть таблица с именем 'sales'.

create table sales
    (
        cust    varchar(20),
        prod    varchar(20),
        day integer,
        month   integer,
        year    integer,
        state   char(2),
        quant   integer
    )

insert into sales values ('Bloom', 'Pepsi', 2, 12, 2001, 'NY', 4232);
insert into sales values ('Knuth', 'Bread', 23, 5, 2005, 'PA', 4167);
insert into sales values ('Emily', 'Pepsi', 22, 1, 2006, 'CT', 4404);
insert into sales values ('Emily', 'Fruits', 11, 1, 2000, 'NJ', 4369);
insert into sales values ('Helen', 'Milk', 7, 11, 2006, 'CT', 210);
...

Всего 500 строк, 10 разных товаров и 5 разных покупателей.

Это выглядит так: enter image description here

Теперь мне нужно найти наиболее «популярные» и наименее «популярные» продукты (продукты с наибольшим и наименьшим общим объемом продаж) и соответствующие общие объемы продаж (т. Е. Суммы) для каждогоиз 12 месяцев ( независимо от года ).

Результат должен быть таким:

enter image description here

Теперь я могу написать запрос только так:

select month,
       prod,
       sum(quant)
from sales
group by month,prod
order by month,prod;

И он дает мне такой результат:

enter image description here

СейчасМне нужно подобрать максимальное значение за каждый месяц. Например, самое большое значение в первых 10 суммах месяца 1 и т. Д. ...

Мне также нужно получить минимальное значение сумм ( независимо от года ),И объединить их горизонтально ... Я понятия не имею об этом ...

1 Ответ

2 голосов
/ 09 октября 2019

Примечание: для TLDR, пропустите до конца.

Ваша проблема - очень интересный случай из учебника, так как включает в себя несколько аспектов Postgres.

Я часто нахожу очень полезным разложитьпроблема в нескольких подзадачах перед их объединением для получения окончательного набора результатов.

В вашем случае я вижу две подзадачи: поиск самого популярного продукта за каждый месяц и поиск наименее популярного продукта за каждый месяц.

Давайте начнем с самых популярных продуктов:

WITH months AS (
  SELECT generate_series AS month
  FROM generate_series(1, 12)
)
SELECT DISTINCT ON (month)
  month,
  prod,
  SUM(quant)
FROM months
LEFT JOIN sales USING (month)
GROUP BY month, prod
ORDER BY month, sum DESC;

Пояснения:

  • WITH это общее табличное выражение , которое действуеткак временная таблица (на время запроса) и помогает уточнить запрос. Если вас это смущает, вы также можете выбрать подзапрос.
  • generate_series(1, 12) - это функция Postgres , которая генерирует серию целых чисел, в данном случае от 1 до 12.
  • LEFT JOIN позволяет нам связать каждую продажу с соответствующим месяцем. Если за данный месяц продажа не найдена, возвращается строка с месяцем и объединенные столбцы со значениями NULL. Более подробную информацию о соединениях можно найти здесь . В вашем случае важно использовать LEFT JOIN, так как использование INNER JOIN исключает продукты, которые никогда не продавались (что в этом случае должно быть наименее популярным).
  • GROUP BY используется для суммированиясверх количеств.
  • на данном этапе, вы должны - потенциально - иметь несколько продуктов за любой данный месяц. Мы хотим сохранить только те из них, которые имеют наибольшее количество за каждый месяц. DISTINCT ON особенно полезно для этой цели. Учитывая столбец, он позволяет нам сохранить первую итерацию каждого значения. Поэтому важно сначала ORDER продавать по сумме, так как будет выбран только первый. Сначала нужно большее число, поэтому следует использовать DESC (в порядке убывания).

Теперь мы можем повторить процесс для наименее популярных продуктов:

WITH months AS (
  SELECT generate_series AS month
  FROM generate_series(1, 12)
)
SELECT DISTINCT ON (month)
  month,
  prod,
  SUM(quant)
FROM months
LEFT JOIN sales USING (month)
GROUP BY month, prod
ORDER BY month, sum;

Заключение (и TLDR):

Теперь нам нужно объединить два запроса в один итоговый запрос.

WITH months AS (
  SELECT generate_series AS month
  FROM generate_series(1, 12)
), agg_sales AS (
  SELECT
    month,
    prod,
    SUM(quant)
  FROM months
  LEFT JOIN sales USING (month)
  GROUP BY month, prod
), most_popular AS (
  SELECT DISTINCT ON (month)
    month,
    prod,
    sum
  FROM agg_sales
  ORDER BY month, sum DESC
), least_popular AS (
  SELECT DISTINCT ON (month)
    month,
    prod,
    sum
  FROM agg_sales
  ORDER BY month, sum
)
SELECT
  most_popular.month,
  most_popular.prod AS most_popular_prod,
  most_popular.sum AS most_pop_total_q,
  least_popular.prod AS least_popular_prod,
  least_popular.sum AS least_pop_total_q
FROM most_popular
  JOIN least_popular USING (month);

Обратите внимание, что я использовал промежуточный agg_sales CTEпопытаться сделать запрос немного понятнее и избегать повторения одной и той же операции дважды, хотя это не должно быть проблемой для оптимизатора Postgres.

Надеюсь, вы найдете мой ответ удовлетворительным. Не стесняйтесь комментировать иное!

РЕДАКТИРОВАТЬ: хотя это решение должно работать как есть, я бы посоветовал хранить ваши даты в виде одного столбца типа TIMESTAMPTZ. Часто гораздо проще манипулировать датами с использованием этого типа, и это всегда хорошая практика, если вам необходимо проанализировать и провести аудит вашей базы данных далее.

Вы можете получить месяц любой даты, просто используя EXTRACT(MONTH FROM date).

...