在Microsoft SQL Server中,可以指定“不区分重音”的排序规则(对于数据库,表或列),这意味着可以对诸如
SELECT * FROM users WHERE name LIKE 'João'
查找具有Joao
名称的行。
我知道可以使用unaccent_string contrib函数从PostgreSQL的字符串中去除重音符号,但是我想知道PostgreSQL是否支持这些“不区分重音符号”的排序规则,因此SELECT
上述方法可行。
在Microsoft SQL Server中,可以指定“不区分重音”的排序规则(对于数据库,表或列),这意味着可以对诸如
SELECT * FROM users WHERE name LIKE 'João'
查找具有Joao
名称的行。
我知道可以使用unaccent_string contrib函数从PostgreSQL的字符串中去除重音符号,但是我想知道PostgreSQL是否支持这些“不区分重音符号”的排序规则,因此SELECT
上述方法可行。
Answers:
为此,请使用unaccent模块 -与链接到的模块完全不同。
unaccent是一种文本搜索词典,用于删除词素中的重音符号。
每个数据库安装一次:
CREATE EXTENSION unaccent;
如果出现类似以下错误:
ERROR: could not open extension control file "/usr/share/postgresql/<version>/extension/unaccent.control": No such file or directory
按照相关答案中的指示,在数据库服务器上安装contrib软件包:
除其他外,它提供了unaccent()
可与示例一起使用的功能(LIKE
似乎不需要)。
SELECT *
FROM users
WHERE unaccent(name) = unaccent('João');
要将索引用于此类查询,请在expression上创建一个索引。但是,Postgres仅接受IMMUTABLE
索引功能。如果函数可以为相同的输入返回不同的结果,则索引可能会默默中断。
unaccent()
只是STABLE
没有IMMUTABLE
不幸的是,unaccent()
只是STABLE
,不是IMMUTABLE
。根据pgsql-bugs上的该线程,这是由于三个原因:
search_path
,电流很容易改变。网络上的一些教程指示只是将函数的波动性更改为IMMUTABLE
。在某些情况下,这种暴力破解方法可能会崩溃。
其他人建议使用一个简单的IMMUTABLE
包装函数(就像我过去做的一样)。
是否使用两个参数 IMMUTABLE
显式声明所用字典的变体一直存在争议。在这里或这里阅读。
另一个选择是该模块,由 Github提供,具有Musicbrainz提供的IMMUTABLE unaccent()
功能。我自己还没有测试过。我想我提出了一个更好的主意:
随着其他解决方案的发展,这种方法更有效,也更安全。
创建一个IMMUTABLE
SQL包装函数,该函数使用硬连线的模式限定函数和字典执行两参数形式。
由于嵌套不可改变的函数会禁用函数内联,因此也应基于声明的C函数(伪)副本IMMUTABLE
。它的唯一目的是在SQL函数包装器中使用。不能单独使用。
需要复杂的方法,因为无法在C函数的声明中硬连接字典。(将需要修改C代码本身。)SQL包装函数可以做到这一点,并允许函数内联和表达式索引。
CREATE OR REPLACE FUNCTION public.immutable_unaccent(regdictionary, text)
RETURNS text LANGUAGE c IMMUTABLE PARALLEL SAFE STRICT AS
'$libdir/unaccent', 'unaccent_dict';
CREATE OR REPLACE FUNCTION public.f_unaccent(text)
RETURNS text LANGUAGE sql IMMUTABLE PARALLEL SAFE STRICT AS
$func$
SELECT public.immutable_unaccent(regdictionary 'public.unaccent', $1)
$func$;
PARALLEL SAFE
从Postgres 9.5或更早版本的两个功能中删除。
public
是安装扩展的架构(public
默认设置)。
显式类型声明(regdictionary
)可以防止恶意用户使用功能的重载变体进行假设攻击。
以前,我提倡基于unaccent模块附带的STABLE
函数的包装函数unaccent()
。该禁用功能内联。这个版本的执行速度比我之前在这里提到的简单包装器功能快十倍。
并且这已经是添加SET search_path = public, pg_temp
到该函数的第一个版本的速度的两倍-直到我发现字典也可以通过模式限定。从文档中仍然(Postgres 12)不太明显。
如果您缺少创建C函数所必需的特权,那么您将返回到第二好的实现:IMMUTABLE
函数包装器,用于包装STABLE
unaccent()
模块提供的函数:
CREATE OR REPLACE FUNCTION public.f_unaccent(text)
RETURNS text AS
$func$
SELECT public.unaccent('public.unaccent', $1) -- schema-qualify function and dictionary
$func$ LANGUAGE sql IMMUTABLE PARALLEL SAFE STRICT;
最后,使用表达式索引来快速查询:
CREATE INDEX users_unaccent_name_idx ON users(public.f_unaccent(name));
切记在对函数或字典进行任何更改后,都要重新创建涉及此函数的索引,例如就地升级不会重新创建索引的主发行版。最近的主要发行版均对该unaccent
模块进行了更新。
调整查询以匹配索引(因此查询计划者将使用它):
SELECT * FROM users
WHERE f_unaccent(name) = f_unaccent('João');
您不需要正确的表达式中的函数。您也可以在那里'Joao'
直接提供未重音的字符串。
使用表达式索引,更快的功能不会转换为更快的查询。这对预先计算的值起作用并且已经非常快。但是索引维护和查询不使用索引的好处。
使用Postgres 10.3 / 9.6.8等加强了客户端程序的安全性。如在任何索引中使用时所示,您需要对模式限定的函数和字典名称进行验证。看到:
在Postgres 9.5或更旧的连字中,例如'Œ'或'ß'必须手动扩展(如果需要),因为unaccent()
总是替换单个字母:
SELECT unaccent('Œ Æ œ æ ß');
unaccent
----------
E A e a S
扩展
contrib/unaccent
标准unaccent.rules
文件以处理Unicode已知的所有变音符号,并正确扩展连字(Thomas Munro,LéonardBenedetti)
大胆强调我的。现在我们得到:
SELECT unaccent('Œ Æ œ æ ß');
unaccent
----------
OE AE oe ae ss
对于LIKE
或ILIKE
带有任意模式,请将其与pg_trgm
PostgreSQL 9.1或更高版本中的模块结合使用。创建一个三字母组GIN(通常是首选)或GIST表达索引。GIN示例:
CREATE INDEX users_unaccent_name_trgm_idx ON users
USING gin (f_unaccent(name) gin_trgm_ops);
可以用于以下查询:
SELECT * FROM users
WHERE f_unaccent(name) LIKE ('%' || f_unaccent('João') || '%');
与普通btree相比,GIN和GIST索引的维护成本更高:
对于左固定模式,有更简单的解决方案。有关模式匹配和性能的更多信息:
pg_trgm
还为“相似性”(%
)和“距离”(<->
)提供有用的运算符。
Trigram索引也支持~
et al等简单的正则表达式。和不区分大小写的模式匹配ILIKE
:
unaccent(name)
?
utf8_general_ci
这类问题的答案吗?
PostgreSQL不支持这样的排序规则(是否区分重音),因为除非事物是二进制相等的,否则没有任何比较可以返回相等。这是因为在内部它会为诸如哈希索引之类的事情引入很多复杂性。因此,最严格的排序规则只会影响排序,而不会影响平等。
对于FTS,您可以使用定义自己的字典unaccent
,
CREATE EXTENSION unaccent;
CREATE TEXT SEARCH CONFIGURATION mydict ( COPY = simple );
ALTER TEXT SEARCH CONFIGURATION mydict
ALTER MAPPING FOR hword, hword_part, word
WITH unaccent, simple;
然后可以使用功能索引编制索引,
-- Just some sample data...
CREATE TABLE myTable ( myCol )
AS VALUES ('fóó bar baz'),('qux quz');
-- No index required, but feel free to create one
CREATE INDEX ON myTable
USING GIST (to_tsvector('mydict', myCol));
您现在可以非常简单地查询它
SELECT *
FROM myTable
WHERE to_tsvector('mydict', myCol) @@ 'foo & bar'
mycol
-------------
fóó bar baz
(1 row)
也可以看看
该unaccent
模块也可以自身使用而无需集成FTS,以查看Erwin的答案