我有两张桌子:
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_after
和include_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(不能再添加标签)
实际问题比我说的更复杂,很多表连在一起和观点,我把它缩小到只有一个表来掌握概念,如果有替代方案
答案 0 :(得分:1)
出现这种情况时,您应该担心性能问题。对于您提供的示例,stocks(item_id, enter_on, expire_on)
上的索引应该足够了。那么您可能实际上需要两个索引:stocks(item_id, enter_on desc, expire_on)
。
如果表现不充分,您有两种选择。一个是范围的GIST索引。 (Here是对该问题的有趣讨论。)第二个是另一种查询公式。
但是,我会尝试优化查询,直到有足够的数据来显示性能问题。对少量数据的解决方案可能无法很好地扩展。
答案 1 :(得分:1)
讨论您显示的查询,也不考虑expire_on
。
COALESCE
用于相关子查询首先,表达 永远是有道理的。你可以用NULL替换NULL。COALESCE(anything, 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;
。但是我不会创建太多专门的索引,除非你实际上需要为这个查询挤出更多的读取性能(关键词:过早优化)。
此相关答案中的详细讨论: