I mitt förra inlägg, "Ett sätt att få en indexsökning för ett ledande jokertecken", nämnde jag att du skulle behöva triggers för att hantera att underhålla de fragment som jag rekommenderade. Ett par personer har kontaktat mig för att fråga om jag kunde visa dessa triggers.
För att förenkla från föregående inlägg, låt oss anta att vi har följande tabeller – en uppsättning företag och sedan en CompanyNameFragments-tabell som tillåter pseudo-jokerteckensökning mot vilken delsträng som helst av företagsnamnet:
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);
Med tanke på den här funktionen för att generera fragment (den enda ändringen från den ursprungliga artikeln är att jag har ökat @input
för att stödja 100 tecken):
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 skapa en enda trigger som kan hantera alla tre operationerna:
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
Detta fungerar utan att kontrollera vilken typ av operation som hände eftersom:
- För en UPPDATERING eller en DELETE kommer DELETE att ske – för en UPPDATERING kommer vi inte att bry oss om att försöka matcha fragment som förblir desamma; vi ska bara blåsa bort dem alla, så att de kan bytas ut i massor. För en INSERT kommer DELETE-satsen inte att ha någon effekt, eftersom det inte kommer att finnas några rader i
deleted
. - För en INSERT eller en UPPDATERING kommer INSERT att ske. För en DELETE kommer INSERT-satsen inte att ha någon effekt, eftersom det inte kommer att finnas några rader i
inserted
.
Nu, bara för att se till att det fungerar, låt oss göra några ändringar i Companies
bord och sedan inspektera våra två bord.
-- 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: Inledande tabellinnehåll | Figur 2: Tabellinnehåll efter uppdatering | Figur 3: Tabellinnehåll efter radering |
Varning (för referensintegritet)
Observera att om du ställer in korrekta främmande nycklar mellan dessa två tabeller, måste du använda en istället för trigger för att hantera borttagningar, annars kommer du att få problem med kyckling och ägg – du kan inte vänta till *efter* föräldern rad tas bort för att ta bort underordnade rader. Så du måste ställa in ON DELETE CASCADE
(vilket jag personligen inte brukar gilla), eller så skulle dina två triggers se ut så här (efterutlösaren måste fortfarande utföra ett DELETE/INSERT-par i fallet med en UPPDATERING):
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
Sammanfattning
Det här inlägget syftade till att visa hur lätt det är att ställa in triggers som förblir sökbara strängfragment för att förbättra jokerteckensökningar, åtminstone för medelstora strängar. Nu vet jag fortfarande att den här typen av kommer fram som en knäpp idé, men jag fortsätter att prata om det eftersom jag är övertygad om att det finns bra användningsfall där ute.
I mitt nästa inlägg kommer jag att visa hur man ser effekten av detta val:Du kan enkelt ställa in representativa arbetsbelastningar för att jämföra resurskostnaderna för att underhålla fragmenten mot prestandabesparingarna vid frågetillfället. Jag kommer att titta på olika stränglängder samt olika arbetsbelastningsbalanser (för det mesta läs och mest skriv) och försöker hitta sweet spots och farozoner.