SQL 服务器 - 在 Update 语句中使用连接
SQL Server - Using joins in Update statement
我有一个 HRUser
和一个 Audit
table,它们都在生产中,行数很多。
现在我又在我的 HRUser
table 中添加了一列,名为 IsActivated
。
我需要创建一个将在生产环境中执行的一次性脚本,并将数据填充到此 IsActivated
列中。执行此一次性脚本后,每当用户激活他们的帐户时,HRUser
table 的 IsActivated
列将自动更新。
为了更新 HRUser
table 中的 IsActivated
列,我需要检查 Audit
table 用户是否已经登录到现在.
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
table...
逻辑是,如果用户至少登录一次,则表示用户已激活。
这无法在较低的环境中进行测试,需要直接在生产环境中执行,因为在较低的环境中我们在 UserAudit
table.
中没有任何数据
我不确定这是否有效,因为我从未在更新语句中使用过联接,我正在寻找比这更好的方法来完成我的任务的建议
您可以使用 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
)
请注意,在子查询中重新打开目标 table 没有意义;你只需要将它与外部查询相关联。
下面两种方法。不建议将方法 1 用于 tables “在具有大量行的生产中”。但是编码要容易得多。方法 2 可在生产环境中运行,无需停机。
无论您选择哪种方法:在生产环境之外对其进行测试。从生产中复制数据。如果你做不到,那就建立你自己的。建立一个玩具系统。强烈建议您在 运行 生产中的任何一种方法之前进行某种程度的测试。
方法一:
更新连接很简单。使用别名。提醒,这不推荐“有大量行”和生产 运行。 SQL 服务器优化器很可能会升级两个 table 上的锁并阻止 table 直到更新完成。如果您正在中断并且不关心更新需要多长时间,则此方法有效。
UPDATE U
SET IsActivated = 1
FROM dbo.[UserAudit] A
JOIN dbo.[HRUser] U ON A.UserId = U.UserId
WHERE A.AuditTypeId = 14
方法 2:
如果您不能为此更新停止您的生产系统(我们大多数人不能),那么我建议您做两件事:
设置一个循环,事务在循环内。这意味着优化器将使用行锁而不是阻塞整个 table。此方法可能需要更长的时间,但不会阻止生产。如果更新需要更长的时间,只要 devops 团队永远不会因为生产被阻止而打电话,就不要担心。
捕获要在事务外更新的行。然后,基于主键更新(最快)。总事务时间是更新的行将被阻止的时间。
这是一个循环玩具示例。
-- 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
我有一个 HRUser
和一个 Audit
table,它们都在生产中,行数很多。
现在我又在我的 HRUser
table 中添加了一列,名为 IsActivated
。
我需要创建一个将在生产环境中执行的一次性脚本,并将数据填充到此 IsActivated
列中。执行此一次性脚本后,每当用户激活他们的帐户时,HRUser
table 的 IsActivated
列将自动更新。
为了更新 HRUser
table 中的 IsActivated
列,我需要检查 Audit
table 用户是否已经登录到现在.
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
table...
逻辑是,如果用户至少登录一次,则表示用户已激活。
这无法在较低的环境中进行测试,需要直接在生产环境中执行,因为在较低的环境中我们在 UserAudit
table.
我不确定这是否有效,因为我从未在更新语句中使用过联接,我正在寻找比这更好的方法来完成我的任务的建议
您可以使用 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
)
请注意,在子查询中重新打开目标 table 没有意义;你只需要将它与外部查询相关联。
下面两种方法。不建议将方法 1 用于 tables “在具有大量行的生产中”。但是编码要容易得多。方法 2 可在生产环境中运行,无需停机。
无论您选择哪种方法:在生产环境之外对其进行测试。从生产中复制数据。如果你做不到,那就建立你自己的。建立一个玩具系统。强烈建议您在 运行 生产中的任何一种方法之前进行某种程度的测试。
方法一: 更新连接很简单。使用别名。提醒,这不推荐“有大量行”和生产 运行。 SQL 服务器优化器很可能会升级两个 table 上的锁并阻止 table 直到更新完成。如果您正在中断并且不关心更新需要多长时间,则此方法有效。
UPDATE U
SET IsActivated = 1
FROM dbo.[UserAudit] A
JOIN dbo.[HRUser] U ON A.UserId = U.UserId
WHERE A.AuditTypeId = 14
方法 2: 如果您不能为此更新停止您的生产系统(我们大多数人不能),那么我建议您做两件事:
设置一个循环,事务在循环内。这意味着优化器将使用行锁而不是阻塞整个 table。此方法可能需要更长的时间,但不会阻止生产。如果更新需要更长的时间,只要 devops 团队永远不会因为生产被阻止而打电话,就不要担心。
捕获要在事务外更新的行。然后,基于主键更新(最快)。总事务时间是更新的行将被阻止的时间。
这是一个循环玩具示例。
-- 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