web-dev-qa-db-fra.com

Création d'une colonne calculée persistante avec une fonction

Je travaille avec les programmeurs sur une solution de base de données. Ils veulent ajouter une colonne calculée pour imiter les anciennes clés des requêtes, procédures et systèmes plus anciens et l'indexer. Les nouvelles clés seront GUIDS.

Pour ce faire, ils souhaitent créer une fonction pour la colonne calculée qui crée une valeur et la conserver. Il ne les laissera pas persister dans la colonne. Je n'ai pas de flou chaleureux sur l'idée et je ne trouve pas non plus d'informations sur la technique sur le web (est-ce une technique?).

Je pense qu'ils doivent ajouter un déclencheur à la place. Quelqu'un a-t-il une idée?

La fonction sera exécutée comme suit:

(SELECT [INT Identity field] FROM TABLE WHERE [GUID COLUMN] = @GUIDKEY

Il renvoie un champ d'identité INT basé sur le GUID.

Celui-ci sera exécuté à chaque insertion dans une table associée. SO si Table One contient la clé primaire, la table associée Two sera mise à jour (en utilisant le GUID passé) pour obtenir la clé de la Table one et insérer dans le tableau deux.

5
Paul

Je ne comprends toujours pas pourquoi cela doit être un colonne dans un tablea, tant pis pour celui qui persiste.

Pourquoi ne pas simplement créer une fonction table que vous croisez appliquer lorsque (et seulement lorsque) la requête en a réellement besoin? Étant donné que l'ancienne clé ne changera jamais, elle n'a pas besoin d'être calculée ou persistée de toute façon.

Si vous voulez vraiment que l'ancienne clé vive à plusieurs endroits (et cela ressemble à des gens qui ne devraient pas prendre ce genre de décision ont déjà pris ce genre de décision), faites simplement la recherche dans un déclencheur et remplissez-la au moment de l'écriture . Ensuite, c'est juste une colonne statique dans un tableau.

Je recommande toujours fortement une fonction table pour faciliter cela, afin que vous puissiez écrire le déclencheur de manière à ce qu'il gère les opérations sur plusieurs lignes ... sans avoir à écrire une boucle, ou appeler une fonction à valeur scalaire et encore une fois pour chaque ligne.

Juste pour montrer à quel point ces choses sont similaires (et interrogez votre développeur principal qui "ne l'aime pas"):

-- bad, slow, painful row-by-row
CREATE FUNCTION dbo.GetIDByGUID
(
  @GuidKey uniqueidentifier
)
RETURNS int
AS
BEGIN
  RETURN (SELECT $IDENTITY FROM dbo.tablename WHERE guid_column = @GuidKey);
END
GO

-- in the trigger:
UPDATE r SET oldkey = dbo.GetIDByGUID(i.guid_column)
  FROM dbo.related AS r
  INNER JOIN inserted AS i
  ON r.guid_column = i.guid_column;

Maintenant, si vous avez une fonction table, le code est assez similaire, mais vous constaterez que les performances sont bien meilleures dans les opérations sur plusieurs lignes et presque identiques pour les opérations sur une seule ligne.

-- ah, much better
ALTER FUNCTION dbo.GetIDByGUID_TVF
(
  @GuidKey uniqueidentifier
)
RETURNS TABLE
AS
  RETURN (SELECT id = $IDENTITY FROM dbo.tablename WHERE guid_column = @GuidKey);
GO

-- in the trigger:
UPDATE r SET oldkey = f.id
  FROM dbo.related AS r
  INNER JOIN inserted AS i
  ON r.guid_column = i.guid_column
  CROSS APPLY dbo.GetIDByGUID_TVF(i.guid_column) AS f;
9
Aaron Bertrand

Je ne sais pas pourquoi vous pensez avoir besoin d'une fonction ou d'une colonne calculée pour ce faire. Vous pouvez simplement ajouter une nouvelle colonne à la table avec une valeur par défaut et l'indexer comme vous le souhaitez.

CREATE TABLE dbo.whatever ( Id INT );

ALTER TABLE dbo.whatever
ADD YourMom UNIQUEIDENTIFIER
        DEFAULT NEWSEQUENTIALID();

CREATE INDEX ix_whatever ON dbo.whatever (YourMom);

Depuis que vous avez mis à jour votre question, examinons ce qu'est vraiment une idée horrible. Je vais simplifier un peu l'exemple.

CREATE TABLE dbo.whatever ( Id INT PRIMARY KEY);

CREATE TABLE dbo.ennui ( Id INT PRIMARY KEY, meh INT );
GO 

CREATE FUNCTION dbo.BadIdea ( @notguido INT )
RETURNS INT
WITH SCHEMABINDING, RETURNS NULL ON NULL INPUT
AS
    BEGIN
        DECLARE @out INT;
        SELECT @out = ( SELECT e.Id FROM dbo.ennui AS e WHERE e.meh = @notguido );
        RETURN @out;
    END;
GO 

ALTER TABLE dbo.whatever ADD ishygddt AS dbo.BadIdea(Id)

/*Will fail*/
ALTER TABLE dbo.whatever ALTER COLUMN ishygddt ADD PERSISTED;

/*Will fail*/
CREATE INDEX ix_whatever ON dbo.whatever (ishygddt);

Essayer de persister une colonne calculée basée sur une fonction scalaire (même déterministe avec LIAISON DE SCHÉMA ) échouera si elle effectue un accès aux données.

Msg 4934, niveau 16, état 3, ligne 23 La colonne calculée "ishygddt" dans la table "quel que soit" ne peut pas être conservée car la colonne a un accès aux données utilisateur ou système.

Vous ne pouvez pas non plus l'indexer:

Msg 2709, niveau 16, état 1, ligne 25 La colonne 'ishygddt' de la table 'dbo.wwhat' ne peut pas être utilisée dans un index ou des statistiques ou comme clé de partition car elle permet l'accès aux données utilisateur ou système.

Vous rencontrerez également de nombreux problèmes car la fonction s'exécutera ligne par ligne pour récupérer les données et forcera toutes les requêtes sur la table à s'exécuter en série .

Si vous modifiez des données dans la table référencée par la fonction et sélectionnez des données dans la table qui appelle la fonction dans la colonne calculée, vous pouvez vous retrouver avec des scénarios de blocage vraiment déroutants dans lesquels la fonction ne peut pas renvoyer de données à une requête sur une table apparemment sans rapport.

C'est une mauvaise idée tout autour. Aaron a donné ce que je pense être le meilleur conseil dans son commentaire:

Pourquoi ne pas simplement créer une fonction table que vous croisez appliquer lorsque (et seulement lorsque) la requête en a réellement besoin? Étant donné que l'ancienne clé ne changera jamais, elle n'a pas besoin d'être calculée ou persistée de toute façon. Si vous voulez vraiment que l'ancienne clé vive à plusieurs endroits, faites la recherche dans un déclencheur.

8
Erik Darling

Vous pouvez lire sur Colonnes calculées persistantes dans BOL , et les Index sur les colonnes calculées .

Il existe des restrictions sur l'expression que vous pouvez utiliser dans une colonne calculée persistante. L'expression "doit être déterministe lorsque PERSISTED est spécifié."

Si je comprends bien, vous avez:

  1. Une table, appelons-la t.
  2. La table t a une colonne guid, appelons-la gc.
  3. Une table supplémentaire est une recherche mappant les valeurs dans t.gc à une valeur de clé différente d'un type différent, qui est utilisée dans le code hérité. Appelons la table lt et la colonne de clé héritée lk.
  4. Tu souhaites lt.lk pour apparaître dans t en tant que t.lk afin que le code hérité puisse continuer à l'utiliser.

J'enquêterais en utilisant une vue.

  1. Renommez t, quelque chose comme t_base.
  2. Créez une vue nommée t qui rejoint t_base à lt et renvoie la colonne de t_base et lt.lk.
  3. S'il doit persister, examinez les vues indexées. (Nécessite l'édition entreprise et comporte de nombreuses restrictions, mais convient probablement à cette jointure.)
