如何更新主键

时间:2010-03-23 10:55:09

标签: sql-server sql-server-2008 foreign-keys composite-primary-key

这是我的问题: 我有2张桌子:

  1. WORKER,列|ID|OTHER_STAF|,其中ID是主键
  2. FIRM,列|FPK|ID|SOMETHING_ELSE|,其中组合FPK和ID构成主键,ID也是引用WORKER.ID的外键(非空,并且必须具有与WORKER中相同的值)。
  3. 我想创建存储过程UPDATE_ID_WORKER,我希望在WORKER中更改特定ID的值,以及在FIRM中更改ID的特定值的所有实例。

    存储过程:

    ........ @ID .. ???? ........

6 个答案:

答案 0 :(得分:30)

您不应该这样做,而是插入新记录中并以此方式更新 但是,如果您真的需要,可以执行以下操作:

  • 暂时禁用强制执行FK约束(例如ALTER TABLE foo WITH NOCHECK CONSTRAINT ALL
  • 然后更新你的PK
  • 然后更新您的FK以匹配PK更改
  • 最后启用强制执行FK约束

答案 1 :(得分:23)

首先,我们选择稳定(非静态)数据列来形成主键,正是因为更新关系数据库中的键(其中引用是按键)是我们希望避免的。

  1. 对于这个问题,Key是一个关系密钥(“由数据组成”)并不重要,因此具有关系完整性,权限和速度,或者如果“密钥”是记录ID,没有任何关系完整性,功能和速度。效果是一样的。

  2. 我说明了这一点,因为无知的帖子有很多帖子,他们认为这就是记录ID在某种程度上优于关系密钥的确切原因。

  3. 重点是,密钥或记录ID会迁移到需要参考的地方。

  4. 其次,如果您必须更改密钥或记录ID的值,那么您必须更改它。这是符合OLTP标准的方法。请注意,高端供应商不允许“级联更新”。

    • 写一个proc。 Foo_UpdateCascade_tr @ID,其中Foo是表名

    • 开始交易

    • 首先使用新的Key或RID值从旧行中选择父表中的新行

    • 其次,对于所有子表,从上到下工作,使用新的Key或RID值从旧行INSERT-SELECT新行

    • 第三,删除具有旧Key或RID值的子表中的行,从头到尾工作

    • 最后,删除父表中具有旧密钥或RID值的行

    • 提交交易

    重新提出其他答案

    其他答案不正确。

    • 禁用约束然后启用它们,在UPDAT所需的行(父级加所有子级)之后,如果他们希望继续使用,则不是某个人在在线生产环境中会做的事情。该建议适用于单用户数据库。

    • 需要更改密钥或RID的值并不表示存在设计缺陷。这是一个普通的需要。通过选择稳定(非静态)键可以减轻这种影响。它可以减轻,但不能消除。

    • 代替自然密钥的代理人不会有任何区别。在您给出的示例中,“密钥”是代理。它需要更新。

      • 请,只是代理,没有“代理钥匙”这样的东西,因为每个词都与另一个词相矛盾。它是一个密钥(由数据组成)xor它不是。代理不是由数据组成的,而是明确非数据。它没有Key的属性。
    • 关于级联所有必需的更改,没有什么“棘手”。请参阅上面给出的步骤。

    • 宇宙变化无法阻止任何事情发生。它改变。处理它。由于数据库是有关Universe的事实的集合,因此当Universe发生更改时,数据库将不得不更改。那就是大城市的生活,不适合新玩家。

    • 人们结婚和刺猬被埋没不是问题(尽管这些例子被用来暗示 是一个问题)。因为我们不使用Names作为键。我们使用小而稳定的标识符,例如用于标识Universe中的数据。

      • 名称,描述等在一行中存在一次。密钥存在于迁移的任何位置。如果“密钥”是RID,那么RID也存在于迁移的任何地方。
    • 不要更新PK!是我在一段时间内读过的第二个最热闹的东西。 添加新列是最多的。

答案 2 :(得分:11)

如果您确定此更改适合您正在使用的环境:将辅助表格上的FK条件设置为UPDATE CASCADING。

例如,如果将SSMS用作GUI:

  1. 右键单击密钥
  2. 选择修改
  3. 折叠' INSERT AND UPDATE具体'
  4. 对于'更新规则',请选择级联。
  5. 关闭对话框并保存密钥。
  6. 然后更新主表中PK列中的值时,其他表中的FK引用将更新为指向新值,从而保持数据完整性。

答案 3 :(得分:5)

当您发现有必要更新主键值以及所有匹配的外键时,则需要修复整个设计。

级联所有必要的外键更改很棘手。从不更新主键是最佳做法,如果您认为有必要,则应使用Surrogate Primary Key,这是不是从应用程序数据派生的密钥。因此,它的价值与业务逻辑无关,永远不需要改变(并且对最终用户来说应该是不可见的)。然后,您可以更新并显示其他一些列。

例如:

BadUserTable
UserID     varchar(20) primary key --user last name
other columns...

当您创建许多具有FK到UserID的表时,要跟踪用户所处理的所有内容,但该用户随后结婚并希望ID与其新姓相匹配,那么您就不幸了。

GoodUserTable
UserID    int identity(1,1) primary key
UserLogin varchar(20) 
other columns....

您现在将所有其他表格的代理主键FK,并在必要时显示UserLogin,允许它们使用该值登录,当需要更改它时,只在一行的一列中更改它。 / p>

答案 4 :(得分:3)

不要更新主键。如果您有任何其他表引用它,它可能会导致很多问题,保持您的数据完整。

理想情况下,如果您想要一个可更新的唯一字段,请创建一个新字段。

答案 5 :(得分:0)

您可以使用此递归函数生成必要的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;