【问题标题】:Update if different/changed如果不同/更改则更新
【发布时间】:2011-10-04 09:15:45
【问题描述】:

是否可以在sql中执行更新语句,但只有更新不同时才更新?

例如

如果在数据库中,col1 = "hello"

update table1 set col1 = 'hello'

不应执行任何类型的更新

如果

update table1 set col1 = "bye"

应该执行更新。

【问题讨论】:

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


    【解决方案1】:

    这可以通过更新前触发器实现。 在此触发器中,您可以将旧值与新值进行比较,如果它们没有差异,则取消更新。但这会导致调用者网站出现错误。
    我不知道您为什么要这样做,但这里有几种可能性:

    1. 性能:这里没有性能提升,因为更新不仅需要找到正确的行,还需要比较数据。
    2. 触发器:如果您希望仅在发生真正更改时触发触发器,您需要像这样实现触发器,在执行任何操作之前将所有旧值与新值进行比较。

    【讨论】:

    • 好答案。我在 2 号营地 - 我想阻止更新触发。
    • 我不同意您的说法,即执行此检查没有性能优势。这会因场景而异,但请参阅我的回答。
    • 当更新中没有值更改但您想避免更改 ModifiedDate 值时,它会很有帮助。但我认为使用存储过程可以轻松完成,因为仅通过存储过程完成所有 DML 操作并不是一个坏主意。
    • 在 IOPS 有限的 Amazon RDS 上,可以带来极其强大的性能优势。检查行是否被修改很可能从内存缓存中完成,因此不使用 IOPS。另一方面,不必要地写入行会耗尽 IOPS。一旦 IOPS 耗尽,您的查询将需要几分钟才能写入大表。我已经看到这导致大型 AWS RDS 实例上的 0.1 毫秒和 640 秒之间的差异。
    • 虽然时间很短,但是执行单个请求而不是两个单独的请求会提高网络性能
    【解决方案2】:

    如果您只想将字段更改为'hello',如果它是'bye',请使用:

    UPDATE table1
    SET col1 = 'hello'
    WHERE col1 = 'bye'
    

    如果您只想在与'hello' 不同的情况下更新,请使用:

    UPDATE table1
    SET col1 = 'hello'
    WHERE col1 <> 'hello'
    

    这种奇怪的方法有什么原因吗?正如丹尼尔所评论的那样,没有特别的收获 - 除非您有数千行 col1='hello'。是这样吗?

    【讨论】:

      【解决方案3】:

      如果新值与数据库中的值相同,则不执行任何更新

      WHERE col1 != @newValue
      

      (显然也应该有一些Id字段来标识一行)

      WHERE Id = @Id AND col1 != @newValue
      

      PS:本来你只想在值为'bye'时才进行更新,所以只需添加AND col1 = 'bye',但我觉得这是多余的,我只是假设

      PS 2:(来自评论)另请注意,如果col1NULL,这不会更新值,所以如果NULL 是可能的,则设为WHERE Id = @Id AND (col1 != @newValue OR col1 IS NULL)

      【讨论】:

      • 这似乎是最简单的解决方案。
      • 只是好奇。哪个版本的 SQL-Server 有 == 运算符?
      • 无论你的Sql语言如何定义equals运算符,想法很重要
      • @t-clausen.dk,请解释一下您的 -1(我已将 == 更正为 =,但这并不重要,显然问题的作者知道如何写等于;)并且可以忽略这种错别字;))
      • 可能值得注意的是,如果 col1 为 NULL,这不会更新值 - 如果 col1 可能为 null,则设置为 WHERE Id = @Id AND (col1 != @newValue OR col1 IS NULL)
      【解决方案4】:
      CREATE OR REPLACE PROCEDURE stackoverflow([your_value] IN TYPE) AS
      BEGIN
         UPDATE   [your_table] t
           SET t.[your_collumn] = [your_value]
         WHERE t.[your_collumn] != [your_value];
        COMMIT;
      
      
      EXCEPTION
       [YOUR_EXCEPTION];
      
      END stackoverflow;
      

      【讨论】:

        【解决方案5】:

        在查询编译和执行期间,SQL Server 不会花时间确定 UPDATE 语句是否会实际更改任何值。它只是按预期执行写入,即使是不必要的。

        在这样的场景中

        update table1 set col1 = 'hello'
        

        您可能认为 SQL 不会做任何事情,但它会 - 它会执行所有必要的写入操作,就像您实际更改了值一样。物理表(或聚集索引)以及在该列上定义的任何非聚集索引都会发生这种情况。这会导致写入物理表/索引,重新计算索引和事务日志写入。处理大型数据集时,仅更新将收到更改的行具有巨大的性能优势。

        如果我们想在不必要时避免这些写入的开销,我们必须设计一种方法来检查是否需要更新。检查是否需要更新的一种方法是添加类似“where col 'hello'。

        update table1 set col1 = 'hello' where col1 <> 'hello'
        

        但这在某些情况下效果不佳,例如,如果您要更新包含许多行的表中的多个列,并且这些行的一小部分实际上会更改其值。这是因为需要对所有这些列进行过滤,并且非相等谓词通常无法使用索引查找,以及上述表和索引写入以及事务日志条目的开销。

        但是使用 EXISTS 子句和 EXCEPT 子句的组合有一个更好的选择。想法是将目标行中的值与匹配源行中的值进行比较,以确定是否确实需要更新。查看下面修改后的查询并检查以 EXISTS 开头的附加查询过滤器。请注意在 EXISTS 子句中 SELECT 语句如何没有 FROM 子句。这部分特别重要,因为这只会在查询计划中增加额外的常量扫描和过滤操作(两者的成本都是微不足道的)。因此,您最终得到的是一种非常轻量级的方法,用于确定是否首先需要 UPDATE,从而避免不必要的写入开销。

        update table1 set col1 = 'hello'
        /* AVOID NET ZERO CHANGES */
        where exists 
            (
            /* DESTINATION */
            select table1.col1
            except
            /* SOURCE */
            select col1 = 'hello'
            )
        

        当您使用文字值更新表中所有行的一个值时,这看起来过于复杂,而不是检查原始问题中简单场景的简单 WHERE 子句中的更新。但是,如果您要更新表中的多个列,并且更新的源是另一个查询并且您希望最小化写入和事务日志条目,则此技术非常有效。它的性能也比使用 测试每个字段更好。

        一个更完整的例子可能是

        update table1
           set col1 = 'hello',
               col2 = 'hello',
               col3 = 'hello'
        /* Only update rows from CustomerId 100, 101, 102 & 103 */
        where table1.CustomerId IN (100, 101, 102, 103)
        /* AVOID NET ZERO CHANGES */
          and exists 
            (
            /* DESTINATION */
            select table1.col1
                   table1.col2
                   table1.col3
            except
            /* SOURCE */
            select z.col1,
                   z.col2,
                   z.col3
              from #anytemptableorsubquery z
             where z.CustomerId = table1.CustomerId
            )
        

        【讨论】:

        • 感谢您的解释 :) 我来到这里是因为我遇到了您所描述的不必要的写入问题。 Amazon RDS 上的 PostgreSQL 似乎也会盲目地写入行,即使它们没有改变。因此,在一张大桌子上,发出 UPDATE users SET status=1; 可能会耗尽您的 IOPS(从而导致之后的高延迟),而 UPDATE users SET status=1 WHERE status&lt;&gt;1; 可以从内存缓存中得到满足,因此不会浪费任何宝贵的 IOPS。
        • 最后一个示例也适用于存储过程,因为临时表可以替换为参数,例如select @param1, @param2, etc.
        • 这适用于更复杂的情况,但对于更简单的用例,更简单的答案是来自 sll (stackoverflow.com/a/6677560/4801767) 的答案,根据该答案下方 rbennett485 的评论进行了小幅修正。
        • 小修正 - 在解决方案的第一个/更简单的版本中,有一个错误 - 执行 select table1.col1 except select col1 = 'hello' 可能会导致错误。因为select col1 = 'hello' 的结果是布尔值,所以不能从select col from table 的结果中排除布尔值,除非该结果也是布尔值。所以第二部分应该类似于... except select col1 from table1 where col1 = 'hello'。然后,我们将col1 类型的结果从另一个col1 类型的结果中排除。
        【解决方案6】:

        我认为这应该对你有用...

        create trigger [trigger_name] on [table_name]
        for insert 
        AS declare  @new_val datatype,@id int;
        select @new_val = i.column_name from inserted i;
        select @id = i.Id from inserted i;
        update table_name set column_name = @new_val
        where table_name.Id = @id and column_name != @new_val;
        

        【讨论】:

          【解决方案7】:

          您需要在表中使用唯一键 id(假设它的值为 1)来执行以下操作:

          UPDATE table1 SET col1="hello" WHERE id=1 AND col1!="hello"
          

          【讨论】:

            【解决方案8】:

            老问题,但没有一个答案正确解决 null 值。

            使用 或 != 如果在新值或旧值中存在潜在的空值以安全更新,则在比较差异值时使用 或 != 会遇到麻烦,仅在 Postgres 中使用 is distinct from 运算符进行更改。阅读更多关于它的信息here

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 2014-03-06
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2012-10-29
              • 2017-10-16
              • 1970-01-01
              • 1970-01-01
              相关资源
              最近更新 更多