121

在 Microsoft SQL Server 中,可以指定“不区分重音”排序规则(针对数据库、表或列),这意味着可以进行类似的查询

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

查找具有Joao名称的行。

我知道可以使用unaccent_string contrib 函数从 PostgreSQL 中的字符串中去除重音符号,但我想知道 PostgreSQL 是否支持这些“重音不敏感”排序规则,所以SELECT上面的方法可以工作。

4

3 回答 3

250

为此使用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');

指数

要为此类查询使用索引,请在表达式上创建索引。但是,Postgres 只接受IMMUTABLE索引函数。如果函数可以为相同的输入返回不同的结果,则索引可能会静默中断。

unaccent()只是STABLE没有IMMUTABLE

不幸的是,unaccent()只是STABLE,不是IMMUTABLE。根据pgsql-bugs 上的这个线程,这是由于三个原因:

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

网络上的一些教程指示只需将函数波动性更改为IMMUTABLE. 这种蛮力方法可以在某些条件下破坏。

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

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

另一种选择是这个模块,它具有由 Musicbrainz提供的IMMUTABLEunaccent()功能,在 Github 上提供。自己没有测试过。我想我想出了一个更好的主意

现在最好

这种方法比其他解决方案更有效,也更安全
创建一个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$;

从 Postgres 9.5 或更早版本的两个函数中删除PARALLEL SAFE

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'直接提供无重音字符串。

使用表达式 index,更快的函数不会转换为更快的查询。这在预先计算的值上运行,并且已经非常快了。但是不使用索引的索引维护和查询有好处。

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éonard Benedetti)

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

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

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

模式匹配

对于LIKEILIKE具有任意模式,将其与pg_trgmPostgreSQL 9.1 或更高版本中的模块结合使用。创建三元组 GIN(通常更可取)或 GIST 表达式索引。杜松子酒的例子:

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 高:

对于左锚模式有更简单的解决方案。有关模式匹配和性能的更多信息:

pg_trgm还为“相似性”(%)和“距离”(<->提供了有用的运算符。

Trigram 索引还支持简单的正则表达式~等。和不区分大小写的模式匹配ILIKE

于 2012-06-13T01:51:07.453 回答
10

不,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 的答案

于 2018-05-30T01:33:17.413 回答
2

我很确定 PostgreSQL 依赖于底层操作系统进行排序。它确实支持创建新排序规则自定义排序规则。不过,我不确定这对你来说有多少工作量。(可能很多。)

于 2012-06-12T21:54:48.687 回答