2010-06-02 5 views
1

Dans ma base de données, supposons que nous avons une table définie comme suit:SQL Server 2005, les index larges, des colonnes calculées et des requêtes sargable

CREATE TABLE [Chemical](
    [ChemicalId] int NOT NULL IDENTITY(1,1) PRIMARY KEY, 
    [Name] nvarchar(max) NOT NULL, 
    [Description] nvarchar(max) NULL 
) 

La valeur de nom peut être très grand, donc nous devons utiliser nvarchar (max). Malheureusement, nous voulons créer un index sur cette colonne, mais nvarchar (max) n'est pas supporté dans un index.

Nous créons donc la colonne calculée suivant et index associé en fonction il:

ALTER TABLE [Chemical] 
ADD [Name_Indexable] AS LEFT([Name], 20) 

CREATE INDEX [IX_Name] 
ON [Chemical]([Name_Indexable]) 
INCLUDE([Name]) 

L'indice ne sera pas unique, mais nous pouvons garantir l'unicité via un déclencheur.

Si nous effectuons la requête suivante, les résultats du plan d'exécution dans un index scan, ce qui est pas ce que nous voulons:

SELECT [ChemicalId], [Name], [Description] 
FROM [Chemical] 
WHERE [Name]='[1,1''-Bicyclohexyl]-2-carboxylic acid, 4'',5-dihydroxy-2'',3-dimethyl-5'',6-bis[(1-oxo-2-propen-1-yl)oxy]-, methyl ester' 

Cependant, si nous modifions la requête pour le faire « sargable, » alors ce que les résultats du plan d'exécution dans un indice cherchent, qui est que nous voulons:

SELECT [ChemicalId], [Name], [Description] 
FROM [Chemical] 
WHERE [Indexable_Name]='[1,1''-Bicyclohexyl]-' AND [Name]='[1,1''-Bicyclohexyl]-2-carboxylic acid, 4'',5-dihydroxy-2'',3-dimethyl-5'',6-bis[(1-oxo-2-propen-1-yl)oxy]-, methyl ester' 

est-ce une bonne solution si nous contrôlons le format de toutes les requêtes exécutées sur le base de données via notre niveau intermédiaire? Y a-t-il un meilleur moyen? Est-ce un kludge majeur? Devrions-nous utiliser l'indexation de texte intégral?

+0

[Nom] est un nom chimique réel, pas une liste délimitée par des virgules, désolé pour la confusion! – luksan

+0

petite remarque: vous créez une colonne calculée [Name_Indexable] 'et utilisez plus tard' SELECT ... WHERE [Indexable_Name] = ... '. Il devrait être changé en '[Name_Indexable]'. – Oleg

Répondre

1

À mon humble avis, oui, je pense que c'est une mauvaise approche. Si vous saviez que les 20 premiers caractères seront uniques, alors il devrait s'agir d'une colonne de première classe avec une contrainte unique. Si vous souhaitez améliorer la recherche dans la colonne Nom, l'utilisation de la recherche en texte intégral est la meilleure solution. Si vous voulez vous assurer que la colonne varchar (max) est unique, créez une colonne calculée qui génère un hachage de la valeur et placez une contrainte unique sur celle-ci.

Alter Table Add NameHash Hashbytes('SHA1', [Name]) 

AJOUT

