我有一个查询,试图(非常天真地)实现对国家名称的模糊搜索:
WITH matching_countries AS (
SELECT * FROM directory.country WHERE id = $1
UNION
SELECT * FROM directory.country
WHERE
alpha2 = $2 OR
alpha3 = $2 OR
name = $2 OR
canonical_name = $2 OR
name LIKE $3 OR
canonical_name LIKE $3
UNION
SELECT country.* FROM directory.country country
LEFT JOIN directory.country_alias cAlias
ON cAlias.country_id = country.id
WHERE
cAlias.name = $2 OR
cAlias.name LIKE $3
)
SELECT * FROM matching_countries
ORDER BY LENGTH(name) ASC
LIMIT 1理想情况下,一旦找到精确匹配,这个查询就应该“放弃”扫描表,当您向下的每一行时,我们都会牺牲准确性来进行模糊匹配。我原以为LIMIT 1会这么做,但它似乎从LIKE过滤器中获得了明显的假阳性。
我怎样才能让它“放弃”/提前返回比赛?我正在优化它,因为我从应用程序代码中实现了多个单独的查询,但是这样做有点慢,而不是UNION、-ing、all查询。
发布于 2021-03-03 16:27:46
首先,我将使用UNION ALL而不是UNION来避免额外的排序。
如果您想对结果进行排序,请使用以下内容
WITH matching_countries AS (
SELECT *,
1 AS match_level
FROM directory.country
WHERE /* first condition */
UNION ALL
SELECT *,
2 AS match_level
FROM directory.country
WHERE /* second condition */
UNION
...
)
SELECT *
FROM matching_countries
ORDER BY match_level, length(name)
LIMIT 1;这仍然将执行所有UNION ALL分支,但它将从返回结果的第一个子查询返回一个匹配项。
发布于 2021-03-03 19:20:25
这似乎是可行的:
SELECT version();
CREATE TABLE country
( id integer not null primary key
, name text
, canonical_name text
);
INSERT INTO country(id, name, canonical_name) VALUES
( 1, 'United Kingdom', 'UK' )
, ( 2, 'Deutchland', 'DE' )
, ( 3, 'France', 'FR' )
, ( 4, 'Nederland', 'NL' )
;
CREATE TABLE country_alias
( id SERIAL not null primary key
, country_id integer not null REFERENCES country(id)
, name text
);
INSERT INTO country_alias(country_id, name) VALUES
( 1, 'Britain' )
, ( 1, 'Great Britain' )
, ( 2, 'Germany')
, ( 3, 'Marianne')
, ( 3, 'Ohlala')
, ( 4, 'Holland' )
, ( 4, 'Kaaskoppen' )
;
----------
prepare onion(integer, text, text) AS
WITH one AS (
SELECT 'one' AS tag, * FROM country
WHERE id = $1
)
, two AS (
SELECT 'two' AS tag, * FROM country
WHERE NOT EXISTS ( SELECT * FROM one)
AND ( name = $2
OR name ILIKE $3
OR canonical_name = $2
OR canonical_name ILIKE $3
)
)
, three AS (
SELECT 'three' AS tag, *
FROM country co
WHERE NOT EXISTS ( SELECT * FROM two)
AND EXISTS ( SELECT *
FROM country_alias ca
WHERE ca.country_id = co.id
AND ( ca.name = $2 OR ca.name ILIKE $3)
)
)
SELECT * FROM (
SELECT * FROM one
UNION ALL SELECT * FROM two
UNION ALL SELECT * FROM three
) six
-- ORDER BY LENGTH(name) ASC
LIMIT 1
;
-- EXPLAIN ANALYZE EXECUTE onion(1,'Huge Britain', 'Uk');
EXPLAIN ANALYZE EXECUTE onion(6,'Huge Britain', 'U%');
EXECUTE onion(9,'Ned', 'kaas%');
EXPLAIN ANALYZE EXECUTE onion(11,'Huge Britain', 'U%');结果:
PostgreSQL 11.6 on armv7l-unknown-linux-gnueabihf, compiled by gcc (Raspbian 8.3.0-6+rpi1) 8.3.0, 32-bit
(1 row)
CREATE TABLE
INSERT 0 4
CREATE TABLE
INSERT 0 7
PREPARE
QUERY PLAN
----------------------------------------------------------------------------------------------------------------------------------------------------------------
Limit (cost=90.37..90.39 rows=1 width=100) (actual time=0.163..0.165 rows=1 loops=1)
CTE one
-> Index Scan using country_pkey on country (cost=0.15..2.37 rows=1 width=100) (actual time=0.054..0.055 rows=0 loops=1)
Index Cond: (id = 6)
CTE two
-> Result (cost=0.02..27.02 rows=850 width=100) (actual time=0.083..0.084 rows=1 loops=1)
One-Time Filter: (NOT $1)
InitPlan 2 (returns $1)
-> CTE Scan on one one_1 (cost=0.00..0.02 rows=1 width=0) (actual time=0.002..0.003 rows=0 loops=1)
-> Seq Scan on country country_1 (cost=0.00..27.00 rows=850 width=68) (actual time=0.069..0.070 rows=1 loops=1)
Filter: ((name = 'Huge Britain'::text) OR (name ~~* 'U%'::text) OR (canonical_name = 'Huge Britain'::text) OR (canonical_name ~~* 'U%'::text))
CTE three
-> Result (cost=35.52..60.98 rows=425 width=100) (never executed)
One-Time Filter: (NOT $3)
InitPlan 4 (returns $3)
-> CTE Scan on two two_1 (cost=0.00..17.00 rows=850 width=0) (never executed)
-> Hash Join (cost=35.50..60.96 rows=425 width=68) (never executed)
Hash Cond: (co.id = ca.country_id)
-> Seq Scan on country co (cost=0.00..18.50 rows=850 width=68) (never executed)
-> Hash (cost=33.00..33.00 rows=200 width=4) (never executed)
-> HashAggregate (cost=31.00..33.00 rows=200 width=4) (never executed)
Group Key: ca.country_id
-> Seq Scan on country_alias ca (cost=0.00..28.00 rows=1200 width=4) (never executed)
Filter: ((name = 'Huge Britain'::text) OR (name ~~* 'U%'::text))
-> Append (cost=0.00..31.90 rows=1276 width=100) (actual time=0.158..0.159 rows=1 loops=1)
-> CTE Scan on one (cost=0.00..0.02 rows=1 width=100) (actual time=0.060..0.061 rows=0 loops=1)
-> CTE Scan on two (cost=0.00..17.00 rows=850 width=100) (actual time=0.092..0.093 rows=1 loops=1)
-> CTE Scan on three (cost=0.00..8.50 rows=425 width=100) (never executed)
Planning Time: 2.560 ms
Execution Time: 1.169 ms
(30 rows)
tag | id | name | canonical_name
-------+----+-----------+----------------
three | 4 | Nederland | NL
(1 row)查询计划的最重要细节是最后几行:
-> CTE Scan on one (cost=0.00..0.02 rows=1 width=100)
(actual time=0.060..0.061 rows=0 loops=1)
-> CTE Scan on two (cost=0.00..17.00 rows=850 width=100)
(actual time=0.092..0.093 rows=1 loops=1)
-> CTE Scan on three (cost=0.00..8.50 rows=425 width=100)
(never executed)因此,one不产生任何行,导致使用two,这确实会导致某些行,因此根本不需要计算three。(在本例中,估计值为off,因为新创建的表没有有效的统计信息)
https://stackoverflow.com/questions/66460546
复制相似问题