0

我正在研究一些 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;

发票输出

4

2 回答 2

0

这可以通过递归查询来完成,该查询将为我们需要查找第一张和最后 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 - 该月所有选定发票的 ID

type - 所选发票的类型(“前 10 个”或“后 10 个”)。

因此,在当前实施中,您每个月将有 2 行(如果该月至少有 1 张发票)。如果需要,您可以轻松地将其加入一行。

于 2018-10-02T08:44:55.580 回答
0

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

不要被长度吓倒。这个查询实际上非常简单;它只需要几个子查询。

这是它在逻辑上所做的:

  • 获取相关会计年度的行
  • 为其月份内的行分配一个“排名”,从开始和结束开始计数
  • 过滤掉当月不在前 10 名的所有内容(从任一方向计数)
  • 添加一个关于它是在月初还是月底的指示器。(请注意,如果一个月内少于 20 行,它会将更多的行归类为“开始”。)
  • 将 ID 聚合在一起

这是为您正在尝试做的工作而设计的工具集。如果确实需要,您可以稍微调整此方法以使它们进入同一行,但您必须先聚合,然后再将结果连接在一起,然后在当月加入;你不能加入然后聚合。

于 2018-10-02T07:39:05.813 回答