PostgreSQL是否支持“不区分重音”的排序规则?


98

在Microsoft SQL Server中,可以指定“不区分重音”的排序规则(对于数据库,表或列),这意味着可以对诸如

SELECT * FROM users WHERE name LIKE 'João'

查找具有Joao名称的行。

我知道可以使用unaccent_string contrib函数从PostgreSQL的字符串中去除重音符号,但是我想知道PostgreSQL是否支持这些“不区分重音符号”的排序规则,因此SELECT上述方法可行。


请参见以下答案以创建带有重音符号的FTS词典:stackoverflow.com/a/50595181/124486
Evan Carroll '18年

您要区分大小写还是不区分大小写的搜索?
埃文·卡洛尔

Answers:


204

为此,请使用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上的该线程,这是由于三个原因:

  1. 这取决于字典的行为。
  2. 此字典没有硬线连接。
  3. 因此,它还取决于电流search_path,电流很容易改变。

网络上的一些教程指示只是将函数的波动性更改为IMMUTABLE。在某些情况下,这种暴力破解方法可能会崩溃。

其他人建议使用一个简单的IMMUTABLE包装函数(就像我过去做的一样)。

是否使用两个参数 IMMUTABLE显式声明所用字典的变体一直存在争议。在这里这里阅读。

另一个选择是该模块, Github提供,具有Musicbrainz提供的IMMUTABLE unaccent()功能。我自己还没有测试过。我想我提出了一个更好的主意

现在最好

随着其他解决方案的发展,这种方法更有效,也更安全
创建一个IMMUTABLESQL包装函数,该函数使用硬连线的模式限定函数和字典执行两参数形式。

由于嵌套不可改变的函数会禁用函数内联,因此也应基于声明的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

您会喜欢此更新在Postgres 9.6中不突出显示

扩展contrib/unaccent标准unaccent.rules文件以处理Unicode已知的所有变音符号,并正确扩展连字(Thomas Munro,LéonardBenedetti)

大胆强调我的。现在我们得到:

SELECT unaccent('Œ Æ œ æ ß');

unaccent
----------
OE AE oe ae ss

模式匹配

对于LIKEILIKE带有任意模式,请将其与pg_trgmPostgreSQL 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)
Daniel Serodio 2012年

@ErwinBrandstetter在psql 9.1.4中,我得到“索引表达式中的函数必须标记为IMMUTABLE”,因为unaccent函数是STABLE而不是INMUTABLE。您有什么推荐的吗?
e3matheus

1
@ e3matheus:由于没有测试我提供的以前的解决方案而感到内,因此我针对问题的调查和更新(IMHO)解决了我的问题,而不是到目前为止所发现的问题。
Erwin Brandstetter

排序规则不是utf8_general_ci这类问题的答案吗?
2014年

5
您的答案与Postgres文档一样好:惊人!
电版

6

不,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)

也可以看看

本身不讲重音。

unaccent模块也可以自身使用而无需集成FTS,以查看Erwin的答案


2

我很确定PostgreSQL依赖底层操作系统进行排序。它确实支持创建新的归类自定义归类。不过,我不确定会为您带来多少工作。(可能很多。)


1
当前,新的归类支持基本上仅限于操作系统语言环境的包装器和别名。这是非常基本的。不支持过滤器功能,自定义比较器或真正自定义归类所需的任何功能。
Craig Ringer 2015年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.