【问题标题】:Is a non-correlated exists(...) query in the where clause executed for each row or just once?是为每一行执行的 where 子句中的非相关存在(...)查询还是只执行一次?
【发布时间】:2017-07-07 10:13:39
【问题描述】:

我有以下疑问:

SELECT *
FROM   t1, t2
WHERE  t1.some_id = t2.some_id
       and
       not exists(select true from some_table where some_column = true)

这里,not exists(select true from some_table where ...) 不引用主查询中的t1t2

not exists(select true from some_table where ...)只执行一次还是t1t2 之间的乘积中的每个元组 执行,其中t1.some_id = t2.some_id 为真? p>

也就是说,如果只执行一次,如果结果为假,可以立即返回一个空表。从逻辑上讲,我们已经写了:

if (not exists(select true from some_table where some_column = true))
    SELECT *
    FROM   t1, t2
    WHERE  t1.some_id = t2.some_id
else
    empty table

【问题讨论】:

  • 注意:SQL 中没有IF(plpgsql 中有)。并且:EXISTS 子查询只执行一次,即使不相关(检查计划!)如果子查询相关的,它将成为(JOIN)计划的一部分)
  • 我知道 SQL 中没有 if。我是说我们正在模拟if。像这样模拟if 是个好主意吗?
  • 像这样模拟 if 是在纯 SQL 中进行此操作的唯一方法。
  • 我的意思是:我们可以这样做,或者我们可以编写一个 plpgsql 函数来表达一个真正的if。首选哪种方法?

标签: sql postgresql correlated-subquery


【解决方案1】:

首先,学会使用正确的join语法:

SELECT *
FROM t1 JOIN
     t2
     ON t1.some_id = t2.some_id
WHERE not exists(select true from some_table where some_column = true);

子查询应该只执行一次。然而,最终这取决于 Postgres 优化器。您可以使用LEFT JOIN 保证代码只执行一次:

SELECT t1.*, t2.*
FROM t1 JOIN
     t2
     ON t1.some_id = t2.some_id LEFT JOIN
     some_table st
     ON st.some_column = true
WHERE st.some_column IS NULL;

【讨论】:

  • 谢谢。您能否解释一下为什么 left join 强制计划程序只执行一次查询,而 where 子句中的 not exists 不确定? (我已经知道join 语法,不过谢谢。我主要关注那个子查询。)
  • 没有区别,LEFT JOIN ... IS NULLNOT EXISTS(...) 都产生了一个反连接计划。唯一的区别是t2.* 列在外部查询中变得可见。
  • 我仍然没有 100% 关注。 The only difference is that the t2.* columns become visible the outer query. 到底是什么意思?
  • @CarolineKwerts 。 . .这意味着如果您使用select *,您也会从该表中获取列。
  • 谢谢。然后,我仍然很困惑为什么你说用LEFT JOIN ... IS NULL,我们保证查询只会执行一次,但不能用简单的NOT EXISTS。这是为什么呢?
【解决方案2】:

您甚至可以将 EXISTS() 条件包装在 pure SQL 函数中,它只会执行一次:


-- \i tmp.sql

CREATE TABLE omg
        ( id integer NOT NULL PRIMARY KEY
        , must_pay integer NOT NULL
        );
INSERT INTO omg(id, must_pay) VALUES(1,0);

CREATE FUNCTION owe_money() RETURNS BOOLEAN AS
$func$

SELECT EXISTS(SELECT 1
        FROM omg o
        WHERE o.must_pay > 0
        );
$func$
-- language sql;
language sql STABLE;

EXPLAIN
SELECT owe_money();

INSERT INTO omg(id, must_pay) VALUES(2,100);

EXPLAIN
SELECT owe_money();

EXPLAIN
SELECT * FROM omg
WHERE owe_money();

如果您将关键字STABLE 添加到函数定义中,DBMS 将知道返回值将不会更改(在同一事务/语句中)。如果没有STABLE,该函数将每行调用一次。


                                           QUERY PLAN                                            
-------------------------------------------------------------------------------------------------
 Seq Scan on omg  (cost=0.00..566.40 rows=713 width=8) (actual time=0.303..0.331 rows=2 loops=1)
   Filter: owe_money()
 Total runtime: 0.384 ms
(3 rows)

STABLE 函数的结果是一次性过滤器:


                                             QUERY PLAN                                               
-------------------------------------------------------------------------------------------------------
 Result  (cost=0.25..31.65 rows=2140 width=8) (actual time=0.304..0.316 rows=2 loops=1)
   One-Time Filter: owe_money()
   ->  Seq Scan on omg  (cost=0.25..31.65 rows=2140 width=8) (actual time=0.004..0.009 rows=2 loops=1)
 Total runtime: 0.379 ms

(4 行)


与不相关子查询相同的条件本质上产生与 STABLE 函数相同的计划(现在子查询执行一次,其结果 ($0) 在一次性过滤器中进行测试):


EXPLAIN ANALYZE
SELECT * FROM omg
WHERE EXISTS (
        SELECT 1
        FROM omg o
        WHERE o.must_pay > 0
        );

                                                QUERY PLAN                                                
----------------------------------------------------------------------------------------------------------
 Result  (cost=0.05..31.45 rows=2140 width=8) (actual time=0.022..0.034 rows=2 loops=1)
   One-Time Filter: $0
   InitPlan 1 (returns $0)
     ->  Seq Scan on omg o  (cost=0.00..36.75 rows=713 width=0) (actual time=0.011..0.011 rows=1 loops=1)
           Filter: (must_pay > 0)
           Rows Removed by Filter: 1
   ->  Seq Scan on omg  (cost=0.00..31.40 rows=2140 width=8) (actual time=0.003..0.008 rows=2 loops=1)
 Total runtime: 0.081 ms
(8 rows)

但是我们不是还在扫描表格,如果该函数返回 false,则基本上执行 where false 每一行,或者如果该函数返回 true,则执行 where true?


EXPLAIN ANALYZE
SELECT * FROM omg
WHERE EXISTS (
        SELECT 1
        FROM omg o
        WHERE o.id < 0
        );

                                                         QUERY PLAN                                                          
-----------------------------------------------------------------------------------------------------------------------------
 Result  (cost=1.66..3.66 rows=100 width=8) (actual time=0.011..0.011 rows=0 loops=1)
   One-Time Filter: $0
   InitPlan 1 (returns $0)
     ->  Index Only Scan using omg_pkey on omg o  (cost=0.14..1.66 rows=1 width=0) (actual time=0.006..0.006 rows=0 loops=1)
           Index Cond: (id < 0)
           Heap Fetches: 0
   ->  Seq Scan on omg  (cost=0.00..2.00 rows=100 width=8) (never executed)
 Total runtime: 0.063 ms
(8 rows)

从未执行过说明了一切。

【讨论】:

  • 执行最后一个查询 (SELECT * FROM omg WHERE owe_money()) 时,规划器给出:"Seq Scan on omg (cost=0.00..597.60 rows=753 width=8)" " Filter: owe_money()"。那么这不是说每行都会执行吗?
  • stable 关键字,明白了。该函数现在执行一次,因此查询执行一次。但是,如果该函数返回 false 或 where true 如果该函数返回 true,我们是否仍在扫描表格并基本上执行where false 每一行
  • 谢谢,很有见地!
猜你喜欢
  • 2013-01-23
  • 1970-01-01
  • 1970-01-01
  • 2011-02-13
  • 2019-12-17
  • 1970-01-01
  • 2015-09-12
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多