web-dev-qa-db-fra.com

Comment mettre à jour la clé primaire

Voici mon problème: J'ai 2 tables:

  1. WORKER, avec les colonnes |ID|OTHER_STAF|, où ID est la clé primaire
  2. FIRM, avec les colonnes |FPK|ID|SOMETHING_ELSE|, où la combinaison FPK et ID constituent la clé primaire et ID est également une clé étrangère référencée à WORKER.ID (non null et doit avoir la même valeur que dans WORKER).

Je souhaite créer la procédure stockée UPDATE_ID_WORKER, dans laquelle je souhaite modifier la valeur de l'ID spécifique dans WORKER, ainsi que dans toutes les instances de la valeur spécifique de l'ID dans FIRM.

procédure stockée:

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

19
Slavisa

Ne faites pas cela, mais insérez plutôt un nouvel enregistrement et mettez-le à jour de cette façon.
Mais si vous en avez vraiment besoin, vous pouvez procéder comme suit:

  • Désactiver l'application temporaire des contraintes FK (par exemple, ALTER TABLE foo WITH NOCHECK CONSTRAINT ALL)
  • Puis mettez à jour votre PK 
  • Puis mettez à jour vos FK pour qu'ils correspondent au changement de PK
  • Enfin, activez les contraintes FK en retour 
28
kevchadders

Tout d'abord, nous choisissons des colonnes de données stables (non statiques) pour former une clé primaire, précisément parce que nous souhaitons éviter de mettre à jour des clés dans une base de données relationnelle (dans laquelle les références sont classées par clé).

  1. Pour ce problème, peu importe que la clé soit une clé relationnelle ("constituée des données") et qu’elle possède donc l’intégrité relationnelle, la puissance et la vitesse, ou si la "clé" est un ID d’enregistrement, avec aucun de cette intégrité relationnelle, puissance et vitesse. L'effet est le même. 

  2. J'affirme cela, car de nombreux messages rédigés par des personnes désemparées suggèrent que c'est la raison exacte pour laquelle les ID d'enregistrement sont en quelque sorte supérieurs aux clés relationnelles.

  3. Le fait est que l'ID de clé ou d'enregistrement est migré partout où une référence est requise.

Deuxièmement, si vous devez modifier la valeur de la clé ou de l’ID d’enregistrement, vous devez la modifier. Voici la méthode OLTP conforme à la norme. Notez que les fournisseurs haut de gamme n'autorisent pas la "mise à jour en cascade".

  • Ecrire un proc. Foo_UpdateCascade_tr @ID, où Foo est le nom de la table

  • Commencer une transaction

  • D'abord INSERT-SELECT une nouvelle ligne dans la table parente, à partir de l'ancienne ligne, avec la nouvelle valeur Key ou RID

  • Deuxièmement, pour toutes les tables enfants, en commençant par le haut, INSERT-SELECT les nouvelles lignes, à partir des anciennes lignes, avec la nouvelle valeur Key ou RID

  • Troisièmement, SUPPRIMEZ les lignes dans les tables enfants qui ont l'ancienne valeur de clé ou RID, de bas en haut.

  • Enfin, supprimez la ligne de la table parente qui contient l'ancienne valeur de clé ou RID

  • Commettre la transaction

Re les autres réponses

