Dans mon dernier message, "Une façon d'obtenir une recherche d'index pour un caractère générique de début", j'ai mentionné que vous aurait besoin de déclencheurs pour gérer le maintien des fragments que j'ai recommandés. Quelques personnes m'ont contacté pour me demander si je pouvais démontrer ces déclencheurs.
Pour simplifier à partir de l'article précédent, supposons que nous ayons les tables suivantes :un ensemble d'entreprises, puis une table CompanyNameFragments qui permet une recherche par pseudo-générique sur n'importe quelle sous-chaîne du nom de l'entreprise :
CREATE TABLE dbo.Companies ( CompanyID int CONSTRAINT PK_Companies PRIMARY KEY, Name nvarchar(100) NOT NULL ); GO CREATE TABLE dbo.CompanyNameFragments ( CompanyID int NOT NULL, Fragment nvarchar(100) NOT NULL ); CREATE CLUSTERED INDEX CIX_CNF ON dbo.CompanyNameFragments(Fragment, CompanyID);
Étant donné cette fonction pour générer des fragments (le seul changement par rapport à l'article original est que j'ai augmenté @input
pour prendre en charge 100 caractères) :
CREATE FUNCTION dbo.CreateStringFragments( @input nvarchar(100) ) RETURNS TABLE WITH SCHEMABINDING AS RETURN ( WITH x(x) AS ( SELECT 1 UNION ALL SELECT x+1 FROM x WHERE x < (LEN(@input)) ) SELECT Fragment = SUBSTRING(@input, x, LEN(@input)) FROM x ); GO
Nous pouvons créer un seul déclencheur capable de gérer les trois opérations :
CREATE TRIGGER dbo.Company_MaintainFragments ON dbo.Companies FOR INSERT, UPDATE, DELETE AS BEGIN SET NOCOUNT ON; DELETE f FROM dbo.CompanyNameFragments AS f INNER JOIN deleted AS d ON f.CompanyID = d.CompanyID; INSERT dbo.CompanyNameFragments(CompanyID, Fragment) SELECT i.CompanyID, fn.Fragment FROM inserted AS i CROSS APPLY dbo.CreateStringFragments(i.Name) AS fn; END GO
Cela fonctionne sans aucune vérification pour quel type d'opération s'est produit car :
- Pour un UPDATE ou un DELETE, le DELETE se produira - pour un UPDATE, nous n'allons pas nous embêter à essayer de faire correspondre des fragments qui resteront les mêmes ; nous allons juste les faire exploser tous, afin qu'ils puissent être remplacés en masse. Pour un INSERT, l'instruction DELETE n'aura aucun effet, car il n'y aura pas de lignes dans
deleted
. - Pour un INSERT ou un UPDATE, l'INSERT aura lieu. Pour un DELETE, l'instruction INSERT n'aura aucun effet, car il n'y aura pas de lignes dans
inserted
.
Maintenant, juste pour nous assurer que cela fonctionne, apportons quelques modifications aux Companies
table, puis inspectez nos deux tables.
-- First, let's insert two companies -- (table contents after insert shown in figure 1 below) INSERT dbo.Companies(Name) VALUES(N'Banana'), (N'Acme Corp'); -- Now, let's update company 2 to 'Orange' -- (table contents after update shown in figure 2 below): UPDATE dbo.Companies SET Name = N'Orange' WHERE CompanyID = 2; -- Finally, delete company #1 -- (table contents after delete shown in figure 3 below): DELETE dbo.Companies WHERE CompanyID = 1;
Figure 1 : Contenu initial du tableau | Figure 2 : Contenu du tableau après mise à jour | Figure 3 : Contenu du tableau après suppression |
Mise en garde (pour les spécialistes de l'intégrité référentielle)
Notez que si vous configurez des clés étrangères appropriées entre ces deux tables, vous devrez utiliser un déclencheur au lieu de pour gérer les suppressions, sinon vous aurez un problème de poule et d'œuf - vous ne pouvez pas attendre * après * le parent ligne est supprimée pour supprimer les lignes enfants. Vous devrez donc configurer ON DELETE CASCADE
(ce que je n'aime pas personnellement), ou vos deux déclencheurs ressembleraient à ceci (le déclencheur après devrait toujours effectuer une paire DELETE/INSERT dans le cas d'une mise à jour) :
CREATE TRIGGER dbo.Company_DeleteFragments ON dbo.Companies INSTEAD OF DELETE AS BEGIN SET NOCOUNT ON; DELETE f FROM dbo.CompanyNameFragments AS f INNER JOIN deleted AS d ON f.CompanyID = d.CompanyID; DELETE c FROM dbo.Companies AS c INNER JOIN deleted AS d ON c.CompanyID = d.CompanyID; END GO CREATE TRIGGER dbo.Company_MaintainFragments ON dbo.Companies FOR INSERT, UPDATE AS BEGIN SET NOCOUNT ON; DELETE f FROM dbo.CompanyNameFragments AS f INNER JOIN deleted AS d ON f.CompanyID = d.CompanyID; INSERT dbo.CompanyNameFragments(CompanyID, Fragment) SELECT i.CompanyID, fn.Fragment FROM inserted AS i CROSS APPLY dbo.CreateStringFragments(i.Name) AS fn; END GO
Résumé
Ce message visait à montrer à quel point il est facile de configurer des déclencheurs qui resteront recherchables fragments de chaîne pour améliorer les recherches par caractères génériques, au moins pour les chaînes de taille moyenne. Maintenant, je sais toujours que ce genre d'idée semble farfelu, mais je continue d'en parler parce que je suis convaincu qu'il existe de bons cas d'utilisation.
Dans mon prochain article, je montrerai comment voir l'impact de ce choix :vous pouvez facilement configurer des charges de travail représentatives pour comparer les coûts en ressources de la maintenance des fragments par rapport aux économies de performances au moment de la requête. J'examinerai différentes longueurs de chaîne ainsi que différents équilibres de charge de travail (principalement en lecture contre principalement en écriture) et j'essaierai de trouver des zones idéales et des zones de danger.