In mijn laatste bericht, "Een manier om een index te laten zoeken naar een leidende wildcard," zei ik dat je zou triggers nodig hebben om de door mij aanbevolen fragmenten te onderhouden. Een paar mensen hebben contact met me opgenomen om te vragen of ik die triggers kan demonstreren.
Ter vereenvoudiging van het vorige bericht, laten we aannemen dat we de volgende tabellen hebben:een reeks bedrijven en vervolgens een tabel CompanyNameFragments die pseudo-jokertekens zoeken mogelijk maakt op elke subtekenreeks van de bedrijfsnaam:
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);
Gezien deze functie voor het genereren van fragmenten (de enige verandering ten opzichte van het originele artikel is dat ik @input
heb verhoogd om 100 tekens te ondersteunen):
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
We kunnen een enkele trigger maken die alle drie de bewerkingen aankan:
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
Dit werkt zonder te controleren welk type bewerking is uitgevoerd, omdat:
- Voor een UPDATE of een VERWIJDERING zal de VERWIJDERING plaatsvinden - voor een UPDATE gaan we niet de moeite nemen om fragmenten te matchen die hetzelfde blijven; we gaan ze gewoon allemaal wegblazen, zodat ze massaal kunnen worden vervangen. Voor een INSERT heeft de DELETE-instructie geen effect, omdat er geen rijen zijn in
deleted
. - Voor een INSERT of een UPDATE zal de INSERT plaatsvinden. Voor een DELETE heeft de INSERT-instructie geen effect, omdat er geen rijen zijn in
inserted
.
Laten we nu, om er zeker van te zijn dat het werkt, enkele wijzigingen aanbrengen in de Companies
tafel en inspecteer vervolgens onze twee tafels.
-- 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;
Figuur 1: Initiële tabelinhoud | Figuur 2: Tabelinhoud na update | Figuur 3: Tabelinhoud na verwijderen |
Voorbehoud (voor de referentiële integriteitsmensen)
Merk op dat als je de juiste externe sleutels tussen deze twee tabellen instelt, je een in plaats van een trigger moet gebruiken om verwijderingen af te handelen, anders heb je een kip-en-ei-probleem - je kunt niet wachten tot *na* de ouder rij wordt verwijderd om de onderliggende rijen te verwijderen. U moet dus ON DELETE CASCADE
. instellen (wat ik persoonlijk niet leuk vind), of je twee triggers zouden er zo uitzien (de after-trigger zou nog steeds een DELETE/INSERT-paar moeten uitvoeren in het geval van een UPDATE):
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
Samenvatting
Dit bericht was bedoeld om te laten zien hoe gemakkelijk het is om triggers in te stellen die zoekbaar blijven tekenreeksfragmenten om zoekopdrachten met jokertekens te verbeteren, in ieder geval voor tekenreeksen van gemiddelde grootte. Nu weet ik nog steeds dat dit een gek idee overkomt, maar ik blijf erover praten omdat ik ervan overtuigd ben dat er goede gebruiksscenario's zijn.
In mijn volgende bericht zal ik laten zien hoe u de impact van deze keuze kunt zien:u kunt eenvoudig representatieve workloads instellen om de resourcekosten voor het onderhouden van de fragmenten te vergelijken met de prestatiebesparingen op het moment van query's. Ik zal kijken naar verschillende snaarlengtes en verschillende werklastbalansen (meestal lezen versus meestal schrijven) en proberen goede plekken en gevarenzones te vinden.