PostgreSQL 是不是支持“不区分重音”排序规则?
Posted
技术标签:
【中文标题】PostgreSQL 是不是支持“不区分重音”排序规则?【英文标题】:Does PostgreSQL support "accent insensitive" collations?PostgreSQL 是否支持“不区分重音”排序规则? 【发布时间】:2012-06-15 19:54:51 【问题描述】:在 Microsoft SQL Server 中,可以指定“不区分重音”排序规则(针对数据库、表或列),这意味着可以进行类似的查询
SELECT * FROM users WHERE name LIKE 'João'
查找名称为Joao
的行。
我知道可以使用unaccent_string contrib 函数从 PostgreSQL 中的字符串中去除重音符号,但我想知道 PostgreSQL 是否支持这些“重音不敏感”排序规则,所以上面的 SELECT
可以工作。
【问题讨论】:
查看此答案以创建不带重音的 FTS 字典:***.com/a/50595181/124486 您想要区分大小写还是不区分大小写的搜索? 【参考方案1】:为此使用unaccent module - 这与您链接的内容完全不同。
unaccent 是一个文本搜索字典,可以去除重音符号(变音符号 符号)来自词位。
每个数据库安装一次:
CREATE EXTENSION unaccent;
如果您收到如下错误:
ERROR: could not open extension control file "/usr/share/postgresql/<version>/extension/unaccent.control": No such file or directory
按照相关答案中的说明在您的数据库服务器上安装 contrib 包:
Error when creating unaccent extension on PostgreSQL除其他外,它还提供了您可以在示例中使用的函数unaccent()
(似乎不需要LIKE
)。
SELECT *
FROM users
WHERE unaccent(name) = unaccent('João');
索引
要对此类查询使用索引,请创建index on the expression。 但是,Postgres 只接受 IMMUTABLE
函数作为索引。如果函数可以为相同的输入返回不同的结果,则索引可能会静默中断。
unaccent()
仅 STABLE
不是 IMMUTABLE
很遗憾,unaccent()
只是 STABLE
,而不是 IMMUTABLE
。根据this thread on pgsql-bugs,这是由于三个原因:
-
这取决于字典的行为。
这本词典没有硬连线连接。
因此它还取决于当前的
search_path
,它可以轻松更改。
网络上的Some tutorials 指示只需将函数波动性更改为IMMUTABLE
。这种暴力破解方法在某些情况下可能会崩溃。
其他人建议simple IMMUTABLE
wrapper function(就像我过去自己做的那样)。
关于是否使用明确声明使用的字典的variant with two parameters IMMUTABLE
一直存在争议。阅读here 或here。
另一种选择是这个模块带有IMMUTABLE unaccent()
function by Musicbrainz,在 Github 上提供。自己没有测试过。我想我想出了一个更好的主意:
目前最佳
这种方法比其他浮动解决方案更有效,也更安全。
创建一个IMMUTABLE
SQL 包装函数,执行带有硬连线模式限定函数和字典的双参数形式。
由于嵌套非不可变函数会禁用函数内联,因此它也基于声明为 IMMUTABLE
的 C 函数(假)的副本。它的唯一目的是在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$;
从 Postgres 9.5 或更早版本的两个函数中删除 PARALLEL SAFE
。
public
是您安装扩展的架构(public
是默认值)。
显式类型声明 (regdictionary
) 可防御恶意用户使用函数的重载变体进行的假设攻击。
之前,我提倡基于 unaccent 模块附带的 STABLE
函数 unaccent()
的包装函数。那禁用了function inlining。这个版本的执行速度比我之前在这里的简单包装函数快十倍。
这已经比在函数中添加SET search_path = public, pg_temp
的第一个版本快两倍——直到我发现字典也可以是模式限定的。 Still (Postgres 12) not too obvious from documentation.
如果您缺乏创建 C 函数所需的权限,您将回到次优实现:模块提供的 STABLE
unaccent()
函数的 IMMUTABLE
函数包装器:
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'
这样的无重音字符串。
使用 expression index 时,更快的功能不会转化为更快的查询。这在预先计算的值上运行,并且已经非常快了。但是索引维护和查询不使用索引的好处。
Postgres 10.3 / 9.6.8 等版本加强了客户端程序的安全性。您需要按照在任何索引中使用时演示的模式限定函数和字典名称。见:
'text search dictionary “unaccent” does not exist' entries in postgres log, supposedly during automatic analyze连字
在 Postgres 9.5 或更早版本中,必须手动扩展诸如 'Œ' 或 'ß' 之类的连字(如果需要),因为 unaccent()
总是替换为 单个 信:
SELECT unaccent('Œ Æ œ æ ß');
unaccent
----------
E A e a S
你会喜欢 Postgres 9.6 中的this update to unaccent:
扩展
contrib/unaccent
的标准unaccent.rules
文件以处理所有 Unicode 已知的变音符号,并正确扩展连字(Thomas 门罗,伦纳德·贝内代蒂)
我的大胆强调。现在我们得到:
SELECT unaccent('Œ Æ œ æ ß');
unaccent
----------
OE AE oe ae ss
模式匹配
对于具有任意模式的 LIKE
或 ILIKE
,将其与 PostgreSQL 9.1 或更高版本中的模块 pg_trgm
结合使用。创建三元组 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') || '%');
GIN 和 GIST 索引的维护成本比普通 btree 高:
Difference between GiST and GIN index对于左锚定模式有更简单的解决方案。有关模式匹配和性能的更多信息:
Pattern matching with LIKE, SIMILAR TO or regular expressions in PostgreSQLpg_trgm
还提供有用的operators for "similarity" (%
) and "distance" (<->
)。
Trigram 索引还支持带有~
等的简单正则表达式。和 不区分大小写 模式匹配 ILIKE
:
【讨论】:
在您的解决方案中,是否使用了索引,或者我是否需要在unaccent(name)
上创建索引?
@e3matheus:因为没有测试我之前提供的解决方案而感到内疚,我调查并更新了我的答案,用一个新的、更好的 (恕我直言) 解决方案来解决这个问题,而不是到目前为止。
你的答案和 Postgres 文档一样好:太棒了!
我想知道现在ICU排序规则是否可以不区分重音。
@a_horse_with_no_name:我还没有时间测试它,但这是一个预期的用例。【参考方案2】:
不,PostgreSQL 不支持这种意义上的排序规则
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)
另见
Creating a case-insensitive and accent/diacritics insensitive search on a field本身不重音。
unaccent
module 也可以在没有 FTS 集成的情况下单独使用,请查看 Erwin's answer
【讨论】:
请注意,从引入nondeterministic collations 的 Postgres 12 开始,此处的开头段落不再严格正确。但是,模式匹配运算符仍然不支持它们。【参考方案3】:我很确定 PostgreSQL 依赖于底层操作系统进行排序。它确实支持creating new collations和customizing collations。不过,我不确定这对你来说有多少工作量。 (可能很多。)
【讨论】:
新的排序规则支持目前基本上仅限于操作系统区域设置的包装器和别名。这是非常基本的。不支持过滤器函数、自定义比较器或任何您需要的真正自定义排序规则。以上是关于PostgreSQL 是不是支持“不区分重音”排序规则?的主要内容,如果未能解决你的问题,请参考以下文章