Les autres réponses sont incorrectes.

  • Désactiver les contraintes, puis les activer, après avoir mis à jour les lignes requises (parent plus tous les enfants) n’est pas ce qu’une personne ferait dans un environnement de production en ligne si elle souhaite rester. Ce conseil est bon pour les bases de données mono-utilisateur.

  • La nécessité de modifier la valeur d'une clé ou d'un RID n'indique pas un défaut de conception. C'est un besoin ordinaire. Cela est atténué en choisissant des clés stables (non statiques). Cela peut être atténué, mais on ne peut pas l'éliminer.

  • Un substitut substituant une clé naturelle ne fera aucune différence. Dans l'exemple que vous avez donné, la "clé" est un substitut. Et il doit être mis à jour.

    • S'il vous plaît, juste substitut, il n'y a pas de "clé de substitution", car chaque mot contredit l'autre. Soit il s’agit d’une clé (constituée des données), sinon ce n’est pas le cas. Un substitut n'est pas constitué à partir des données, il est explicitement non-data . Il n'a aucune des propriétés d'une clé.
  • Il n'y a rien de "délicat" à appliquer en cascade toutes les modifications requises. Reportez-vous aux étapes indiquées ci-dessus.

  • Rien ne peut être empêché si l’univers est en train de changer. Ça change. Faites avec. Et comme la base de données est un ensemble de faits sur l’univers, lorsque celui-ci changera, la base de données devra changer. C'est la vie dans la grande ville, ce n'est pas pour les nouveaux joueurs.

  • Les gens qui se marient et les hérissons qui se font enterrer ne sont pas un problème (malgré de tels exemples suggérant que cela est un problème). Parce que nous n'utilisons pas les noms comme clés. Nous utilisons de petits identificateurs stables, tels que ceux utilisés pour identifier les données de l'univers. 

    • Les noms, les descriptions, etc., existent une fois, dans une rangée. Les clés existent partout où elles ont été migrées. Et si la "clé" est un RID, le RID existe également partout où il a été migré.
  • Ne mettez pas à jour le PK! est la deuxième chose la plus hilarante que j'ai lue depuis un moment. Ajouter une nouvelle colonne est le plus.

22
PerformanceDBA

Si vous êtes sûr que cette modification convient à l'environnement dans lequel vous travaillez: définissez les conditions FK des tables secondaires sur UPDATE CASCADING.

Par exemple, si vous utilisez SSMS comme interface graphique:

  1. clic droit sur la clé
  2. sélectionner Modifier
  3. Déplier 'INSERT AND UPDATE Specific'
  4. Pour 'Update Rule', sélectionnez Cascade.
  5. Fermez la boîte de dialogue et enregistrez la clé.

Lorsque vous mettez ensuite à jour une valeur dans la colonne PK de votre table primaire, les références FK des autres tables sont mises à jour pour pointer sur la nouvelle valeur, en préservant l'intégrité des données.

10
pbeentje

Lorsque vous estimez qu'il est nécessaire de mettre à jour une valeur de clé primaire ainsi que toutes les clés étrangères correspondantes, l'ensemble de la conception doit être corrigé.

Il est difficile de mettre en cascade tous les changements de clés étrangères nécessaires. Il est recommandé de ne jamais mettre à jour la clé primaire. Si vous le jugez nécessaire, vous devez utiliser un Surrogate Primary Key, clé qui n'est pas dérivée des données de l'application. Par conséquent, sa valeur n’est pas liée à la logique d’entreprise et n’a jamais besoin d’être modifiée (et doit être invisible pour l’utilisateur final). Vous pouvez ensuite mettre à jour et afficher une autre colonne.

par exemple:

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

lorsque vous créez de nombreuses tables ayant un FK en UserID, afin de suivre tout ce sur quoi l'utilisateur a travaillé, mais que cet utilisateur se marie ensuite et souhaite un ID correspondant à son nouveau nom de famille, vous n'avez aucune chance.

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

fK maintenant la clé primaire de substitution pour toutes les autres tables, et affiche UserLogin lorsque cela est nécessaire, leur permet de se connecter en utilisant cette valeur, et quand ils doivent la modifier, vous le modifiez dans une colonne de une seule ligne.

5
KM.

Ne met pas à jour la clé primaire . Cela pourrait vous causer beaucoup de problèmes si vous gardez vos données intactes, si vous avez d'autres tables qui les référencent.

Idéalement, si vous voulez un champ unique pouvant être mis à jour, créez un nouveau champ.

2
Daniel A. White

Vous pouvez utiliser cette fonction récursive pour générer le script T-SQL nécessaire.

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