Index de colonne calculé non utilisé


14

Je veux avoir une recherche rapide basée sur si deux colonnes sont égales. J'ai essayé d'utiliser une colonne calculée avec un index, mais SQL Server ne semble pas l'utiliser. Si j'utilise simplement une colonne de bits à remplissage statique avec un index, j'obtiens la recherche d'index attendue.

Il semble qu'il y ait d'autres questions comme celle-ci, mais aucune ne se concentre sur la raison pour laquelle un index ne serait pas utilisé.

Table de test:

CREATE TABLE dbo.Diffs
    (
    Id int NOT NULL IDENTITY (1, 1),
    DataA int NULL,
    DataB int NULL,
    DiffPersisted  AS isnull(convert(bit, case when [DataA] is null and [DataB] is not null then 1 when [DataA] <> [DataB] then 1 else 0 end), 0) PERSISTED ,
    DiffComp  AS isnull(convert(bit, case when [DataA] is null and [DataB] is not null then 1 when [DataA] <> [DataB] then 1 else 0 end), 0),
    DiffStatic bit not null,
    Primary Key (Id)
    )

create index ix_DiffPersisted on Diffs (DiffPersisted)
create index ix_DiffComp on Diffs (DiffComp)
create index ix_DiffStatic on Diffs (DiffStatic)

Et la requête:

select Id from Diffs where DiffPersisted = 1
select Id from Diffs where DiffComp = 1
select Id from Diffs where DiffStatic = 1

Et les plans d'exécution qui en résultent: Plan d'exécution

Réponses:


10

Essayez avec COALESCEau lieu de ISNULL. Avec ISNULL, SQL Server ne semble pas capable de pousser un prédicat contre l'index plus étroit, et doit donc analyser le cluster pour trouver les informations.

CREATE TABLE dbo.Diffs
    (
    Id int NOT NULL IDENTITY (1, 1),
    DataA int NULL,
    DataB int NULL,
    DiffPersisted  AS COALESCE(convert(bit, case when [DataA] is null 
      and [DataB] is not null then 1 when [DataA] <> [DataB] 
      then 1 else 0 end), 0) PERSISTED ,
    DiffComp  AS COALESCE(convert(bit, case when [DataA] is null 
      and [DataB] is not null then 1 when [DataA] <> [DataB] 
      then 1 else 0 end), 0),
    DiffStatic bit not null,
    Primary Key (Id)
    );

Cela dit, si vous vous en tenez à une colonne statique, un index filtré pourrait avoir plus de sens et aura des coûts d'E / S inférieurs (tout dépend du nombre de lignes correspondant généralement au prédicat de filtre), par exemple:

CREATE INDEX ix_DiffStaticFiltered 
  ON dbo.Diffs(DiffStatic)
  WHERE DiffStatic = 1;

Très intéressant, je n'y aurais pas pensé. Il semble que vous puissiez simplement vous débarrasser de COALESCEce point à ce stade; Je crois que la CASEdéclaration était déjà garantie de retourner 0ou 1, mais ISNULLn'était présente que pour que SQL Server produise une valeur non nulle BITpour la colonne calculée. Cependant, COALESCEproduira toujours une colonne nullable. Ainsi, le seul impact de ce changement, avec ou sans le COALESCE, est que la colonne calculée est désormais nullable mais la recherche d'index peut être utilisée.
Geoff Patterson

@Geoff Oui, c'est vrai. Mais dans ce cas, puisque nous savons que la définition de colonne calculée NULL n'est vraiment pas une sortie possible, cela n'a vraiment d' importance que si nous utilisons cette table comme source d'un SELECT INTO.
Aaron Bertrand

Ce sont des informations incroyables - merci! Mon objectif final est que les colonnes DataA et DataB soient utilisées comme des uuids "sales" pour permettre la mise à jour asynchrone des colonnes dénormalisées sur l'enregistrement, donc il ne devrait pas y en avoir trop où l'indicateur Diff est 1. Si j'utilise le statique champ, alors je pensais à ajouter un déclencheur pour surveiller les deux uuids et mettre à jour le champ.
David Faivre

De plus, comme l'a souligné @GeoffPatterson, ne puis-je pas utiliser le COALESCE? Pourquoi devrais-je le garder?
David Faivre

@David Vous pouvez probablement supprimer le COALESCE. J'ai essayé de conserver l'apparence et l'intention de votre code d'origine, et je n'ai pas testé sans, afin que les tests soient sur vous. (Je ne peux pas non plus expliquer pourquoi vous ISNULLy étiez en premier lieu.)
Aaron Bertrand

5

Il s'agit d'une limitation spécifique de la logique de correspondance de colonne calculée SQL Server, lorsqu'un élément le plus à l'extérieur ISNULLest utilisé et que le type de données de la colonne l'est bit.

Rapport d'erreur

Pour éviter le problème, l'une des solutions de contournement suivantes peut être utilisée:

  1. N'utilisez pas un élément externe ISNULL(le seul moyen de créer une colonne calculée NOT NULL).
  2. N'utilisez pas le bittype de données comme type final de la colonne calculée.
  3. Créez la colonne calculée PERSISTEDet activez l'indicateur de trace 174 .

Détails

Le cœur du problème est que sans l'indicateur de trace 174, toutes les références de colonne calculées dans une requête (même persistées) sont toujours développées dans la définition sous-jacente très tôt dans la compilation de la requête.

L'idée de l'expansion est qu'elle pourrait permettre des simplifications et des réécritures qui ne peuvent fonctionner que sur la définition, pas uniquement sur le nom de la colonne. Par exemple, il peut y avoir des prédicats dans la requête référençant cette colonne calculée qui pourraient rendre une partie du calcul redondante, ou autrement plus contrainte.

Une fois les premières simplifications et réécritures prises en compte, la compilation des requêtes tente de faire correspondre les expressions de la requête aux colonnes calculées (toutes les colonnes calculées, pas seulement celles trouvées à l'origine dans le texte de la requête).

Les expressions de colonne calculées non modifiées correspondent à la colonne calculée d'origine sans problème dans la plupart des cas. Il semble y avoir un bogue lorsqu'il est spécifique à la correspondance d'une expression de bittype, avec un élément le plus à l'extérieur ISNULL. L'appariement échoue dans ce cas spécifique, même lorsqu'un examen détaillé des éléments internes montre qu'il doit réussir.

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.