【问题标题】:What is the best way to run N independent column updates in PostgreSQL? What is the best way to do it in the SQL spec?在 PostgreSQL 中运行 N 个独立列更新的最佳方法是什么?在 SQL 规范中最好的方法是什么?
【发布时间】:2026-01-25 08:35:01
【问题描述】:

我正在寻找一种更有效的方法来在同一张表上运行许多列更新,如下所示:

UPDATE TABLE table
SET col = regexp_replace( col, 'foo', 'bar' )
WHERE regexp_match( col, 'foo' );

这样foobar 将是40 个不同的正则表达式替换的组合。我怀疑甚至 25% 的数据集都需要更新,但我想知道的是有可能在 SQL 中干净地实现以下目标。

  • 单次更新
  • 正则表达式的单个匹配,触发单个替换
  • 如果只有一个匹配,则运行所有可能的 regexp_replaces
  • 如果只有一个列需要更新,则不更新所有列
  • 如果没有列更改则更新行

我也很好奇,我在 MySQL 中知道(请耐心等待)

UPDATE foo SET bar = 'baz'

有一个隐含的WHERE bar != 'baz' 子句

但是,在 PostgreSQL 中,我知道这不存在:如果我知道在目标列未更新的情况下如何跳过单行更新,我想我至少可以回答我的一个问题。

类似

UPDATE TABLE table
SET col = *temp_var* = regexp_replace( col, 'foo', 'bar' )
WHERE col != *temp_var*

