具有不同条件的多个相关子查询到同一个表

时间:2016-06-11 11:54:21

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

我有两张桌子:

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始终是已进入但未过期的股票,如果已过期,则显示为空,include_before将始终显示下一个传入的股票enter_on,除非有&# 39; s 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部分),但我担心在select中使用两个子查询会导致性能问题。

有没有人有其他建议?

更新

  

我使用Postgresql 9.4(不能再添加标签)

     

实际问题比我说的更复杂,很多表连在一起和观点,我把它缩小到只有一个表来掌握概念,如果有替代方案

2 个答案:

答案 0 :(得分:1)

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

如果表现不充分,您有两种选择。一个是范围的GIST索引。 (Here是对该问题的有趣讨论。)第二个是另一种查询公式。

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

答案 1 :(得分:1)

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

COALESCE用于相关子查询

首先,表达 COALESCE(anything, NULL) 永远是有道理的。你可以用NULL替换NULL。

聚合函数如max()无论如何返回NULL(并且从不“没有行”),即使找不到符合条件的行也是如此。 (count()例外,返回0)。

一个返回“no row”的相关子查询(就像我将在下面演示的带ORDER BY ... LIMIT 1的变体一样)默认为列值为NULL。

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

查询

  

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

取决于。

如果 ,表item_id中每个stocks只有几个行和/或只有一个索引{{ 1}},然后将两个相关子查询合并到带有条件聚合的一个 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条款要求Postgres 9.4+。旧版本还有其他选择:

如果 ,另一方面,表FILTER item_id有多个行>和索引stocks,您的查询可能仍然更快。或者这个稍微适应的版本(测试两个!):

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; 。但是我不会创建太多专门的索引,除非你实际上需要为这个查询挤出更多的读取性能(关键词:过早优化)。

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