web-dev-qa-db-ja.com

主キーを更新する方法

私の問題は次のとおりです。2つのテーブルがあります。

  1. WORKER、列|ID|OTHER_STAF|、IDは主キー
  2. FIRM、列|FPK|ID|SOMETHING_ELSE|、FPKとIDの組み合わせが主キーを構成し、IDもWORKER.IDを参照する外部キーです(nullではなく、WORKERと同じ値を持つ必要があります)。

WORKERの特定のIDの値を変更するストアドプロシージャUPDATE_ID_WORKERを作成します。また、FIRMのI​​Dの特定の値のすべてのインスタンスを変更します。

ストアドプロシージャ:

........ @id .. ???? ........

21
Slavisa

実際にはこれを行うべきではありませんが、代わりに新しいレコードを挿入し、そのように更新する必要があります。
しかし、本当に必要な場合は、次のことができます。

  • FK制約の一時的な適用を無効にします(例:ALTER TABLE foo WITH NOCHECK CONSTRAINT ALL
  • 次に、PKを更新します
  • 次に、PKの変更に合わせてFKを更新します
  • 最後に、FK制約の逆実行を有効にします
30
kevchadders

最初に、安定した(静的ではない)データ列を選択して主キーを形成します。これは、リレーショナルデータベース(キーによる参照)のキーの更新は避けたいためです。

  1. この問題では、キーがリレーショナルキー(「データから作成」)であり、したがってリレーショナル整合性、パワー、および速度を持っているか、または「キー」がレコードIDであり、なしその関係の完全性、力、および速度の。効果は同じです。

  2. これは、レコードIDがリレーショナルキーよりも優れている正確な理由であると示唆する無知な人による投稿が多いためです。

  3. ポイントは、キーまたはレコードIDが参照が必要な場所に移行されることです。

第二に、キーまたはレコードIDの値を変更する必要がある場合は、変更する必要があります。 OLTP標準準拠の方法です。ハイエンドベンダーは「カスケード更新」を許可していないことに注意してください。

  • プロシージャを作成します。 Foo_UpdateCascade_tr @ ID、Fooはテーブル名

  • 取引を開始する

  • 最初に、古い行から新しいキーまたはRID値を使用して、親テーブルの新しい行をINSERT-SELECTします

  • 第二に、すべての子テーブルについて、上から下に向かって作業し、古い行から新しいキーまたはRID値で新しい行をINSERT-SELECTします

  • 3番目に、古いキーまたはRID値を持つ子テーブルの行を削除します。

  • 最後に、古いキーまたはRID値を持つ親テーブルの行を削除します

  • トランザクションをコミットする

その他の回答について

他の答えは間違っています。

  • 必要な行(親とすべての子)をUPDATした後、制約を無効にしてから有効にすることは、従業員が引き続き雇用を希望する場合にオンラインの実稼働環境で行うことではありません。このアドバイスは、シングルユーザーデータベースに適しています。

  • キーまたはRIDの値を変更する必要は、設計上の欠陥を示すものではありません。それは普通の必要です。これは、安定した(静的ではない)キーを選択することで軽減されます。軽減することはできますが、排除することはできません。

  • 自然なキーを代用するサロゲートは、違いを生じません。あなたが与えた例では、「キー」は代理です。そして、更新する必要があります。

    • 代わりに、「サロゲートキー」などというものはありません。各Wordが互いに矛盾しているためです。キー(データから作成)xまたはそうではありません。サロゲートはデータから構成されておらず、明示的にnon-dataです。キーのプロパティはありません。
  • 必要なすべての変更をカスケードすることについて「難しい」ことは何もありません。上記の手順を参照してください。

  • 宇宙が変化するのを防ぐことができるものは何もありません。それは変わる。それに対処する。また、データベースはユニバースに関するファクトのコレクションであるため、ユニバースが変更されると、データベースを変更する必要があります。それは大都市での生活であり、新しいプレイヤーのためではありません。

  • 結婚したり、ハリネズミが埋葬されたりすることは問題ではありません(そのような例を使用して、問題であることを示唆しています)。キーとして名前を使用しないため。ユニバースのデータを識別するために使用されるような、小さく安定した識別子を使用します。

    • 名前、説明などは、1行に1回存在します。キーは移行された場所に存在します。 「キー」がRIDである場合、RIDも移行された場所に存在します。
  • PKを更新しないでください!は、私がしばらく読んだ2番目に面白いものです。 新しい列の追加が最も多くなります。

23
PerformanceDBA

sureの場合、この変更が作業中の環境に適していること:セカンダリテーブルのFK条件をUPDATE CASCADINGに設定します。

たとえば、SSMSをGUIとして使用している場合:

  1. キーを右クリック
  2. 選択変更
  3. 「INSERT And UPDATE Specific」を展開します
  4. [ルールの更新]で、[カスケード]を選択します。
  5. ダイアログを閉じて、キーを保存します。

次に、プライマリテーブルのPK列の値を更新すると、他のテーブルのFK参照が更新され、新しい値を指すようになり、データの整合性が維持されます。

11
pbeentje

主キー値と一致するすべての外部キーを更新する必要がある場合は、設計全体を修正する必要があります。

必要なすべての外部キーの変更をカスケードするのは難しいです。主キーを更新しないことをお勧めします。必要に応じて、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を表示し、その値を使用してログインできるようにします。

5
KM.

主キーを更新しない。それを参照する他のテーブルがある場合、データをそのまま保持するために多くの問題を引き起こす可能性があります。

理想的には、更新可能な一意のフィールドが必要な場合は、新しいフィールドを作成します。

2
Daniel A. White

この再帰関数を使用して、必要な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;
0