0
Shannon Severance

Le vrai problème

Le problème que les développeurs tentent de résoudre est un problème de migration assez standard, d'un type de données à un nouveau. Ils ont un nouveau problème qui n'était pas prévu au moment où la base de données a été initialement conçue; à savoir, ils doivent maintenant synchroniser les données entre les bases de données. Cela a tendance à être une entreprise coûteuse, en particulier si les types de données sont utilisés dans de nombreux codes. La recherche de mesures d'économie n'est pas totalement déraisonnable.

Leur solution

Les mathématiques disent non

Tout d'abord, il est important de réaliser que le problème est probablement mathématiquement insoluble. Un GUID ou UUID est simplement un nombre de 128 bits ou 16 octets. La taille d'une colonne IDENTITY dépend du type de données utilisé, mais elles sont généralement INT (32 bits, 4 octets); parfois BIGINT (64 bits, 8 octets) est utilisé. Il n'existe aucun moyen mathématique de mapper complètement la plage des GUID à la plage des INT ou même des BIGINT. C'est mathématiquement possible si la colonne est DECIMAL(38,0), ce qui est assez grand, mais c'est très rare.

Aspect pratique

Même s'il est possible de mapper des GUID au type DECIMAL, cela ne signifie pas que ce soit pratique. Pratiquement personne ne fait cela, vous devrez donc passer du temps (= argent) à vous assurer que le mappage fonctionne correctement. Leur solution présente un risque non négligeable de créer des bugs étranges et difficiles à diagnostiquer.

