如何在加入/下推到外部服务器之前强制评估子查询
How to force evaluation of subquery before joining / pushing down to foreign server
假设我想用几个 WHERE
过滤器查询一个大的 table。我正在使用 Postgres 11 和一个外国 table;外部数据包装器 (FDW) 是 clickhouse_fdw
。但我也对通用解决方案感兴趣。
我可以这样做:
SELECT id,c1,c2,c3 from big_table where id=3 and c1=2
我的FDW可以对远程的国外数据源做过滤,保证上面的查询速度很快,不会拉下太多数据。
如果我写:
,上面的方法是一样的
SELECT id,c1,c2,c3 from big_table where id IN (3,4,5) and c1=2
即所有过滤都发送到下游。
但是,如果我尝试进行的过滤稍微复杂一些:
SELECT bt.id,bt.c1,bt.c2,bt.c3
from big_table bt
join lookup_table l on bt.id=l.id
where c1=2 and l.x=5
然后查询规划器决定远程过滤 c1=2
但在本地应用另一个过滤器。
在我的用例中,先计算哪些 id
有 l.x=5
,然后将它们发送出去进行远程过滤会快得多,因此我尝试按以下方式编写:
SELECT id,c1,c2,c3
from big_table
where c1=2
and id IN (select id from lookup_table where x=5)
但是,查询规划器仍然决定在本地对来自 big_table
的所有满足 c1=2
的结果执行第二个过滤器,这非常慢。
有什么方法可以 "force" (select id from lookup_table where x=5)
预先计算并作为远程过滤器的一部分发送?
外部数据包装器
通常,连接或来自子查询或 CTE 的任何派生表在外部服务器上不可用,必须在本地执行。即,必须像您观察到的那样在本地检索和处理示例中简单 WHERE
子句之后剩余的所有行。
如果所有其他方法都失败了,您可以执行子查询 SELECT id FROM lookup_table WHERE x = 5
并将结果连接到查询字符串中。
更方便的是,您可以在 PL/pgSQL 函数中使用动态 SQL 和 EXECUTE
自动执行此操作。喜欢:
CREATE OR REPLACE FUNCTION my_func(_c1 int, _l_id int)
RETURNS TABLE(id int, c1 int, c2 int, c3 int) AS
$func$
BEGIN
RETURN QUERY EXECUTE
'SELECT id,c1,c2,c3 FROM big_table
WHERE c1 =
AND id = ANY ()'
USING _c1
, ARRAY(SELECT l.id FROM lookup_table l WHERE l.x = _l_id);
END
$func$ LANGUAGE plpgsql;
相关:
- Table name as a PostgreSQL function parameter
或者您可以在 psql 中使用元命令 \gexec
。参见:
- Filter column names from existing table for SQL DDL statement
或这可能有效:(反馈说无效。)
SELECT id,c1,c2,c3
FROM big_table
WHERE c1 = 2
AND id = ANY (ARRAY(SELECT id FROM lookup_table WHERE x = 5));
在本地测试,我得到这样的查询计划:
Index Scan using big_table_idx on big_table (cost= ...)
Index Cond: (id = ANY ([=22=]))
Filter: (c1 = 2)
InitPlan 1 (returns [=22=])
-> Seq Scan on lookup_table (cost= ...)
Filter: (x = 5)
大胆强调我的。
计划中的参数[=18=]
让人燃起了希望。生成的数组可能是 Postgres 可以传递给远程使用的东西。我没有看到与您的任何其他尝试或我自己尝试过的更多类似的计划。你能用你的 fdw 测试一下吗?
关于postgres_fdw
的相关问题:
- postgres_fdw: possible to push data to foreign server for join?
SQL
中的一般技巧
那是另外一回事了。只需使用 CTE。但我不希望这对 FDW 有帮助。
WITH cte AS (SELECT id FROM lookup_table WHERE x = 5)
SELECT id,c1,c2,c3
FROM big_table b
JOIN cte USING (id)
WHERE b.c1 = 2;
PostgreSQL 12 更改(改进)行为,以便在给定一些先决条件的情况下,CTE 可以像子查询一样内联。但是,引用 the manual:
You can override that decision by specifying MATERIALIZED
to force separate calculation of the WITH query
所以:
WITH cte AS MATERIALIZED (SELECT id FROM lookup_table WHERE x = 5)
...
通常,如果您的数据库服务器配置正确并且列统计信息是最新的,那么 none 应该是必需的。但也有数据分布不均匀的极端情况...
假设我想用几个 WHERE
过滤器查询一个大的 table。我正在使用 Postgres 11 和一个外国 table;外部数据包装器 (FDW) 是 clickhouse_fdw
。但我也对通用解决方案感兴趣。
我可以这样做:
SELECT id,c1,c2,c3 from big_table where id=3 and c1=2
我的FDW可以对远程的国外数据源做过滤,保证上面的查询速度很快,不会拉下太多数据。
如果我写:
,上面的方法是一样的SELECT id,c1,c2,c3 from big_table where id IN (3,4,5) and c1=2
即所有过滤都发送到下游。
但是,如果我尝试进行的过滤稍微复杂一些:
SELECT bt.id,bt.c1,bt.c2,bt.c3
from big_table bt
join lookup_table l on bt.id=l.id
where c1=2 and l.x=5
然后查询规划器决定远程过滤 c1=2
但在本地应用另一个过滤器。
在我的用例中,先计算哪些 id
有 l.x=5
,然后将它们发送出去进行远程过滤会快得多,因此我尝试按以下方式编写:
SELECT id,c1,c2,c3
from big_table
where c1=2
and id IN (select id from lookup_table where x=5)
但是,查询规划器仍然决定在本地对来自 big_table
的所有满足 c1=2
的结果执行第二个过滤器,这非常慢。
有什么方法可以 "force" (select id from lookup_table where x=5)
预先计算并作为远程过滤器的一部分发送?
外部数据包装器
通常,连接或来自子查询或 CTE 的任何派生表在外部服务器上不可用,必须在本地执行。即,必须像您观察到的那样在本地检索和处理示例中简单 WHERE
子句之后剩余的所有行。
如果所有其他方法都失败了,您可以执行子查询 SELECT id FROM lookup_table WHERE x = 5
并将结果连接到查询字符串中。
更方便的是,您可以在 PL/pgSQL 函数中使用动态 SQL 和 EXECUTE
自动执行此操作。喜欢:
CREATE OR REPLACE FUNCTION my_func(_c1 int, _l_id int)
RETURNS TABLE(id int, c1 int, c2 int, c3 int) AS
$func$
BEGIN
RETURN QUERY EXECUTE
'SELECT id,c1,c2,c3 FROM big_table
WHERE c1 =
AND id = ANY ()'
USING _c1
, ARRAY(SELECT l.id FROM lookup_table l WHERE l.x = _l_id);
END
$func$ LANGUAGE plpgsql;
相关:
- Table name as a PostgreSQL function parameter
或者您可以在 psql 中使用元命令 \gexec
。参见:
- Filter column names from existing table for SQL DDL statement
或这可能有效:(反馈说无效。)
SELECT id,c1,c2,c3
FROM big_table
WHERE c1 = 2
AND id = ANY (ARRAY(SELECT id FROM lookup_table WHERE x = 5));
在本地测试,我得到这样的查询计划:
Index Scan using big_table_idx on big_table (cost= ...) Index Cond: (id = ANY ([=22=])) Filter: (c1 = 2) InitPlan 1 (returns [=22=]) -> Seq Scan on lookup_table (cost= ...) Filter: (x = 5)
大胆强调我的。
计划中的参数[=18=]
让人燃起了希望。生成的数组可能是 Postgres 可以传递给远程使用的东西。我没有看到与您的任何其他尝试或我自己尝试过的更多类似的计划。你能用你的 fdw 测试一下吗?
关于postgres_fdw
的相关问题:
- postgres_fdw: possible to push data to foreign server for join?
SQL
中的一般技巧那是另外一回事了。只需使用 CTE。但我不希望这对 FDW 有帮助。
WITH cte AS (SELECT id FROM lookup_table WHERE x = 5)
SELECT id,c1,c2,c3
FROM big_table b
JOIN cte USING (id)
WHERE b.c1 = 2;
PostgreSQL 12 更改(改进)行为,以便在给定一些先决条件的情况下,CTE 可以像子查询一样内联。但是,引用 the manual:
You can override that decision by specifying
MATERIALIZED
to force separate calculation of the WITH query
所以:
WITH cte AS MATERIALIZED (SELECT id FROM lookup_table WHERE x = 5)
...
通常,如果您的数据库服务器配置正确并且列统计信息是最新的,那么 none 应该是必需的。但也有数据分布不均匀的极端情况...