Удалить все разделители пробелов Unicode в PostgreSQL? - PullRequest
3 голосов
/ 07 августа 2020

Я хотел бы trim() столбец и заменить любые несколько пробелов и Unicode space separators на один пробел. Идея состоит в том, чтобы очистить имена пользователей, не допуская, чтобы у 2 пользователей были вводящие в заблуждение имена foo bar (ПРОБЕЛ u+20) против foo bar (БЕЗ РАЗРЫВА u+A0).

До сих пор я использовал SELECT regexp_replace(TRIM('some string'), '[\s\v]+', ' ', 'g'); он удаляет пробелы, табуляцию и возврат каретки, но не поддерживает разделители пробелов Unicode .

Я бы добавил в регулярное выражение \h, но PostgreSQL не поддерживать его (ни \p{Zs}):

SELECT regexp_replace(TRIM('some string'), '[\s\v\h]+', ' ', 'g');
Error in query (7): ERROR: invalid regular expression: invalid escape \ sequence

Мы запускаем PostgreSQL 12 (12.2-2.pgdg100+1) в контейнере Debian 10 docker, используя кодировку UTF-8 и поддерживаем смайлики в именах пользователей.

Есть ли способ добиться чего-то подобного?

Ответы [ 2 ]

1 голос
/ 08 августа 2020

На основе класса символов Posix «пробел» ( сокращенное обозначение класса \s в Postgres регулярных выражениях), UNICODE «Пробелы», немного похожий на пробел «Формат символы "и некоторые дополнительные непечатаемые символы (наконец, добавлены еще два из сообщения Виктора), я сжал этот пользовательский класс символов:

'[\s\u00a0\u180e\u2007\u200b-\u200f\u202f\u2060\ufeff]'

Так что используйте:

SELECT trim(regexp_replace('some string', '[\s\u00a0\u180e\u2007\u200b-\u200f\u202f\u2060\ufeff]+', ' ', 'g'));

Примечание: trim() идет после regexp_replace(), поэтому он охватывает преобразованные пробелы.

Важно включить базовый c класс пространства \s (сокращенно от [[:space:]], чтобы охватить все текущий (и будущий) базовый c пробел.

Мы могли бы включить больше символов. Или начнем с удаления всех символов, закодированных 4 байтами. Потому что UNICODE темный и полный ужасов.

Рассмотрим эту демонстрацию:

SELECT d AS decimal, to_hex(d) AS hex, chr(d) AS glyph
     , '\u' || lpad(to_hex(d), 4, '0') AS unicode
     , chr(d) ~ '\s' AS in_posix_space_class
     , chr(d) ~ '[\s\u00a0\u180e\u2007\u200b-\u200f\u202f\u2060\ufeff]' AS in_custom_class
FROM  (
   -- TAB, SPACE, NO-BREAK SPACE, OGHAM SPACE MARK, MONGOLIAN VOWEL, NARROW NO-BREAK SPACE
   -- MEDIUM MATHEMATICAL SPACE, WORD JOINER, IDEOGRAPHIC SPACE, ZERO WIDTH NON-BREAKING SPACE
   SELECT unnest('{9,32,160,5760,6158,8239,8287,8288,12288,65279}'::int[])
   UNION ALL
   SELECT generate_series (8192, 8202) AS dec  -- UNICODE "Spaces"
   UNION ALL
   SELECT generate_series (8203, 8207) AS dec  -- First 5 space-like UNICODE "Format characters"
   ) t(d)
ORDER  BY d;
 decimal | hex  |  glyph   | unicode | in_posix_space_class | in_custom_class 
---------+------+----------+---------+----------------------+-----------------
       9 | 9    |          | \u0009  | t                    | t
      32 | 20   |          | \u0020  | t                    | t
     160 | a0   |          | \u00a0  | f                    | t
    5760 | 1680 |          | \u1680  | t                    | t
    6158 | 180e | ᠎        | \u180e  | f                    | t
    8192 | 2000 |          | \u2000  | t                    | t
    8193 | 2001 |          | \u2001  | t                    | t
    8194 | 2002 |          | \u2002  | t                    | t
    8195 | 2003 |          | \u2003  | t                    | t
    8196 | 2004 |          | \u2004  | t                    | t
    8197 | 2005 |          | \u2005  | t                    | t
    8198 | 2006 |          | \u2006  | t                    | t
    8199 | 2007 |          | \u2007  | f                    | t
    8200 | 2008 |          | \u2008  | t                    | t
    8201 | 2009 |          | \u2009  | t                    | t
    8202 | 200a |          | \u200a  | t                    | t
    8203 | 200b | ​        | \u200b  | f                    | t
    8204 | 200c | ‌        | \u200c  | f                    | t
    8205 | 200d | ‍        | \u200d  | f                    | t
    8206 | 200e | ‎        | \u200e  | f                    | t
    8207 | 200f | ‏        | \u200f  | f                    | t
    8239 | 202f |          | \u202f  | f                    | t
    8287 | 205f |          | \u205f  | t                    | t
    8288 | 2060 | ⁠        | \u2060  | f                    | t
   12288 | 3000 |         | \u3000  | t                    | t
   65279 | feff |         | \ufeff  | f                    | t
(26 rows)

Инструмент для создания класса символов:

SELECT '[\s' || string_agg('\u' || lpad(to_hex(d), 4, '0'), '' ORDER BY d) || ']'
FROM  (
   SELECT unnest('{9,32,160,5760,6158,8239,8287,8288,12288,65279}'::int[])
   UNION ALL
   SELECT generate_series (8192, 8202)
   UNION ALL
   SELECT generate_series (8203, 8207)
   ) t(d)
WHERE  chr(d) !~ '\s'; -- not covered by \s
[\s\u00a0\u180e\u2007\u200b\u200c\u200d\u200e\u200f\u202f\u2060\ufeff]

db <> fiddle здесь

Связано с дополнительными пояснениями:

1 голос
/ 07 августа 2020

Вы можете создать выражение в квадратных скобках, включающее символы пробелов из \p{Zs} Категория Unicode + табуляция:

REGEXP_REPLACE(col, '[\u0009\u0020\u00A0\u1680\u2000-\u200A\u202F\u205F\u3000]+', ' ', 'g')

Он заменит все вхождения одного или нескольких горизонтальных пробелов ( соответствует \h в других поддерживающих его разновидностях регулярных выражений) с обычным пробелом.

...