De plus, vous n'allez pas conserver les ID existants des données avec leur solution. Cela pourrait bien briser tous les signets que les utilisateurs finaux ont, y compris les ID.

Enfin, leur solution est susceptible d'être enracinée . Ce n'est pas une bonne pratique pour toutes les raisons ci-dessus, mais s'ils l'obtiennent, il est probable qu'ils ne travailleront pas à s'en éloigner de sitôt car il est trop "facile" de continuer à utiliser les clés entières dans toutes les nouvelles code.

Une approche plus standard

Une approche relativement standard pour introduire la synchronisation avec un système existant consiste à ajouter un nouveau ID unique . Ce nouvel ID est placé dans les données en plus des anciennes clés existantes. Les clés de substitution ne sont alors pas synchronisées.

Cela présente des avantages majeurs:

  • Résout le problème d'activation de la synchronisation entre les bases de données.
  • Le code existant qui repose sur les anciennes clés n'a pas à changer. (Certainement pas à court terme, et peut-être jamais.)
  • Pas de mappages mathématiquement impossibles.
  • Les valeurs de clés existantes sont conservées.

Il y a deux ennuis mineurs avec cette approche:

  • Étant donné que les clés de substitution ne sont pas synchronisées, si les clés de substitution apparaissent dans le code et en particulier dans l'application, ces ID seront différents selon les différentes bases de données. Pour votre situation spécifique (synchronisation pour tester et développer des copies de la base de données, plutôt qu'une sorte de réplication sur plusieurs bases de données de production), ce n'est qu'un inconvénient mineur. Cependant, c'est aussi un problème qui peut être résolu: à mesure que le besoin devient plus évident à travers les inefficacités que cela crée, les développeurs peuvent ajuster des morceaux de code spécifiques et ciblés pour utiliser le nouvel ID de synchronisation selon les besoins , sans réécrire l'intégralité de l'application. Ils pourraient même potentiellement prendre en charge les deux identifiants pendant un certain temps. (Par exemple, un point de terminaison Web peut accepter une clé entière, puis rediriger vers la clé GUID pour garantir que les signets ne sont pas rompus.) Cela devient également le motif de la migration lente de l'utilisation de l'entier clés dans le code.
  • Le code de synchronisation peut devoir mapper des ID de substitution entiers lors de la synchronisation des données avec une clé étrangère. Ce n'est cependant pas un problème insoluble. Il vous suffit de rechercher l'ID de synchronisation associé et de l'utiliser pour trouver la clé de substitution d'entier de la base de données de destination. Cependant, il semble que votre équipe de développement soit déjà prête à passer les clés étrangères des clés de substitution existantes aux nouvelles clés GUID, de toute façon, donc cela peut ne pas être un problème du tout.

Ces deux problèmes sont gérables, cependant, et ce sont des compromis raisonnables à faire si le basculement de tout vers les GUID est actuellement trop cher.

Il convient également de noter que cette solution a activé exactement la requête qu'ils demandent d'être en mesure de faire.

C'est peut-être trop tard pour vous aider maintenant, mais je pense que ce sont de bonnes informations pour l'avenir.

0
jpmc26