随机()子查询中set-returns函数的不一致行为

时间:2016-09-21 18:42:06

标签: sql postgresql

我经常使用WHERE子句random() > 0.5来挑选我的数据的随机子集。现在我注意到,当在子查询中使用set-returns函数时,我得到整个集合或者没有(意味着WHERE random()> 0.5子句被解释为之前集合是被生成)。 e.g:

SELECT num 
FROM (
    SELECT unnest(Array[1,2,3,4,5,6,7,8,9,10]) num
) AS foo 
WHERE random() > 0.5;

这似乎不一致,因为以下查询 将整个集合考虑在内:

SELECT num 
FROM (
    SELECT unnest(Array[1,2,3,4,5,6,7,8,9,10]) num
) AS foo 
WHERE random() > 0.1 * num;

我是否认为这是不一致的还是有意义的?

注意:

  • 无法找到另一个与random()分开测试的功能,但可能还有一些

  • 我也使用generate_series进行了测试

3 个答案:

答案 0 :(得分:3)

在第一个查询中,where子句中的表达式执行一次,因为它与select列表中的列无关:

Result  (cost=0.01..0.51 rows=100 width=0) (actual time=0.017..0.021 rows=10 loops=1)
  One-Time Filter: (random() > '0.5'::double precision)
Planning time: 0.156 ms
Execution time: 0.058 ms

在第二种情况下,where表达式取决于列:

Subquery Scan on foo  (cost=0.00..2.76 rows=33 width=4) (actual time=0.052..0.083 rows=5 loops=1)
  Filter: (random() > ((0.1 * (foo.num)::numeric))::double precision)
  Rows Removed by Filter: 5
  ->  Result  (cost=0.00..0.51 rows=100 width=0) (actual time=0.017..0.022 rows=10 loops=1)
Planning time: 0.119 ms
Execution time: 0.137 ms

答案 1 :(得分:2)

你是对的,这似乎非常不一致。

这里的关键点是random()VOLATILE,(理论上)意味着查询计划程序不应该优化对此函数的任何调用。

有趣的是,只有在使用SELECT f()调用set-returns函数时才会发生这种情况,而不是SELECT * FROM f();此查询给出了预期结果:

SELECT num 
FROM (
    SELECT * FROM unnest(Array[1,2,3,4,5,6,7,8,9,10]) num
) AS foo 
WHERE random() > 0.5;

我不知道这是一个错误还是一个已知的限制,因为有类似的情况需要这种行为。例如,比较以下内容:

SELECT random() FROM generate_series(1,10);          -- 10 random numbers
SELECT (SELECT random()) FROM generate_series(1,10); -- 10 copies of the same random number

如果你在这里没有得到明确的答案,你可能想要询问Postgres mailing list你所看到的行为是否有意。

答案 2 :(得分:1)

事实上,postgres邮件列表给出了很好的回复,很可能是一个错误。

这是Tom Lane的答案,包括解决方法:

嗯,我认为这是一个优化错误。有两种合法的行为 这里:

SELECT * FROM unnest(ARRAY[1,2,3,4,5,6,7,8,9,10]) WHERE random() > 0.5;

应该(并且确实)重新评估unfst()输出的每一行的WHERE。

SELECT unnest(ARRAY[1,2,3,4,5,6,7,8,9,10]) WHERE random() > 0.5;

应该只评估WHERE一次,因为这会在扩展之前发生 目标列表中的set-returns函数。 (如果您是Oracle用户并且 你认为这个查询有一个隐含的“FROM dual”,WHERE应该 评估来自FROM子句的单行。)

如果你到了这里,给定WHERE在外面的位置 查询,您当然希望对每一行进行评估 内部查询。但是优化器决定它可以推动WHERE 子句下来成为子选择的WHERE。这是合法的 很多情况,但不是在子选择中有SRF的情况 targetlist,因为它推动WHERE在SRF之前发生, 类似于我写的两个查询之间的变化。

我对现有版本中的更改有点犹豫。鉴于缺乏 以前的投诉,似乎更有可能破坏查询 表现如预期,而不是让人开心。但我们可以改变它 在v10及以上,特别是因为其他一些角落的变化 SRF-in-tlist行为正在进行中。

与此同时,您可以通过插入来强制它按照您的意愿工作 在子选择中的通用优化围栏“OFFSET 0”:

=# SELECT num FROM (
    SELECT unnest(Array[1,2,3,4,5,6,7,8,9,10]) num OFFSET 0) AS foo WHERE random() > 0.5;
 num
-----
   1
   4
   7
   9
(4 rows)