Compte tenu de notre discussion, si vos recherches vont toujours être sur une correspondance exacte, vous pouvez hachage votre paramètre de recherche et le comparer à NameHash ci-dessus. Cependant, le résultat est que la correspondance doit être une correspondance exacte (c'est-à-dire sensible à la casse).

Je suis toujours satisfait que le FTS sera votre meilleur pari. Même s'il y a des frais généraux dans la décomposition de votre texte en mots, FTS est l'outil le mieux conçu pour effectuer des recherches sur de grandes quantités de texte. Plus vos critères de recherche sont longs, plus la recherche sera rapide.

+0

Oui, nous savons tout sur HASHBYTES. Cependant, nous sommes toujours obligés d'écrire une requête géniale pour exploiter l'index. – luksan

+1

@luksan - Comment ça? Vous n'utiliserez jamais NameHash dans votre recherche. Si vous recherchez sur le nom, vous utiliserez la recherche en texte intégral et qui utilisera certainement l'index (texte intégral). – Thomas

+0

@Thomas Si nous avons activé l'indexation de texte intégral, oui. Le problème est que je ne connais pas les frais généraux, que ce soit optimisé pour un scénario comme celui-ci (correspondant à toute la chaîne par opposition à des mots individuels), qu'il soit aussi rapide qu'un index normal, etc. posté la question. – luksan

1

Avez-vous essayé

WHERE [Name_Indexable]='1,2,3-Propanetriol' 

Après tout ce qui est l'endroit où l'index est créé sur

+0

+1 a du sens pour moi. –

+0

Oui, désolé. J'ai fixé mon exemple pour clarifier ce que j'essaie d'accomplir. – luksan

2

Votre index est sur name_indexable, pas sur name. Puisque name_indexable est généré à partir d'une fonction impliquant name au lieu de directement sur la colonne name, l'optimiseur n'utilisera pas automatiquement l'index lorsque votre clause where inclut une référence à name. Vous devez rechercher sur name_indexable afin d'utiliser l'index. Puisque vous avez un niveau intermédiaire, votre meilleur pari est probablement de fournir une fonction qui recherche sur name_indexable si le nom donné est < = 200 caractères, et recherche autrement sur les deux.

+0

Oui, c'est ce que j'essaie d'éviter cependant. – luksan

+1

Vous pourriez essayer d'utiliser un indice de requête 'WITH (INDEX xxx)'. Je ne suis pas sûr que cela fonctionne ou non avec la façon dont vous calculez la colonne, mais cela vaut la peine d'essayer. – Donnie

+0

idée intéressante, entraîne toujours la même requête si. En fait, l'index est utilisé dans la requête d'origine, mais il effectue un scan plutôt qu'une recherche. – luksan

2

Faites de la colonne Name_Index une colonne calculée persistante et la clé primaire et appliquez l'unicité en ajoutant l'ID chimique au lieu de s'appuyer sur des déclencheurs.

CREATE TABLE dbo.[Chemical] 
    ([ChemicalId] int NOT NULL IDENTITY(1,1), 
    [Name] Nvarchar(max) NOT NULL, 
    [Description] Nvarchar(max) NOT NULL, 
    [Name_Index] AS (CONVERT(VARCHAR(20), LEFT([Name], 20)) + CONVERT(VARCHAR(20), [ChemicalId])) PERSISTED PRIMARY KEY); 
+0

Si le faire persister permet à l'index d'être utilisé, bon. Sinon, peut-être que vous devriez mordre la balle et en faire une colonne "à temps plein". –

+0

Cela garantirait que toute valeur insérée dans Name (même en double) ne pourrait jamais violer la contrainte unique, puisque l'identifiant sera toujours unique. – luksan

+0

L'objectif n'est pas vraiment d'en faire la clé primaire, nous aimons nos clés d'identité.J'ai vérifié le plan de requête, votre solution ne nous permet toujours pas de provoquer une recherche d'index sans spécifier explicitement [Name_Index] dans la clause where. – luksan

0

Fixez votre modèle de données. Vous avez une liste délimitée par des virgules dans la colonne de nom, pour moi cela signifie que vous feriez mieux d'interroger si vous aviez une table liée. Votre nom semble être une liste d'ingrédients pas un nom.

Si c'est vraiment un vrai nom, alors l'utilisateur enregistré a un bon plan.

+0

Ce n'est pas une liste délimitée par des virgules, c'est un nom chimique. Vraiment. Je ne peux pas faire ce genre de choses. – luksan

1

Je trouve votre solution de la question (la dernière requête) très bonne, mais personnellement, je préfère dire SQL plus exactement quoi et comment je veux faire. Donc, si vous fonctionne avec Microsoft SQL Server ou avec un autre SQL Server qui prend en charge les CTE (expression de table commune) vous pouvez réécrire votre requête comme suit:

DECLARE @data nvarchar(max); 

SET @data = '[1,1''-Bicyclohexyl]-2-carboxylic acid, 4'',5-dihydroxy-2'',3-dimethyl-5'',6-bis[(1-oxo-2-propen-1-yl)oxy]-, methyl ester'; 

WITH ReduceData ([ChemicalId], [Name], [Description]) AS (
    SELECT [ChemicalId], [Name], [Description] 
    FROM [dbo].[Chemical] 
    WHERE [Name_Indexable]=LEFT(@data,20) 
) 
SELECT [ChemicalId], [Name], [Description] 
FROM ReduceData 
WHERE [Name][email protected] 

(Dans la mise en œuvre réelle, vous n'avez probablement pas besoin de définir @data Au lieu de cela, vous pouvez simplement utiliser une requête paramétrée.). Ce que je suggère est juste de dire SQL plus explicite ce que vous voulez. Toutes les requêtes CTE peuvent être très bien optimisées.

Il se peut que votre requête d'origine soit compilée avec le même plan d'exécution que ma version CTE. Vous pouvez regarder les deux plans et comparer là-bas. Dans votre projet, vous avez probablement des requêtes beaucoup plus complexes à partir de votre question. Si vous utilisez plus de CTE, votre code SQL sera facile à lire, il peut être très bien optimisé et vous pouvez être sûr que SQL Server fera exactement ce que vous voulez.

MISE À JOUR: Soit dit en passant la ligne

ALTER TABLE [Chemical] 
    ADD [Name_Indexable] AS LEFT([Name], 20) 

devrait être modifiée pour

ALTER TABLE [Chemical] 
    ADD [Name_Indexable] AS CAST(LEFT([Name], 20) AS varchar(20)) PERSISTED 

pour faire un [Name_Indexable] colonne du type varchar(20) sur Microsoft SQL Server 2008 et la marque il PERSISTE pour stocker les valeurs calculées dans la table, et les met à jour quand toutes les autres colonnes dont dépend la colonne calculée sont mises à jour

+0

Je ne pense pas que vous ayez vraiment besoin du CTE (juste ET les deux critères de filtrage), mais l'exemple d'un seul paramètre utilisé deux fois est génial. Je supporte totalement cela et, de plus, vous pouvez l'intégrer dans un UDF paramétré en ligne avec une table afin que l'utilisation de l'index soit appliquée de manière transparente. Évidemment, pour les opérations de jointure basées sur des ensembles, ceci est un peu plus problématique, mais les jointures communes peuvent être représentées avec des vues avec l'utilisation LEFT() appropriée. –

+0

Pourquoi la persistance de la colonne est-elle importante? La colonne sera "persistée" dans l'index, qui est le seul endroit où il sera vraiment nécessaire (ne sera jamais utilisé dans un SELECT). Ne le persistera-t-il pas dans la table? Les nœuds d'index ne seront-ils pas automatiquement mis à jour si la colonne calculée n'est pas persistante? Quel est l'avantage de persister? – luksan

Questions connexes