这是我的问题 - 我有 2 张桌子:
- WORKER,带有列
|ID|OTHER_STAF|
,其中 ID 是主键 - FIRM,带有列
|FPK|ID|SOMETHING_ELSE|
,其中 FPK 和 ID 组合构成主键,并且 ID 是引用到 WORKER.ID 的外键(不为空,并且必须与 WORKER 具有相同的值)。
我想制作存储过程UPDATE_ID_WORKER
,我想更改 WORKER 中特定 ID 的值,以及 FIRM 中特定 ID 值的所有实例。
这是我的问题 - 我有 2 张桌子:
|ID|OTHER_STAF|
,其中 ID 是主键|FPK|ID|SOMETHING_ELSE|
,其中 FPK 和 ID 组合构成主键,并且 ID 是引用到 WORKER.ID 的外键(不为空,并且必须与 WORKER 具有相同的值)。我想制作存储过程UPDATE_ID_WORKER
,我想更改 WORKER 中特定 ID 的值,以及 FIRM 中特定 ID 值的所有实例。
您不应该真的这样做,而是插入一条新记录并以这种方式更新它。
但是,如果您真的需要,您可以执行以下操作:
ALTER TABLE foo WITH NOCHECK CONSTRAINT ALL
)首先,我们选择稳定(非静态)的数据列来形成主键,正是因为我们希望避免更新关系数据库中的键(其中引用是键)。
对于这个问题,如果密钥是关系密钥(“由数据组成”)并因此具有关系完整性、功率和速度,或者“密钥”是否是记录 ID,则无关紧要,没有关系完整性、力量和速度。效果是一样的。
我这么说是因为有很多无知者的帖子,他们认为这就是记录 ID 在某种程度上比关系键更好的确切原因。
关键是,密钥或记录 ID 被迁移到需要引用的任何地方。
其次,如果您必须更改 Key 或 Record ID 的值,那么您必须更改它。这是符合 OLTP 标准的方法。请注意,高端供应商不允许“级联更新”。
写一个过程。Foo_UpdateCascade_tr @ID,其中 Foo 是表名
开始交易
首先 INSERT-SELECT 父表中的新行,从旧行中使用新的 Key 或 RID 值
其次,对于所有子表,从上到下工作,使用新的 Key 或 RID 值从旧行中插入选择新行
第三,删除子表中具有旧 Key 或 RID 值的行,从下到上工作
最后,删除父表中具有旧 Key 或 RID 值的行
提交事务
其他答案不正确。
在更新所需的行(父项加上所有子项)之后禁用约束然后启用它们不是一个人在在线生产环境中会做的事情,如果他们希望继续受雇。该建议适用于单用户数据库。
需要更改 Key 或 RID 的值并不表示存在设计缺陷。这是一个普通的需求。通过选择稳定的(非静态的)密钥可以缓解这种情况。它可以减轻,但不能消除。
替代自然键的代理不会有任何区别。在您给出的示例中,“键”是代理项。它需要更新。
级联所有必需的更改没有什么“棘手的”。参考上面给出的步骤。
没有什么是可以阻止宇宙变化的。它改变。处理它。而且由于数据库是关于宇宙的事实的集合,当宇宙发生变化时,数据库将不得不改变。那是大城市的生活,不适合新玩家。
人们结婚和刺猬被埋葬都不是问题(尽管这些例子被用来暗示这是一个问题)。因为我们不使用名称作为键。我们使用小的、稳定的标识符,例如用于识别宇宙中的数据。
不更新PK!是我在一段时间内读过的第二个最搞笑的东西。 添加新列是最多的。
如果您确定此更改适合您工作的环境:将辅助表上的 FK 条件设置为 UPDATE CASCADING。
例如,如果使用 SSMS 作为 GUI:
然后,当您更新主表中 PK 列中的值时,其他表中的 FK 引用将更新为指向新值,从而保持数据完整性。
当您发现有必要更新主键值以及所有匹配的外键时,则需要修复整个设计。
级联所有必要的外键更改是很棘手的。最好不要更新主键,如果您认为有必要,您应该使用 a Surrogate Primary Key
,它不是从应用程序数据派生的键。因此,它的值与业务逻辑无关,并且永远不需要更改(并且应该对最终用户不可见)。然后,您可以更新并显示其他列。
例如:
BadUserTable
UserID varchar(20) primary key --user last name
other columns...
当您创建许多具有与 UserID 的 FK 的表时,以跟踪用户所做的所有工作,但该用户随后结婚并希望 ID 与他们的新姓氏相匹配,那么您就不走运了。
GoodUserTable
UserID int identity(1,1) primary key
UserLogin varchar(20)
other columns....
您现在将代理主键 FK 到所有其他表,并在必要时显示 UserLogin,允许他们使用该值登录,当他们需要更改它时,您仅在一行的一列中更改它。
不要更新主键。如果您有任何其他表引用它,它可能会导致您保持数据完整的许多问题。
理想情况下,如果您想要一个可更新的唯一字段,请创建一个新字段。
您可以使用此递归函数来生成必要的 T-SQL 脚本。
CREATE FUNCTION dbo.Update_Delete_PrimaryKey
(
@TableName NVARCHAR(255),
@ColumnName NVARCHAR(255),
@OldValue NVARCHAR(MAX),
@NewValue NVARCHAR(MAX),
@Del BIT
)
RETURNS NVARCHAR
(
MAX
)
AS
BEGIN
DECLARE @fks TABLE
(
constraint_name NVARCHAR(255),
table_name NVARCHAR(255),
col NVARCHAR(255)
);
DECLARE @Sql NVARCHAR(MAX),
@EnableConstraints NVARCHAR(MAX);
SET @Sql = '';
SET @EnableConstraints = '';
INSERT INTO @fks
(
constraint_name,
table_name,
col
)
SELECT oConstraint.name constraint_name,
oParent.name table_name,
oParentCol.name col
FROM sys.foreign_key_columns sfkc
--INNER JOIN sys.foreign_keys sfk
-- ON sfk.[object_id] = sfkc.constraint_object_id
INNER JOIN sys.sysobjects oConstraint
ON sfkc.constraint_object_id = oConstraint.id
INNER JOIN sys.sysobjects oParent
ON sfkc.parent_object_id = oParent.id
INNER JOIN sys.all_columns oParentCol
ON sfkc.parent_object_id = oParentCol.object_id
AND sfkc.parent_column_id = oParentCol.column_id
INNER JOIN sys.sysobjects oReference
ON sfkc.referenced_object_id = oReference.id
INNER JOIN sys.all_columns oReferenceCol
ON sfkc.referenced_object_id = oReferenceCol.object_id
AND sfkc.referenced_column_id = oReferenceCol.column_id
WHERE oReference.name = @TableName
AND oReferenceCol.name = @ColumnName
--AND (@Del <> 1 OR sfk.delete_referential_action = 0)
--AND (@Del = 1 OR sfk.update_referential_action = 0)
IF EXISTS(
SELECT 1
FROM @fks
)
BEGIN
DECLARE @Constraint NVARCHAR(255),
@Table NVARCHAR(255),
@Col NVARCHAR(255)
DECLARE Table_Cursor CURSOR LOCAL
FOR
SELECT f.constraint_name,
f.table_name,
f.col
FROM @fks AS f
OPEN Table_Cursor FETCH NEXT FROM Table_Cursor INTO @Constraint, @Table,@Col
WHILE (@@FETCH_STATUS = 0)
BEGIN
IF @Del <> 1
BEGIN
SET @Sql = @Sql + 'ALTER TABLE ' + @Table + ' NOCHECK CONSTRAINT ' + @Constraint + CHAR(13) + CHAR(10);
SET @EnableConstraints = @EnableConstraints + 'ALTER TABLE ' + @Table + ' CHECK CONSTRAINT ' + @Constraint
+ CHAR(13) + CHAR(10);
END
SET @Sql = @Sql + dbo.Update_Delete_PrimaryKey(@Table, @Col, @OldValue, @NewValue, @Del);
FETCH NEXT FROM Table_Cursor INTO @Constraint, @Table,@Col
END
CLOSE Table_Cursor DEALLOCATE Table_Cursor
END
DECLARE @DataType NVARCHAR(30);
SELECT @DataType = t.name +
CASE
WHEN t.name IN ('char', 'varchar', 'nchar', 'nvarchar') THEN '(' +
CASE
WHEN c.max_length = -1 THEN 'MAX'
ELSE CONVERT(
VARCHAR(4),
CASE
WHEN t.name IN ('nchar', 'nvarchar') THEN c.max_length / 2
ELSE c.max_length
END
)
END + ')'
WHEN t.name IN ('decimal', 'numeric') THEN '(' + CONVERT(VARCHAR(4), c.precision) + ','
+ CONVERT(VARCHAR(4), c.Scale) + ')'
ELSE ''
END
FROM sys.columns c
INNER JOIN sys.types t
ON c.user_type_id = t.user_type_id
WHERE c.object_id = OBJECT_ID(@TableName)
AND c.name = @ColumnName
IF @Del <> 1
BEGIN
SET @Sql = @Sql + 'UPDATE [' + @TableName + '] SET [' + @ColumnName + '] = CONVERT(' + @DataType + ', ' + ISNULL('N''' + @NewValue + '''', 'NULL')
+ ') WHERE [' + @ColumnName + '] = CONVERT(' + @DataType + ', ' + ISNULL('N''' + @OldValue + '''', 'NULL') +
');' + CHAR(13) + CHAR(10);
SET @Sql = @Sql + @EnableConstraints;
END
ELSE
SET @Sql = @Sql + 'DELETE [' + @TableName + '] WHERE [' + @ColumnName + '] = CONVERT(' + @DataType + ', N''' + @OldValue
+ ''');' + CHAR(13) + CHAR(10);
RETURN @Sql;
END
GO
DECLARE @Result NVARCHAR(MAX);
SET @Result = dbo.Update_Delete_PrimaryKey('@TableName', '@ColumnName', '@OldValue', '@NewValue', 0);/*Update*/
EXEC (@Result)
SET @Result = dbo.Update_Delete_PrimaryKey('@TableName', '@ColumnName', '@OldValue', NULL, 1);/*Delete*/
EXEC (@Result)
GO
DROP FUNCTION Update_Delete_PrimaryKey;