sql >> Databasteknik >  >> RDS >> Database

Uppföljning #1 på ledande jokerteckensökningar

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.


  1. Hur man gör en databas online från återställningsläge i SQL Server

  2. Vad är motsvarande PostgreSQL-syntax till Oracles CONNECT BY ... BÖRJA MED?

  3. Procedur för att exportera tabell till flera csv-filer

  4. Hur ställer du in autocommit i en SQL Server-session?