【问题标题】:Multiple string replacements in a text field文本字段中的多个字符串替换
【发布时间】:2018-04-04 01:11:15
【问题描述】:

我有一个包含 HTML 内容的表格。此内容可能包含一个或多个 URL。我还有一个映射表,其中包含 URL 及其相关的重写。 我需要能够在每个 HTML 内容中用它们存在的重写替换所有 URL。

用例(Postgres 9.5):

TABLE some_content (content_id int4, content text)
row1: 1, 'A BA BLAH PIKA',
row2: 2, 'B AB',
row3: 3, 'C PIKA NOTA CA'

TABLE rewrite (rule_id int4, old_string text, new_string text)
row1: 1, 'PIKA', 'CHU',
row2: 2, 'BLAH', 'POM'

查询应输出以下集合:

row1: 1, 'A BA POM CHU'
row2: 2, 'B AB'
row3: 3, 'C CHU NOTA CA'

rewrite 表中添加新行,如:

row3: 3, 'NOTA', 'ISB'

然后将结果集转换为 (int4, text):

row1: 1, 'A BA POM CHU'
row2: 2, 'B AB'
row3: 3, 'C CHU ISB CA'

有什么提示吗?

【问题讨论】:

  • Postgres 版本,表定义?
  • @ErwinBrandstetter 添加,谢谢
  • rewrite中应该有一个ID列。原则上,也因为应用替换的顺序很重要。另外,请澄清您是只想替换整个单词(定义分隔符准确)还是任何匹配项。
  • 是的,也添加了。由于要替换的文本是 URL,因此整个匹配就足够了。谢谢。

标签: sql postgresql replace


【解决方案1】:

每次替换都取决于最后一次的结果。你需要某种循环。并且您需要在替换中具有确定性顺序。假设rule_id 按升序排列。并假设您要替换 any 匹配项,而不仅仅是整个单词(易于适应)。

你可以循环一个 plpgsql 函数。可能更快。见:

或者,对于纯 SQL,试试这个递归 CTE:

WITH RECURSIVE cte AS (
   SELECT s.content_id, r.rule_id
        , replace(s.content, r.old_string, r.new_string) AS content
   FROM   some_content s
   CROSS  JOIN (
      SELECT rule_id, old_string, new_string
      FROM   rewrite
      ORDER  BY rule_id  -- order of rows is relevant!
      LIMIT  1
      ) r

   UNION ALL
   SELECT c.content_id, r.rule_id
        , replace(c.content, r.old_string, r.new_string) AS content
   FROM   cte c
        , LATERAL (
      SELECT rule_id, old_string, new_string
      FROM   rewrite
      WHERE  rule_id > c.rule_id
      ORDER  BY rule_id  -- order of rows is relevant!
      LIMIT  1
      ) r
   )
SELECT DISTINCT ON (content_id) content
FROM   cte
ORDER  BY content_id, rule_id DESC;

LATERAL 连接以解决 "invalid reference to FROM-clause entry for table "c" 的问题,您可以通过引用 CTE 的直接子查询获得。相关:

或者,使用row_number() 生成一个没有空格的序列号like you commented

WITH RECURSIVE r AS (
   SELECT old_string, new_string
        , row_number() OVER (ORDER BY rule_id) AS rn  -- your ORDER BY expression?
   FROM   rewrite
   )
 , cte AS (
   SELECT s.content_id, r.rn
        , replace(s.content, r.old_string, r.new_string) AS content
   FROM   some_content s
   JOIN   r ON r.rn = 1

   UNION ALL
   SELECT s.content_id, r.rn
        , replace(s.content, r.old_string, r.new_string) AS content
   FROM   cte s
   JOIN   r ON r.rn = s.rn + 1
   )
SELECT DISTINCT ON (content_id) content
FROM   cte
ORDER  BY content_id, rn DESC;

dbfiddle here

经常被忽略的是,在WITH RECURSIVE之后仍然可以添加普通的CTE:

关于DISTINCT ON

【讨论】:

  • 别担心,我要求提示。我也选择了递归 CTE,但发现在 CTE 中有多个表达式(一个用于在重写规则中添加 row_number() 以添加询问的标识符)。您建议在 PLPgSQL 函数中使用循环也是一个很好的提示。谢谢。
  • 现在可以了。 LATERAL 加入以解决 "invalid reference to FROM-clause entry for table "c"
  • 使用WHERE rule_id > c.rule_id … ORDER BY … LIMIT 1还是直接使用INNER JOIN on an incrementing id哪个更快?
  • @greg:后者——如果你能保证没有差距的话。
  • 没错,它是通过row_number()窗口函数获得的,所以它应该没有间隙。感谢您 - 一如既往 - 友好而快速的回复。
猜你喜欢
  • 2021-05-19
  • 1970-01-01
  • 1970-01-01
  • 2017-08-01
  • 2015-03-15
  • 1970-01-01
  • 2021-12-12
  • 2013-02-25
  • 2017-03-20
相关资源
最近更新 更多