【问题标题】:Is it possible to perform multiple updates with a single UPDATE SQL statement?是否可以使用单个 UPDATE SQL 语句执行多个更新?
【发布时间】:2010-09-29 13:15:23
【问题描述】:

假设我有一个表 tbl,其中包含列 idtitle。 我需要更改标题列的所有值:

  1. 从“a-1”到“a1”,
  2. 从“a.1”到“a1”,
  3. 从“b-1”到“b1”,
  4. 从“b.1”到“b1”。

现在,我正在执行两个 UPDATE 语句:

UPDATE tbl SET title='a1' WHERE title IN ('a-1', 'a.1')
UPDATE tbl SET title='b1' WHERE title IN ('b-1', 'b.1')

这根本不是问题,如果表很小,并且单个语句在不到一秒的时间内完成,您只需要执行几条语句。

您可能是客串了它 - 我有一个巨大的表要处理(一个语句在大约 90 秒内完成),并且我有大量的更新要执行。

那么,是否可以合并更新,使其只扫描表一次?或者,在这种情况下有更好的处理方法。

编辑:请注意,我正在使用的真实数据以及我必须执行的数据更改并不是那么简单 - 字符串更长并且它们不遵循任何模式(它是用户数据,所以不能做任何假设 - 它可以是任何东西)。

【问题讨论】:

  • 因此,从您的 EDIT 注释推断,字符串本身可能不同,但您尝试的更新是否遵循模式?如果是这样,它是什么?如果没有任何东西有模式,那么就没有解决方案,您必须单独编码每个特殊更新
  • 我有一个 正确 值列表,并且我有一个明确指定的 错误 值列表(并且必须将哪个错误值更改为哪个正确的值)。所以是的 - 更新确实有一个模式。简而言之 - 每次更新都会更改一个值,但前提是旧值在指定的值列表中。

标签: sql sql-server sql-server-2005 tsql optimization


【解决方案1】:

您可以使用一个语句和多个 case 语句

update tbl
  set title = 
    case
      when title in ('a-1', 'a.1') then 'a1'
      when title in ('b-1', 'b.1') then 'b1'
      else title
    end

当然,这会导致对每条记录的写入,并且对于索引,这可能是一个问题,因此您可以只过滤掉您想要更改的行:

update tbl
  set title = 
    case
      when title in ('a-1', 'a.1') then 'a1'
      when title in ('b-1', 'b.1') then 'b1'
      else title
    end
where
  title in ('a.1', 'b.1', 'a-1', 'b-1')

这将减少对表的写入次数。

【讨论】:

    【解决方案2】:

    在更一般的情况下,每个新值可能有数百个映射,您将创建一个包含旧值和新值的单独表,然后在 UPDATE 语句中使用它。在 SQL 的一种方言中:

    CREATE TEMP TABLE mapper (old_val CHAR(5) NOT NULL, new_val CHAR(5) NOT NULL);
    ...multiple inserts into mapper...
    INSERT INTO mapper(old_val, new_val) VALUES('a.1', 'a1');
    INSERT INTO mapper(old_val, new_val) VALUES('a-1', 'a1');
    INSERT INTO mapper(old_val, new_val) VALUES('b.1', 'b1');
    INSERT INTO mapper(old_val, new_val) VALUES('b-1', 'b1');
    ...etcetera...
    
    UPDATE tbl
       SET title = (SELECT new_val FROM mapper WHERE old_val = tbl.title)
       WHERE title IN (SELECT old_val FROM mapper);
    

    两个选择语句都至关重要。第一个是相关子查询(不一定快,但如果映射表有数千行,则比大多数替代查询要快),它将新值从对应于旧值的映射表中拉出。第二个确保仅修改映射表中具有值的那些行;这很重要,否则,对于那些没有映射条目的行,标题将设置为 null(这些是在您开始之前正常的记录)。

    对于一些替代方案,CASE 操作是可以的。但是,如果您要执行成百上千或数百万个映射,那么您很可能会超出 DBMS 中 SQL 语句长度的限制。

    【讨论】:

    • 这非常非常有趣。我什至从来没有想过这一点。插入映射器仍然很快,更新只会扫描我的表一次,我不需要构造大量查询。
    • 但出于性能原因,最好使用连接而不是相关子查询。
    • @HLGEM:是的,如果您的 DBMS 支持该表示法。您愿意为您知道的一些 DBMS 提供一种有效的语法吗?如果是这样,请编辑我的答案-我相信您有足够的代表来做到这一点。或者通过电子邮件告诉我 - 查看我的个人资料页面。
    【解决方案3】:

    根据乔纳森的回答。

    UPDATE tbl
       SET title = new_val
    FROM mapper
    WHERE title IN (SELECT old_val FROM mapper)
         AND mapper.old_val = tbl.title;
    

    他的初始版本需要对映射表进行大量读取。

    【讨论】:

    • @MrDenny:我可以将您的材料复制到我的答案中吗?当然要给予信用?
    • 我使用了他的查询,它就像一个魅力!我真的很惊讶——它真的比我想象的要快。非常好。
    【解决方案4】:

    如果转换和您的示例一样简单,您可以通过一些字符串操作来进行更新:

    UPDATE tbl 
    SET title = left(title, 1) + right(title, 1) 
    WHERE title IN ('a-1', 'a.1', 'b-1', 'b.1')
    

    这样的东西对你有用吗?

    【讨论】:

    • 不,不幸的是,我处理的真实 I 数据并不像我的示例中那样简单。这对我不起作用。不过还是谢谢。
    • 听起来像 casperOne 使用 CASE WHEN 表达式是要走的路。
    【解决方案5】:

    或者

       Update Table set 
         title = Replace(Replace(title, '.', ''), '-', '')
       Where title Like '[ab][.-]1'
    

    【讨论】:

    • 正如我在马特回答的 cmets 中提到的 - 真实数据库中的数据并不那么简单。
    • 冒着听起来很明显的风险,那么,答案也不会那么简单。真正的问题是什么?
    • 也许,我应该让示例中的标题有所不同,这样它们就没有那么简单了。真正的问题是真正的标题是不遵循任何模式的字符串——它们实际上是用户生成的标题,所以我不能对它们做任何假设。我也编辑了我的问题。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-09-27
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多