Одинаковый вывод в двух разных боковых соединениях - PullRequest
0 голосов
/ 02 октября 2018

Я работаю над PostgreSQL, чтобы получать первые 10 и последние 10 счетов каждого месяца между определенными датами.У меня неожиданный выход в боковых соединениях.Во-первых, ограничение не работает, и каждый из агрегатов array_agg возвращает сотни строк вместо ограничения 10. Во-вторых, агрегаты кажутся одинаковыми, даже если один упорядочен ASC, а другой DESC.

Как получить только первые 10 и последние 10 счетов каждой группы за месяц?

SELECT first.invoice_month,
       array_agg(first.id) first_ten,
       array_agg(last.id) last_ten
FROM public.invoice i
   JOIN LATERAL (
      SELECT id, to_char(invoice_date, 'Mon-yy') AS invoice_month
      FROM  public.invoice
      WHERE id = i.id
      ORDER BY invoice_date, id ASC
      LIMIT 10
   ) first ON i.id = first.id
   JOIN LATERAL (
      SELECT id, to_char(invoice_date, 'Mon-yy') AS invoice_month
      FROM public.invoice
      WHERE id = i.id
      ORDER BY invoice_date, id DESC
      LIMIT 10
   ) last on i.id = last.id
WHERE i.invoice_date BETWEEN date '2017-10-01' AND date '2018-09-30'
GROUP BY first.invoice_month, last.invoice_month;

invoice output

Ответы [ 2 ]

0 голосов
/ 02 октября 2018

Это можно сделать с помощью рекурсивного запроса, который сгенерирует интервал месяцев, для которого нам нужно найти первый и последний 10 счетов.

WITH RECURSIVE all_months AS (
  SELECT date_trunc('month','2018-01-01'::TIMESTAMP) as c_date, date_trunc('month', '2018-05-11'::TIMESTAMP) as end_date, to_char('2018-01-01'::timestamp, 'YYYY-MM') as current_month
  UNION
  SELECT c_date + interval '1 month' as c_date,
    end_date,
    to_char(c_date + INTERVAL '1 month', 'YYYY-MM') as current_month
  FROM all_months
  WHERE c_date + INTERVAL '1 month' <= end_date
),
  invocies_with_month as (
    SELECT *, to_char(invoice_date::TIMESTAMP, 'YYYY-MM') invoice_month FROM invoice
  )
SELECT current_month, array_agg(first_10.id), 'FIRST 10' as type FROM all_months
JOIN LATERAL (
    SELECT * FROM invocies_with_month
    WHERE all_months.current_month = invoice_month AND invoice_date >= '2018-01-01' AND invoice_date <= '2018-05-11'
    ORDER BY invoice_date ASC limit 10
  ) first_10 ON TRUE
GROUP BY current_month
UNION
SELECT current_month, array_agg(last_10.id), 'LAST 10' as type FROM all_months
JOIN LATERAL (
      SELECT * FROM invocies_with_month
    WHERE all_months.current_month = invoice_month AND invoice_date >= '2018-01-01' AND invoice_date <= '2018-05-11'
    ORDER BY invoice_date DESC limit 10
) last_10 ON TRUE
GROUP BY current_month;

В приведенном выше коде '2018-01-01 'и' 2018-05-11 'представляют даты между тем, когда мы хотим найти счета.На основе этих дат мы генерируем месяцы (2018-01, 2018-02, 2018-03, 2018-04, 2018-05), для которых нам нужно найти счета.Мы храним эти данные в all_months .

После того, как мы получим месяцы, мы выполняем боковое объединение, чтобы присоединяться к счетам за каждый месяц.Нам нужно 2 боковых соединения, чтобы получить первые и последние 10 счетов.Наконец, результат представляется в виде:

current_month - месяц

array_agg - идентификаторы всех выбранных счетов за этот месяц

type - тип выбранных счетов-фактур («первые 10» или «последние 10»).

Таким образом, в текущей реализации у вас будет 2 строки для каждого месяца (если естьпо крайней мере 1 счет за этот месяц).Вы можете легко объединить это в один ряд, если вам нужно.

0 голосов
/ 02 октября 2018

LIMIT работает нормально.Это ваш запрос, который не работает.JOIN - это на 100% неправильный инструмент;он даже не делает ничего похожего на то, что вам нужно.Соединяя до 10 строк и до 10 строк, вы получаете до 100 строк назад.Там также нет причин для самостоятельного присоединения только для объединения фильтров.

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

SELECT
    invoice_month,
    time_of_month,
    ARRAY_AGG(id) invoice_ids
FROM (
    SELECT
        id,
        invoice_month,
        -- Categorize as end or beginning of month
        CASE
            WHEN month_rank <= 10 THEN 'beginning'
            WHEN month_reverse_rank <= 10 THEN 'end'
            ELSE 'bug' -- Should never happen. Just a fall back in case of a bug.
        END AS time_of_month
    FROM (
        SELECT
            id,
            invoice_month,
            dense_rank() OVER (PARTITION BY invoice_month ORDER BY invoice_date) month_rank,
            dense_rank() OVER (PARTITION BY invoice_month ORDER BY invoice_date DESC) month_rank_reverse
        FROM (
            SELECT
                id,
                invoice_date,
                to_char(invoice_date, 'Mon-yy') AS invoice_month
            FROM public.invoice
            WHERE invoice_date BETWEEN date '2017-10-01' AND date '2018-09-30'
        ) AS fiscal_year_invoices
    ) ranked_invoices
    -- Get first and last 10
    WHERE month_rank <= 10 OR month_reverse_rank <= 10
) first_and_last_by_month
GROUP BY
    invoice_month,
    time_of_month

Не пугайтесь длины.Этот запрос на самом деле очень прост;ему просто нужно было несколько подзапросов.

Вот что он делает логически:

  • Извлекает строки для рассматриваемого финансового года
  • Назначает "ранг"строка в месяце, считая как с начала, так и с конца
  • Отфильтруйте все, что не входит в десятку лучших за месяц (считая в любом направлении)
  • Добавляетиндикатор того, было ли это в начале или в конце месяца.(Обратите внимание, что если в месяце менее 20 строк, он классифицирует большее их количество как «начало».)
  • Объедините идентификаторы вместе

Этот набор инструментов предназначенза работу, которую вы пытаетесь сделать.Если это действительно необходимо, вы можете немного изменить этот подход, чтобы поместить их в одну строку, но вам нужно агрегировать, прежде чем объединить результаты вместе, а затем присоединиться к месяцу;Вы не можете присоединиться, а затем объединиться.

...