【问题标题】:SQL Server - Using joins in Update statementSQL Server - 在更新语句中使用连接
【发布时间】:2021-02-19 05:48:23
【问题描述】:

我有一个 HRUser 和一个 Audit 表,两者都在生产中,有大量行。

现在我在HRUser 表中添加了一个名为IsActivated 的列。

我需要创建一个一次性脚本,该脚本将在生产环境中执行并将数据填充到此IsActivated 列中。在执行此一次性脚本之后,每当用户激活他们的帐户时,HRUser 表的IsActivated 列将自动更新。

为了更新HRUser表中的IsActivated列,我需要检查Audit表中用户是否登录到现在。

UPDATE [dbo].HRUser 
SET IsActivated = 1
FROM dbo.[UserAudit] A
JOIN dbo.[HRUser] U ON A.UserId = U.UserId
WHERE A.AuditTypeId = 14

AuditTypeId=14 表示用户已登录并且用户可以登录任意次数,并且每次用户登录时都会在UserAudit 表中捕获...

逻辑是,如果用户至少登录过一次,则意味着用户被激活。

这不能在较低的环境中测试,它需要直接在生产环境中执行,因为在较低的环境中我们在UserAudit 表中没有任何数据。

我不确定这是否有效,因为我从未在更新语句中使用过联接,我正在寻找比这更好的方法来完成我的任务的建议

【问题讨论】:

  • 似乎WHERE 中的EXISTS 在这里会更好,而不是JOIN;虽然(假设用户只能有 1 行 AuditTypeId 的值为 14)这应该可以工作。唯一奇怪的语法是你有UPDATE [dbo].HRUserHRUser 出现在JOIN 中。我希望UPDATE U 然后dbo.[HRUser] UFROMdbo.[UserAudit] 加入。
  • "这无法测试..." 哦,是的,它当然可以 - 但是懒惰会阻止您输入一些捏造的测试数据。如果没有非常特殊的原因,绝不应该在未经 QA 环境测试的生产机器上运行脚本。
  • @Larnu UserAudit 表中的单个用户可以有多个条目,因为用户可以多次登录
  • 这就是为什么我提出警告并建议EXISTS

标签: sql sql-server sql-update subquery inner-join


【解决方案1】:

您可以使用EXISTS 和相关子查询来过滤UserId 至少有一个ID 为14 的审计事件的行:

UPDATE h
SET IsActivated = 1
FROM [dbo].HRUser h
WHERE EXISTS (
    SELECT 1 FROM 
    FROM dbo.[UserAudit] a
    WHERE a.UserId = h.UserId AND a.AuditTypeId = 14
)

注意,在子查询中重新打开目标表是没有意义的;您只需要将其与外部查询相关联。

【讨论】:

  • 我曾想过使用 In 或 Exists,但我对性能持怀疑态度,因为 Audit 表中有大量数据,这就是我尝试使用 join 的原因,谢谢,我会尝试使用 exists
  • 当您在查询中说 Select 1 时,它会给出 1 ,这如何工作?还有在 UPDATE 之后使用别名而不是表名的任何具体原因吗?
  • @snadell:为提高性能,请考虑在UserAudit(UserId, AuditTypeId) 上建立索引。至于SELECT 1EXISTS 只是检查子查询是否返回任何内容(无论如何),所以SELECT 1 就可以了(SELECT *SELECT NULL 等也可以)。最后:SQL Server中UPDATE的语法如下:UPDATE中的别名指的是FROM子句中定义的别名。
  • 感谢您的解释 nkw,如果我更新 dbo.[HRUser] h set h.IsActivated = 1 where exists(这里是您提供的子查询),这对我来说更有意义和上面的 1 有什么区别还是两者都一样?进入索引,因为这是一个 1 次脚本,我认为它不是必需的
【解决方案2】:

以下两种方法。不建议将方法 1 用于“在生产中具有大量行”的表。但是编码要容易得多。方法 2 可在生产环境中运行,无需停机。

无论您选择哪种方法:在生产之外进行测试。从生产中复制数据。如果你不能做到这一点,那就建立你自己的。建立一个玩具系统。强烈建议您在生产中运行任一方法之前进行一定程度的测试。

方法一: 更新连接是直截了当的。使用别名。提醒一下,不建议“有大量行”和生产运行。 SQL Server 优化器很可能会升级两个表上的锁并阻塞这些表,直到更新完成。如果您正在中断并且不关心更新需要多长时间,则此方法有效。

UPDATE U 
SET IsActivated = 1
FROM dbo.[UserAudit] A
JOIN dbo.[HRUser] U ON A.UserId = U.UserId
WHERE A.AuditTypeId = 14

方法 2: 如果您无法为此更新停止生产系统(而我们大多数人都不能),那么我建议您做 2 件事:

  1. 使用循环内的事务设置一个循环。这意味着优化器将使用行锁而不是阻塞整个表。这种方法可能需要更长的时间,但不会阻碍生产。如果更新需要更长的时间,只要 devops 团队因为生产被阻止而从不打电话,就不必担心。

  2. 捕获要在事务外部更新的行。然后,根据主键更新(最快)。总事务时间是更新的行将被阻止多长时间。

这是一个循环的玩具示例。

-- STEP 1:  get data to be updated
CREATE TABLE #selected ( ndx INT IDENTITY(1,1), UserId INT )
INSERT INTO #selected (UserId)
SELECT UserId 
FROM dbo.[UserAudit] A
JOIN dbo.[HRUser] U ON A.UserId = U.UserId
WHERE A.AuditTypeId = 14

-- STEP 2:  update on primary key in steps of 1000
DECLARE @RowsToUpdate INT = 1000
    , @LastId INT = 0
    , @RowCnt INT = 0


DECLARE @v TABLE(ndx INT, UserId INT)

WHILE 1=1
BEGIN 

    DELETE @v 
    
    INSERT INTO @v 
    SELECT TOP(@RowsToUpdate) * 
    FROM #selected WHERE ndx > @LastId
    ORDER BY ndx

    SET @RowCnt = @@ROWCOUNT 
    IF @RowCnt = 0 
    BREAK;

    BEGIN TRANSACTION
        UPDATE a
        SET IsActivated = 1
        FROM @v v
        JOIN dbo.HRUser a ON a.Id = v.UserId 
    COMMIT TRANSACTION

    SELECT @LastId = MAX(ndx) FROM @v 

END 

【讨论】:

    猜你喜欢
    • 2012-04-24
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-07-19
    • 2023-03-11
    • 2015-02-27
    • 2019-04-21
    • 1970-01-01
    相关资源
    最近更新 更多