【问题标题】:Same output in two different lateral joins两个不同的横向连接中的相同输出
【发布时间】:2019-03-07 06:46:06
【问题描述】:

我正在开发一些 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;

【问题讨论】:

    标签: sql postgresql aggregate-functions lateral-join


    【解决方案1】:

    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 聚合在一起

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

    【讨论】:

      【解决方案2】:

      这可以通过递归查询来完成,该查询将生成我们需要查找第一张和最后 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 张发票)。如果需要,您可以轻松地将其加入一行。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2022-01-11
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2020-02-17
        • 2011-03-19
        相关资源
        最近更新 更多