Database
 sql >> Base de données >  >> RDS >> Database

Suivi #1 sur les principales recherches de caractères génériques

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.