【问题讨论】:

    标签: sql postgresql sql-update


    【解决方案1】:

    在代码中执行。打开一个游标,然后:抓取一行,运行 40 个正则表达式,如果它改变了,就保存回来。重复,直到光标不再为您提供任何行。

    不管你是那样做还是想出神奇的 SQL 表达式,仍然是对整个表进行行扫描,但代码会简单得多。

    实验结果

    为了回应批评,我做了一个实验。我将文档文件中的 10,000 行插入到具有串行主键和 varchar 列的表中。然后我测试了两种方法来进行更新。方法一:

    in a transaction:
      opened up a cursor (select for update)
      while reading 100 rows from the cursor returns any rows:
        for each row:
          for each regular expression:
            do the gsub on the text column
          update the row
    

    本地连接的数据库需要 1.16 秒。

    然后是“大替换”,一个单一的大型正则表达式更新:

    更新 foo 集 t = REGEXP_REPLACE(REGEXP_REPLACE(REGEXP_REPLACE(REGEXP_REPLACE(REGEXP_REPLACE(REGEXP_REPLACE(REGEXP_REPLACE(REGEXP_REPLACE(REGEXP_REPLACE(REGEXP_REPLACE(REGEXP_REPLACE(REGEXP_REPLACE(REGEXP_REPLACE(REGEXP_REPLACE(REGEXP_REPLACE(REGEXP_REPLACE(REGEXP_REPLACE(REGEXP_REPLACE(REGEXP_REPLACE(REGEXP_REPLACE(REGEXP_REPLACE(REGEXP_REPLACE(REGEXP_REPLACE(REGEXP_REPLACE(REGEXP_REPLACE( regexp_replace(regexp_replace(regexp_replace(regexp_replace(regexp_replace(regexp_replace(regexp_replace(regexp_replace(regexp_replace(regexp_replace(regexp_replace(regexp_replace(regexp_replace(regexp_replace(regexp_replace(regexp_replace(t, E'\bcommit\b', E'COMMIT'), E'\b9acf10762b5f3d3b1b33ea07792a936a25e45010\b', E'9ACF10762B5F3D3B1B33EA07792A936A25E45010'), E'\bAuthor:\b', E'AUTHOR:'), E'\bCarl\b', E'CARL'), E'\bWorth\b', E'WORTH'), E'\b\b', E''), E'\b日期:\b', E'DATE:'), E'\bMon\b', E'MON'), E'\bOct\b', E'OCT'), E'\b26\b', E'26'), E'\b04:53:13\b', E'04:53:13'), E'\b2009\b', E'2009'), E'\b-0700\b', E'-0700'), E'\bUpdate\b', E'UPDATE'), E'\bversion\b', E'VERSION'), E'\bto\b', E'TO'), E'\b2.9.1\b', E'2.9.1'), E'\bcommit\b', E'COMMIT'), E'\b61c89e56f361fa860f18985137d6bf53f48c16ac\b', E'61C89E56F361FA860F18985137D6BF53F48C16AC'), E'\bAuthor:\b', E'AUTHOR:'), E'\bCarl\b', E'CARL'), E'\bWorth\b', E'WORTH'), E'\b\b', E''), E'\b日期:\b', E'DATE:'), E'\bMon\b', E'MON'), E'\bOct\b', E'OCT'), E'\b26\b', E'26'), E'\b04:51:58\b', E'04:51:58'), E'\b2009\b', E'2009'), E'\b-0700\b', E'-0700'), E'\bNEWS:\b', E'NEWS:'), E'\bAdd\b', E'ADD'), E'\bnotes\b', E'NOTES'), E'\bfor\b', E'FOR'), E'\bthe\b', E'THE'), E'\b2.9.1\b', E'2.9.1'), E'\brelease.\b', E'RELEASE.'), E'\bThanks\b', E'THANKS'), E'\bto\b', E'TO'), E'\beveryone\b', E'E'EVERYONE'), E'\bfor\b', E'FOR')

    超级正则表达式更新需要 0.94 秒。

    与 1.16 相比,在 0.94 秒时,mega-regex 更新确实更快,81% 的时间在代码中运行。它不是,但是要快得多。各位大神,看看那个更新声明。当 Postgres 抱怨你在某处丢了括号时,你是想写那个,还是想弄清楚哪里出了问题?

    代码

    使用的代码是:

      def stupid_regex_replace
        sql = Select.new
        sql.select('id')
        sql.select('t')
        sql.for_update
        sql.from(TABLE_NAME)
        Cursor.new('foo', sql, {}, @db) do |cursor|
          until (rows = cursor.fetch(100)).empty?
            for row in rows
              for regex, replacement in regexes
                row['t'] = row['t'].gsub(regex, replacement)
              end
            end
            sql = Update.new(TABLE_NAME, @db)
            sql.set('t', row['t'])
            sql.where(['id = %s', row['id']])
            sql.exec
          end
        end
      end
    

    我通过从文件中获取单词来动态生成正则表达式;对于每个单词“foo”,它的正则表达式是“\bfoo\b”,它的替换字符串是“FOO”(单词大写)。我使用文件中的文字来确保确实发生了替换。我让测试程序吐出正则表达式,这样你就可以看到它们。每对都是一个正则表达式和对应的替换字符串:

    [[/\bcommit\b/, "COMMIT"],
     [/\b9acf10762b5f3d3b1b33ea07792a936a25e45010\b/,
      "9ACF10762B5F3D3B1B33EA07792A936A25E45010"],
     [/\bAuthor:\b/, "AUTHOR:"],
     [/\bCarl\b/, "CARL"],
     [/\bWorth\b/, "WORTH"],
     [/\b<cworth@cworth.org>\b/, "<CWORTH@CWORTH.ORG>"],
     [/\bDate:\b/, "DATE:"],
     [/\bMon\b/, "MON"],
     [/\bOct\b/, "OCT"],
     [/\b26\b/, "26"],
     [/\b04:53:13\b/, "04:53:13"],
     [/\b2009\b/, "2009"],
     [/\b-0700\b/, "-0700"],
     [/\bUpdate\b/, "UPDATE"],
     [/\bversion\b/, "VERSION"],
     [/\bto\b/, "TO"],
     [/\b2.9.1\b/, "2.9.1"],
     [/\bcommit\b/, "COMMIT"],
     [/\b61c89e56f361fa860f18985137d6bf53f48c16ac\b/,
      "61C89E56F361FA860F18985137D6BF53F48C16AC"],
     [/\bAuthor:\b/, "AUTHOR:"],
     [/\bCarl\b/, "CARL"],
     [/\bWorth\b/, "WORTH"],
     [/\b<cworth@cworth.org>\b/, "<CWORTH@CWORTH.ORG>"],
     [/\bDate:\b/, "DATE:"],
     [/\bMon\b/, "MON"],
     [/\bOct\b/, "OCT"],
     [/\b26\b/, "26"],
     [/\b04:51:58\b/, "04:51:58"],
     [/\b2009\b/, "2009"],
     [/\b-0700\b/, "-0700"],
     [/\bNEWS:\b/, "NEWS:"],
     [/\bAdd\b/, "ADD"],
     [/\bnotes\b/, "NOTES"],
     [/\bfor\b/, "FOR"],
     [/\bthe\b/, "THE"],
     [/\b2.9.1\b/, "2.9.1"],
     [/\brelease.\b/, "RELEASE."],
     [/\bThanks\b/, "THANKS"],
     [/\bto\b/, "TO"],
     [/\beveryone\b/, "EVERYONE"],
     [/\bfor\b/, "FOR"]]
    

    如果这是手动生成的正则表达式列表,而不是自动生成的,我的问题仍然合适:您更愿意创建或维护哪个?

    【讨论】:

    • 这可能会导致可怕糟糕的表现。 PostgreSQL 不是 ISAM 数据库。
    • @Magnus,性能会很糟糕,但不会更糟:Postgres 不使用正则表达式的索引,除非它们是左锚定的,所以无论如何它都会是行扫描......最好的方法是单行扫描而不是 40 行,这样做的方法是对每一行进行一次操作,为每一行应用所有 40 个正则表达式。唯一的区别是你是让 Postgres 对每行扫描的行进行 40 次正则表达式替换,还是让代码来做。
    • 一个查询执行表扫描仍然比在 ISAM 样式循环中执行要快得多。在大多数情况下,执行 40 个正则表达式比 ISAM 循环的成本要便宜很多。
    • 我其实很喜欢这个更新,但我会争辩说,当在一个示例中您省略除正则表达式之外的所有内容时,它不是一个公平的代码清晰表示,而在另一个示例中您只是有两个关键字和一个正则表达式。
    • “程序应该写给人们阅读,并且只是偶然地让机器执行。”
    【解决方案2】:

    对于跳过更新,请查看 suppress_redundant_updates - 请参阅 http://www.postgresql.org/docs/8.4/static/functions-trigger.html

    这不一定是胜利 - 但很可能是你的情况。

    或者也许您可以将隐式检查添加为显式检查?

    【讨论】:

      最近更新 更多