Как использовать regexp_matches() в операторе UPDATE?

Я пытаюсь очистить таблицу с очень грязным столбцом varchar с записями вида:

<u><font color="#0000FF"><a href="http://virginialidar.com/index-3.html#.VgLbFPm6e73" target="_blank">VA Lidar</a></font></u> OR <u><font color="#0000FF"><a href="https://inport.nmfs.noaa.gov/inport/item/50122" target="_blank">InPort Metadata</a></font></u>

Я хотел бы обновить столбец, сохранив только html-ссылки и разделив их запятой, если их несколько. В идеале я бы сделал что-то вроде этого:

UPDATE mytable
SET column = array_to_string(regexp_matches(column,'(?<=href=").+?(?=\")','g') , ',');

Но, к сожалению, это возвращает ошибку в Postgres 10:

ERROR: set-returning functions are not allowed in UPDATE

Я предполагаю, что regexp_matches() - это указанная функция, возвращающая набор. Любые идеи о том, как я могу достичь этого?


person Vlad    schedule 22.06.2018    source источник
comment
Что должно быть в column, если html-ссылки вообще нет? НУЛЕВОЙ? Пустой строки? Сохранить исходное значение? Было бы очень полезно предоставить определение таблицы в виде сценария CREATE TABLE, чтобы упростить тестирование и прояснить типы данных и ограничения.   -  person Erwin Brandstetter    schedule 23.06.2018


Ответы (3)


Примечания

1.
Вам не нужно основывать коррелированный подзапрос на отдельном экземпляре базовой таблицы (как предполагают два ответа). Это будет делать больше работы ни за что.

2.
В простых случаях конструктор ARRAY дешевле, чем array_agg(). Видеть:

3.
Я использую регулярное выражение без упреждающие и ретроспективные ограничения и круглые скобки вместо этого: href="([^"]+)

См. запрос 1.

Это работает, потому что подвыражения в скобках перехватываются regexp_matches() (и несколькими другими функциями регулярных выражений Postgres). Таким образом, мы можем заменить более сложные ограничения простыми скобками. Руководство по regexp_match():

Если совпадение найдено и pattern не содержит подвыражений в скобках, результатом является одноэлементный текстовый массив, содержащий подстроку, соответствующую всему шаблону. Если совпадение найдено и *pattern* содержит подвыражения в круглых скобках, то результатом является текстовый массив, чей n-й элемент является подстрокой, соответствующей n-му подвыражению в круглых скобках шаблон

И для regexp_matches():

Эта функция не возвращает ни одной строки, если совпадений нет, одну строку, если совпадение есть и флаг g не задан, или строк N, если совпадений N и задан флаг g. Каждая возвращаемая строка представляет собой текстовый массив, содержащий всю совпадающую подстроку или подстроки, соответствующие заключенным в скобки подвыражениям шаблона, как описано выше для regexp_match.

4.
regexp_matches() возвращает набор массивов (setof text[]) по одной причине: регулярное выражение может не только совпадать несколько раз в одной строке (отсюда набор) , он может также создавать несколько строк для каждого совпадения с несколькими захватывающими круглыми скобками (отсюда и массив). Не происходит с этим регулярным выражением, каждый массив в результате содержит один элемент. Но будущие читатели не должны попасть в ловушку:

При передаче результирующих одномерных массивов в array_agg() (или конструктор ARRAY), который создает двумерный массив, что возможно только с тех пор, как Postgres 9.5 добавил вариант array_agg(), принимающий ввод массива. Видеть:

Однако цитирует руководство:

все входы должны иметь одинаковую размерность и не могут быть пустыми или NULL

Я думаю, что это никогда не даст сбой, поскольку одно и то же регулярное выражение всегда создает одинаковое количество элементов массива. Наш всегда производит один элемент. Но это может быть иначе с другим регулярным выражением. Если да, то есть разные варианты:

  1. Возьмите только первый элемент с (regexp_matches(...))[1]. См. запрос 2.

  2. Разложите массивы и используйте string_agg() для базовых элементов. См. запрос 3.

Каждый подход работает и здесь.

Запрос 1

UPDATE tbl t
SET    col = (
   SELECT array_to_string(ARRAY(SELECT regexp_matches(col, 'href="([^"]+)', 'g')), ',')
   );

Столбцам без совпадений присваивается значение '' (пустая строка).

Запрос 2

UPDATE tbl
SET    col = (
   SELECT string_agg(t.arr[1], ',')
   FROM   regexp_matches(col, 'href="([^"]+)', 'g') t(arr)
   );

Столбцам без совпадений присваивается значение NULL.

Запрос 3

UPDATE tbl
SET    col = (
   SELECT string_agg(elem, ',')
   FROM   regexp_matches(col, 'href="([^"]+)', 'g') t(arr)
        , unnest(t.arr) elem
   );

Столбцам без совпадений присваивается значение NULL.

db‹>fiddle здесь (с расширенным тестовым примером)

person Erwin Brandstetter    schedule 23.06.2018
comment
Спасибо, Эрвин, мне удалось использовать ваши 3 запроса, и я действительно предпочитаю избегать подзапросов. Могу я спросить, почему вы используете именно это регулярное выражение? Я пытаюсь сопоставить только фактические ссылки внутри кавычек без части «href=». - person Vlad; 25.06.2018
comment
Мое регулярное выражение проще и короче (может быть, и быстрее - не проверял), делая то же самое (эффективно, для этого варианта использования). - person Erwin Brandstetter; 26.06.2018
comment
Я очень мало знаю о регулярном выражении, и ваше регулярное выражение действительно дает тот же результат при применении его к запросу. Любая идея, почему результат при тестировании этого виджета отличается? regexr.com/3rjkj - person Vlad; 26.06.2018
comment
@Vlad: Это потому, что функция возвращает подвыражения в скобках. См. добавленное объяснение выше. - person Erwin Brandstetter; 26.06.2018

Вы можете использовать коррелированный подзапрос, чтобы иметь дело с оскорбительной функцией, возвращающей набор (это regexp_matches). Что-то вроде этого:

update mytable
set column = (
    select array_to_string(array_agg(x), ',')
    from (
        select regexp_matches(t2.c, '(?<=href=").+?(?=\")', 'g')
        from t t2
        where t2.id = t.id
    ) dt(x)
)

Вы все еще застряли с мерзостью «CSV в столбце», но это отдельная проблема и, по-видимому, не проблема для вас.

person mu is too short    schedule 22.06.2018

Построение на основе подхода mu слишком короткое с немного другим регулярным выражением и функцией COALESCE для сохранения значений, не содержащих ссылок href:

UPDATE a 
SET    bad_data = COALESCE(
  (SELECT Array_to_string(Array_agg(x), ',') 
   FROM   (SELECT Regexp_matches(a.bad_data, 
                                 '(?<=href=")[^"]+', 'g' 
                                ) AS x 
           FROM   a a2 
           WHERE  a2.id = a.id) AS sub), bad_data
);

скрипт SQL

person wp78de    schedule 22.06.2018