I mit sidste indlæg, "En måde at få et indekssøgning efter et førende jokertegn," nævnte jeg, at du ville have brug for triggere for at håndtere vedligeholdelsen af de fragmenter, jeg anbefalede. Et par personer har kontaktet mig for at spørge, om jeg kunne demonstrere disse triggere.
For at forenkle fra det forrige indlæg, lad os antage, at vi har følgende tabeller – et sæt af virksomheder og derefter en CompanyNameFragments-tabel, der tillader pseudo-wildcard-søgning mod enhver understreng af firmanavnet:
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);
Givet denne funktion til at generere fragmenter (den eneste ændring fra den originale artikel er, at jeg har øget @input
for at understøtte 100 tegn):
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
Vi kan oprette en enkelt trigger, der kan håndtere alle tre operationer:
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
Dette fungerer uden at kontrollere, hvilken type operation der skete, fordi:
- For en OPDATERING eller en SLETT sker SLET – for en OPDATERING vil vi ikke bryde os om at prøve at matche fragmenter, der forbliver de samme; vi skal bare blæse dem alle sammen, så de kan udskiftes i massevis. For en INSERT vil DELETE-sætningen ikke have nogen effekt, fordi der ikke vil være nogen rækker i
deleted
. - For en INSERT eller en OPDATERING vil INSERT ske. For en DELETE vil INSERT-sætningen ikke have nogen effekt, fordi der ikke vil være nogen rækker i
inserted
.
Nu, bare for at sikre, at det virker, lad os udføre nogle ændringer i Companies
bord og derefter inspicere vores to borde.
-- 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;
Figur 1: Indledende tabelindhold | Figur 2: Tabelindhold efter opdatering | Figur 3: Tabelindhold efter sletning |
Advarsel (til de referentielle integritetsfolk)
Bemærk, at hvis du opsætter korrekte fremmednøgler mellem disse to tabeller, bliver du nødt til at bruge en i stedet for trigger for at håndtere sletninger, ellers vil du have et kylling og æg problem – du kan ikke vente til *efter* forælderen række slettes for at fjerne underordnede rækker. Så du bliver nødt til at konfigurere ON DELETE CASCADE
(hvilket jeg personligt ikke plejer at kunne lide), ellers ville dine to triggere se sådan ud (efter-triggeren skulle stadig udføre et DELETE/INSERT-par i tilfælde af en OPDATERING):
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
Oversigt
Dette indlæg havde til formål at vise, hvor nemt det er at konfigurere triggere, der forbliver søgelige strengfragmenter for at forbedre jokertegnsøgninger, i det mindste for strenge af moderat størrelse. Nu ved jeg stadig, at denne slags kommer til at virke som en skør idé, men jeg bliver ved med at tale om det, fordi jeg er overbevist om, at der er gode use cases derude.
I mit næste indlæg vil jeg vise, hvordan du kan se virkningen af dette valg:Du kan nemt opsætte repræsentative arbejdsbelastninger for at sammenligne ressourceomkostningerne ved at vedligeholde fragmenterne med ydeevnebesparelserne på forespørgselstidspunktet. Jeg vil se på varierende strenglængder samt forskellige workload balancer (for det meste læs vs. mest skriv) og forsøge at finde sweet spots og farezoner.