【问题标题】:Multiple correlated subqueries with different conditions to same table对同一张表具有不同条件的多个相关子查询
【发布时间】:2016-10-12 07:36:03
【问题描述】:

我有两张桌子:

orders

| id | item_id | quantity | ordered_on |
|----|---------|----------|------------|
|  1 |    1    |    2     | 2016-03-09 |
|  2 |    1    |    2     | 2016-03-12 |
|  3 |    4    |    3     | 2016-03-15 |
|  4 |    4    |    3     | 2016-03-13 |

stocks

| id | item_id | quantity | enter_on   | expire_on  |
|----|---------|----------|------------|------------|
|  1 |    1    |   10     | 2016-03-07 | 2016-03-10 |
|  2 |    1    |   20     | 2016-03-11 | 2016-03-15 |
|  3 |    1    |   20     | 2016-03-14 | 2016-03-17 |
|  4 |    4    |   10     | 2016-03-14 |    NULL    |
|  5 |    4    |   10     | 2016-03-12 |    NULL    |

我正在尝试创建一个视图来显示订单及其最接近的库存enter_on 就像这样(我使用include_afterinclude_before 来概述我想排除该项目的日期这是预购的,所以库存会正确反映。)

include_after 始终是进来但尚未过期的库存,如果过期,显示 NULL,include_before 将始终显示下一个库存 enter_on,除非有一个早于的 expire_on下一个enter_on

| item_id | quantity | ordered_on | include_after | include_before |
|---------|----------|------------|---------------|----------------|
|    1    |    2     | 2016-03-09 |  2016-03-07   |   2016-03-10   |
|    1    |    2     | 2016-03-12 |  2016-03-11   |   2016-03-14   |
|    4    |    3     | 2016-03-13 |  2016-03-12   |   2016-03-14   |
|    4    |    3     | 2016-03-15 |  2016-03-14   |      NULL      |

所以这就是我想出的:

SELECT
  o.item_id, o.quantity, o.order_on, (
    SELECT COALESCE(MAX(s.enter_on), NULL::DATE)
    FROM stocks s
    WHERE s.enter_on <= o.order_on AND s.item_id = o.item_id
  ) as include_after, (
    SELECT COALESCE(MIN(s.enter_on), NULL::DATE)
    FROM stocks s
    WHERE s.enter_on > o.order_on AND s.item_id = o.item_id
  ) as include_before
FROM
  orders o;

它工作正常(我没有包含expire_on 部分),但我担心在选择中使用两个子查询会出现性能问题。

有人有其他建议吗?

更新

我正在使用 Postgresql 9.4(无法再添加标签)

实际的问题比我说的要复杂得多,很多表和视图连接在一起,如果有替代方案,我将其缩小到一张表以掌握概念

【问题讨论】:

  • 实际的表定义会更有用。理想情况下,完整的CREATE TABLE 语句显示数据类型和约束。而且,显然,你的 Postgres 版本。
  • 那么你有答案了吗?

标签: sql ruby-on-rails postgresql greatest-n-per-group postgresql-performance


【解决方案1】:

当情况出现时,您应该担心性能。对于您提供的示例,stocks(item_id, enter_on, expire_on) 上的索引就足够了。那么您实际上可能需要两个索引:stocks(item_id, enter_on desc, expire_on)

如果性能不够,你有两种选择。一种是范围的 GIST 索引。 (Here 是对这个问题的一个有趣的讨论。)第二个是另一种查询公式。

但是,我会尝试优化查询,直到有足够的数据显示性能问题。针对少量数据的解决方案可能无法很好地扩展。

【讨论】:

    【解决方案2】:

    讨论您显示的查询,也不考虑expire_on

    COALESCE 用于关联子查询

    首先,表达式 COALESCE(anything, NULL) never 有意义。您可以将 NULL 替换为 NULL。

    max() 这样的聚合函数无论如何都会返回NULL(并且永远不会“无行”),即使没有找到符合条件的行。 (count() 例外,它返回 0)。

    并且将返回“无行”的相关子查询(如我将在下面演示的带有 ORDER BY ... LIMIT 1 的变体)默认为 NULL 的列值。

    因此,如果您想在此上下文中使用 COALESCE,您可以将其作为一个整体包裹在相关子查询周围 - 并使其返回 NULL 以外的其他内容。

    查询

    我担心在选择中使用两个子查询的性能问题。

    视情况而定。

    如果您在表stocks 中每个item_id 只有几行 行和/或只有一个索引ON stocks (item_id),那么它将 两个 相关子查询合并到 一个 LATERAL 带有条件聚合的子查询:

    SELECT o.item_id, o.quantity, o.order_on
         , s.include_after, s.include_before
    FROM  orders o
        , LATERAL (
       SELECT max(enter_on) FILTER (WHERE enter_on <= o.order_on) AS include_after
            , min(enter_on) FILTER (WHERE enter_on >  o.order_on) AS include_before
       FROM   stocks
       WHERE  item_id = o.item_id
       ) s;
    

    由于LATERAL 子查询在任何情况下都会返回一行(见上文),所以一个简单的CROSS JOIN 就可以了。否则,您可能想使用LEFT JOIN LATERAL (...) ON true。详情:

    聚合 FILTER 子句需要 Postgres 9.4+。旧版本有替代方案:

    如果,另一方面,您在表stocks 中每个item_id许多 em> 索引ON stocks (item_id, enter_on),您的查询可能仍然更快。或者这个稍微改编的版本(测试两者!):

    SELECT o.item_id, o.quantity, o.order_on
       , (SELECT s.enter_on
          FROM   stocks s
          WHERE  s.item_id = o.item_id
          AND    s.enter_on <= o.order_on
          ORDER  BY 1 DESC NULLS LAST
          LIMIT  1) AS include_after
       , (SELECT s.enter_on
          FROM   stocks s
          WHERE  s.item_id = o.item_id
          AND    s.enter_on > o.order_on
          ORDER  BY 1
          LIMIT  1) AS include_before
    FROM  orders o;
    

    因为两个相关的子查询都可以解析为单个索引查找。

    要优化性能,您可能需要第二个索引 ON stocks (item_id, enter_on DESC NULLS LAST)。但我不会创建太多专门的索引,除非您实际上需要为此查询挤出更多的读取性能(关键字:过早优化)。

    此相关答案中的详细讨论:

    【讨论】:

    • 感谢您的建议,实际问题比我说的要复杂得多,它是很多表连接在一起的视图,如果有替代方案,我将其缩小到只有一个表以掌握概念,看来我还有很多阅读工作要做。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-01-05
    • 2016-07-22
    • 2014-12-01
    相关资源
    最近更新 更多