如何在UPDATE语句中使用regexp_matches()?

时间:2018-06-22 20:06:40

标签: sql regex postgresql sql-update postgresql-10

我正在尝试清理具有非常杂乱的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()是所说的集合返回函数。关于如何实现此目标的任何想法?

3 个答案:

答案 0 :(得分:3)

注释

1。
您不需要将相关子查询建立在基表的单独实例上(就像到目前为止提出的两个答案一样)。那将无所事事地做更多的工作。

2。
在简单情况下, ARRAY构造函数array_agg()便宜。参见:

3。
我使用不带lookahead and lookbehind constraints的正则表达式,而使用括号:href="([^"]+)

请参见 查询1

之所以可行,是因为带括号的子表达式是由regexp_matches()(和其他几个Postgres regexp函数)捕获的。因此,我们可以用普通括号代替更复杂的约束。 The manual on regexp_match():

  

如果找到匹配项,并且 pattern 不包含括号   子表达式,则结果是一个单元素文本数组   包含与整个模式匹配的子字符串。如果匹配   找到,并且*pattern*包含带括号的子表达式,然后   结果是一个文本数组,其第 n 个元素是匹配的子字符串   模式的第 n 个带括号的子表达式

And for regexp_matches():

  

如果没有匹配项,则此函数不返回任何行;如果不存在,则不返回任何行   是一个匹配项,并且不给出g标志;如果存在N,则 N 行   匹配并给出g标志。返回的每一行都是一个文本数组   包含整个匹配的子字符串或匹配的子字符串   括号内的子表达式,如上所述   为regexp_match

4。
regexp_matches()返回一组数组( setof text[] )是有原因的:正则表达式不仅可以在单个字符串中匹配多次(因此, set ),它还可以为每个匹配项生成多个字符串,并带有多个捕获括号(因此, array )。 this 正则表达式不会发生,结果中的每个数组都包含一个元素。但是将来的读者不会陷入陷阱:

当将生成的一维数组馈送到产生二维数组的array_agg()(或ARRAY构造函数)时,这甚至是可行的,因为Postgres 9.5添加了array_agg()接受数组的变体输入。参见:

但是,quoting the manual:

  

输入必须全部具有相同的维数,并且不能为空或NULL

认为这永远不会失败,因为同一个正则表达式总是产生相同数量的数组元素。我们总是产生 one 元素。但这可能与其他正则表达式不同。如果是这样,则有多种选择:

  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 <>小提琴here (带有扩展测试用例)

答案 1 :(得分:1)

您可以使用相关子查询来处理有问题的集合返回函数(即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 in a column”一栏感到困惑,但这是一个单独的问题,可能对您来说不是问题。

答案 2 :(得分:0)

在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 Fiddle