【问题标题】:Postgres: Simplify SQL query to get rid of subselectsPostgres:简化 SQL 查询以摆脱子选择
【发布时间】:2019-12-16 03:00:15
【问题描述】:

我有一个events 表,其中包含各种创建、完成和失败事件。每个事件都有一个 ID(表中的主键),还有一个“entity_id”,它将多个事件链接在一起。

例如,当一个请求被创建然后完成时,我们会有两个事件:

  • 已创建请求 #42
  • 请求 #42 已完成

在上面的例子中,42 是请求的 entity_id。

CREATE TABLE IF NOT EXISTS events (
    id SERIAL PRIMARY KEY,
    entity_id INTEGER NOT NULL,
    type VARCHAR(255) NOT NULL,
    occurred_at TIMESTAMP NOT NULL
);

INSERT INTO events (entity_id, type, occurred_at) VALUES
(1, 'created', '2019-08-08 11:20:04.791592+00'),
(1, 'completed', '2019-08-08 11:20:05.791592+00'),
(2, 'created', '2019-08-08 11:20:06.791592+00'),
(2, 'failed', '2019-08-08 11:20:07.791592+00'),
(3, 'created', '2019-08-08 11:20:08.791592+00'),
(3, 'completed', '2019-08-08 11:20:09.791592+00');

我想创建该表的视图,以便每个 entity_id 与创建和完成/失败时间相关联。

对该视图的查询应返回以下结果:

 entity_id |         created_at         |        completed_at        |         failed_at          
-----------+----------------------------+----------------------------+----------------------------
         1 | 2019-08-08 11:20:04.791592 | 2019-08-08 11:20:05.791592 | 
         2 | 2019-08-08 11:20:06.791592 |                            | 2019-08-08 11:20:07.791592
         3 | 2019-08-08 11:20:08.791592 | 2019-08-08 11:20:09.791592 |

我尝试了left join,但没有得到任何好的结果。到目前为止,我最好的尝试是这样的:

SELECT
    e.entity_id,
    e.occurred_at as created_at,
    (SELECT occurred_at FROM events WHERE type = 'completed' AND entity_id = e.entity_id) AS completed_at,
    (SELECT occurred_at FROM events WHERE type = 'failed' AND entity_id = e.entity_id) AS failed_at
FROM events e
WHERE e.type = 'created';

这对我来说似乎很不优雅,而且可能效率也很低。

你能推荐一个更好的选择吗?我正在使用 postgres,并且很高兴使用 postgres 特有的功能。

【问题讨论】:

    标签: sql postgresql join subquery


    【解决方案1】:

    您正在寻找数据透视查询:

    SELECT
        entity_id,
        MAX(CASE WHEN type = 'created'   THEN occurred_at END) AS created_at,
        MAX(CASE WHEN type = 'completed' THEN occurred_at END) AS completed_at,
        MAX(CASE WHEN type = 'failed'    THEN occurred_at END) AS failed_at
    FROM events
    GROUP BY
        entity_id
    ORDER BY
        entity_id;
    

    Demo

    【讨论】:

    • 看起来很有趣,我从没见过这个。感谢分享 dbfiddle 链接,我不知道它会很有用!
    【解决方案2】:

    你可以使用窗口函数:

    SELECT e.*
    FROM (SELECT e.entity_id,
                 e.occurred_at as created_at,
                 MAX(e.occurred_at) FILTER (WHERE type = 'completed') OVER (PARTITION BY e.entity_id) AS completed_at,
                 MAX(e.occurred_at) FILTER (WHERE type = 'failed') OVER (PARTITION BY e.entity_id) AS failed_at
          FROM events e
         ) e
    WHERE e.type = 'created';
    

    但是,聚合可能更合适:

    SELECT e.entity_id,
           MAX(e.occurred_at) FILTER (WHERE type = 'created') as created_at,
           MAX(e.occurred_at) FILTER (WHERE type = 'completed') AS completed_at,
           MAX(e.occurred_at) FILTER (WHERE type = 'failed') AS failed_at
    FROM events e
    GROUP BY e.entity_id;
    

    【讨论】:

    • 不错!您的第二个建议与蒂姆的回答之间的主要区别是什么?我尝试EXPLAIN 两个查询,它们产生完全相同的结果,至少在这个小数据集上是这样。这个比另一个有什么优势吗?
    • @aspyct 。 . . Postgres 支持标准的FILTER 子句,而且速度更快一些。 Tim 的答案是我会在其他数据库中使用的答案。
    【解决方案3】:

    您可以尝试使用大小写和(假)聚合来减少行数

    SELECT
        entity_id,
        max(case when  type = 'created' then occurred_at end ) as created_at,
        max(case when  type = 'completed' then occurred_at end)  as completed_at,
        max(case when  type = 'failed' then occurred_at end ) as failed_at,
    FROM events 
    group by entity_id
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2022-01-08
      • 1970-01-01
      相关资源
      最近更新 更多