如何在 postgres 的文本列上添加唯一约束(忽略特殊字符)?

How to add an unique constraint (ignoring special characters) on a text column in postgres?

如何在 Postgres 中的文本列上添加唯一约束(忽略特殊字符)?

CREATE TABLE my_table(
    SomeTextColumn citext
CONSTRAINT person_u_1 UNIQUE (SomeTextColumn)
);

在上面的 table 中,我试图添加一个唯一性约束,该约束将通过忽略传入数据中的特殊字符来寻找唯一性

For example:
1. HelloWorld --> Gets inserted successfully
2. Hello World --> Should fail with duplicate constraint
2. Hello%$^&*W^%orld --> Should fail with duplicate constraint

您可以创建一个 unique 索引来执行检查:

create unique index t_txt_unique on t(regexp_replace(txt, '\W', '', 'g'));

正则表达式从字符串中删除所有非单词字符,仅保留字母数字字符和下划线 _。您可以根据需要将字符class调整为

Demo on DB Fiddle:

create table t (id int, txt citext);
create unique index t_txt_unique on t(regexp_replace(txt, '\W', '', 'g'));

insert into t values(1, 'HelloWorld');
-- ok

insert into t values(1, 'Hello World');
-- ERROR:  duplicate key value violates unique constraint "t_txt_unique"
-- DETAIL:  Key (regexp_replace(txt, '\W'::text, ''::text, 'g'::text))=(HelloWorld) already exists.

insert into t values(1, 'Hello%$^&*W^%orld');
-- ERROR:  duplicate key value violates unique constraint "t_txt_unique"
-- DETAIL:  Key (regexp_replace(txt, '\W'::text, ''::text, 'g'::text))=(HelloWorld) already exists.

insert into t values(1, 'Hello Mars');
-- ok

这个问题比较老,但我认为一些额外的注释可能会有用...

  • TEXT 的唯一性总是有问题,因为文本区分大小写(不仅在 PostgreSQL 中)。
    您可以获得重复项,因为“HelloWorld”与“HELLOWORLD”不同。因此,如果您在数据库的文本字段上创建 UNIQUE INDEX,您可能希望添加 UPPER 函数:
    CREATE UNIQUE INDEX t_txt_unique ON t(REGEXP_REPLACE(UPPER(txt), '\W', '', 'g'));
  • 您可能希望使用以下 REGEXP_REPLACE 选项仅保留字符 0-9 和 A-Z(删除变音符号):
    CREATE UNIQUE INDEX t_txt_unique ON t(REGEXP_REPLACE(UPPER(txt), '[^0-9A-Z]', '', 'g'));
  • 如果您想将变音符号保留为通用字符(Ü --> U、Ä --> A 等),您可以加入 UNACCENT 函数(https://www.postgresql.org/docs/current/unaccent.html - 这是您需要的扩展添加到 PostgreSQL 但我也可能会帮助您搜索一些东西 - 请记住,只有超级用户或其他管理员用户可能 able/allowed 添加扩展):
    CREATE EXTENSION IF NOT EXISTS unaccent WITH SCHEMA public;
    CREATE UNIQUE INDEX t_txt_unique ON t(REGEXP_REPLACE(UPPER(UNACCENT(txt)), '[^0-9A-Z]', '', 'g'));

SQL-代码

    
--
-- Check REGEXP_REPLACE to remove unwanted characters.
-- Test-String: '^°!"§$%&/()=?`+#äöüÜÖÄ',.-_:;@ABµC123abc123'
--
SELECT '^°!"§$%&/()=?`+#äöüÜÖÄ'',.-_:;@ABµC123abc123' AS original_text
      ,REGEXP_REPLACE( UPPER( '^°!"§$%&/()=?`+#äöüÜÖÄ'',.-_:;@ABµC123abc123' ), '\W', '', 'g') AS replace_word
      ,REGEXP_REPLACE( UPPER( '^°!"§$%&/()=?`+#äöüÜÖÄ'',.-_:;@ABµC123abc123' ), '[^0-9A-Z]', '', 'g') AS replace_upper_keep_num_a_to_z
      ,REGEXP_REPLACE( UPPER(UNACCENT( '^°!"§$%&/()=?`+#äöüÜÖÄ'',.-_:;@ABµC123abc123' )), '[^0-9A-Z]', '', 'g') AS replace_keep_num_a_to_z_umlaut
;

上面SQL代码的结果为HTMLtable 最终结果中的降价 table 太复杂了(预览有效)。

<table border="1"><tr BGCOLOR="#CCCCFF"><th>original_text</th><th>replace_word</th><th>replace_upper_keep_num_a_to_z</th><th>replace_keep_num_a_to_z_umlaut</th></tr>
<tr><td>^°!&quot;§$%&amp;/()=?`+#äöüÜÖÄ',.-_:;@&lt;&gt;ABµC123abc123</td><td>ÄÖÜÜÖÄ_AB?C123ABC123</td><td>ABC123ABC123</td><td>AOUUOAABC123ABC123</td></tr>
</table>

您可能已经注意到,第二列中的字符“µ”已使用“\W”替换转换为大写 'M',这有点奇怪。