At søge i strengdata efter et vilkårligt understrengmatch kan være en dyr operation i SQL Server. Forespørgsler i formen Column LIKE '%match%'
kan ikke bruge søgeevnerne i et b-træ-indeks, så forespørgselsprocessoren skal anvende prædikatet til hver række individuelt. Derudover skal hver test anvende det fulde sæt af komplicerede sorteringsregler korrekt. Når alle disse faktorer kombineres, er det ingen overraskelse, at disse typer søgninger kan være ressourcekrævende og langsomme.
Full-Text Search er et kraftfuldt værktøj til sproglig matchning, og den nyere statistisk semantisk søgning er fantastisk til at finde dokumenter med lignende betydninger. Men nogle gange skal du virkelig bare finde strenge, der indeholder en bestemt understreng – en understreng, der måske ikke engang er et ord, på et hvilket som helst sprog.
Hvis de søgte data ikke er store, eller krav til svartid ikke er kritiske, skal du bruge LIKE '%match%'
kunne godt være en passende løsning. Men ved den underlige lejlighed, hvor behovet for en superhurtig søgning slår alle andre overvejelser (inklusive lagerplads), kan du overveje en tilpasset løsning, der bruger n-gram. Den specifikke variation, der undersøges i denne artikel, er et trigram med tre tegn.
Jokertegnsøgning ved hjælp af trigrammer
Den grundlæggende idé med en trigramsøgning er ret enkel:
- Bevar tre-tegns understrenge (trigrammer) af måldataene.
- Opdel søgeterm(er) i trigrammer.
- Match søgetrigrammer mod de lagrede trigrammer (lighedssøgning)
- Skær de kvalificerede rækker for at finde strenge, der matcher alle trigrammer
- Anvend det originale søgefilter på det meget reducerede vejkryds
Vi vil gennemgå et eksempel for at se præcis, hvordan det hele fungerer, og hvad afvejningen er.
Eksempeltabel og data
Scriptet nedenfor opretter en eksempeltabel og udfylder den med en million rækker af strengdata. Hver streng er 20 tegn lang, hvor de første 10 tegn er numeriske. De resterende 10 tegn er en blanding af tal og bogstaver fra A til F, genereret ved hjælp af NEWID()
. Der er ikke noget frygteligt specielt ved disse prøvedata; trigramteknikken er ret generel.
-- TesttabellenCREATE TABLE dbo.Example ( id integer IDENTITY NOT NULL, string char(20) NOT NULL, CONSTRAINT [PK dbo.Example (id)] PRIMÆR NØGLE KLYNGET (id));GO-- 1 million rowsINSERT dbo.Eksempel MED (TABLOCKX) (streng)SELECT TOP (1 * 1000 * 1000) -- 10 numeriske tegn REPLACE(STR(RAND(CHECKSUM(NEWID())) * 1e10, 10), MELLEMRUM(1), ' 0') + -- plus 10 blandede numeriske + [A-F] tegn HØJRE(NEWID(), 10)FRA master.dbo.spt_values AS SV1CROSS JOIN master.dbo.spt_values AS SV2OPTION (MAXDOP 1);
Det tager omkring 3 sekunder at oprette og udfylde dataene på min beskedne bærbare computer. Dataene er pseudotilfældige, men som en indikation vil de se nogenlunde således ud:
Dataeksempel
Generering af trigrammer
Følgende inline-funktion genererer distinkte alfanumeriske trigrammer fra en given inputstreng:
--- Generer trigrammer fra en strengCREATE FUNCTION dbo.GenerateTrigrams (@string varchar(255))RETURNER tabel MED SCHEMABINDINGAS RETURNER MED N16 AS ( VÆLG V.v FRA ( VALUES (0),(0),(0),(0) ),(0),(0),(0),(0), (0),(0),(0),(0),(0),(0),(0),(0) ) AS V (v)), -- Taltabel (256) Tal AS ( SELECT n =ROW_NUMBER() OVER (ORDER BY A.v) FRA N16 AS A CRODS JOIN N16 AS B ), Trigrammer AS ( -- Hver 3-tegns understreng SELECT TOP (CASE WHEN LEN(@string)> 2 THEN LEN(@string) - 2 ELSE 0 END) trigram =SUBSTRING(@string, N.n, 3) FROM Nums AS N ORDER BY N.n ) -- Fjern dubletter og sørg for, at alle tre tegn er alfanumeriske VÆLG DISTINKT T.trigram FRA Trigrammer SOM T HVOR -- Binær sorteringssammenligning så r anges fungerer som forventet T.trigram COLLATE Latin1_General_BIN2 IKKE SOM '%[^A-Z0-9a-z]%';
Som et eksempel på dets brug, følgende opkald:
VÆLG GT.trigramFROM dbo.GenerateTrigrams('SQLperformance.com') AS GT;
Producerer følgende trigrammer:
SQLperformance.com-trigrammer
Udførelsesplanen er en ret direkte oversættelse af T-SQL i dette tilfælde:
- Generering af rækker (krydssammenføjning af konstante scanninger)
- Rækkenummerering (segment- og sekvensprojekt)
- Begrænsning af de nødvendige tal baseret på længden af strengen (øverst)
- Fjern trigrammer med ikke-alfanumeriske tegn (Filter)
- Fjern dubletter (Distinct Sort)
Plan for generering af trigrammer
Indlæser trigrammerne
Det næste trin er at fortsætte trigrammer for eksempeldataene. Trigrammerne vil blive holdt i en ny tabel, udfyldt ved hjælp af den inline-funktion, vi lige har oprettet:
-- Trigrammer for eksempel tabelCREATE TABLE dbo.ExampleTrigrams( id integer NOT NULL, trigram char(3) NOT NULL);GO-- Generate trigramsINSERT dbo.ExampleTrigrams WITH (TABLOCKX) (id, trigram)SELECT E.id, GT.trigramFROM dbo.Eksempel AS ECROSS APPLY dbo.GenerateTrigrams(E.string) AS GT;
Det tager omkring 20 sekunder til at udføre på min SQL Server 2016 bærbare instans. Denne særlige kørsel producerede 17.937.972 rækker af trigrammer for de 1 million rækker af 20-karakters testdata. Udførelsesplanen viser i det væsentlige den funktionsplan, der evalueres for hver række i eksempeltabellen:
Befolkning af trigramtabellen
Da denne test blev udført på SQL Server 2016 (indlæser en heap-tabel, under databasekompatibilitetsniveau 130 og med en TABLOCK
tip), har planen fordel af parallel indsættelse. Rækker fordeles mellem tråde ved den parallelle scanning af eksempeltabellen og forbliver på den samme tråd derefter (ingen ompartitioneringsudveksling).
Sorteringsoperatoren ser måske lidt imponerende ud, men tallene viser det samlede antal rækker, der er sorteret over alle iterationer af den indlejrede løkkesammenføjning. Faktisk er der en million separate sorter med 18 rækker hver. Ved en grad af parallelitet på fire (to kerner hyperthreaded i mit tilfælde), er der maksimalt fire små sorter, der foregår på ethvert tidspunkt, og hver sorteringsinstans kan genbruge hukommelse. Dette forklarer, hvorfor den maksimale hukommelsesbrug af denne eksekveringsplan er blot 136 KB (selvom der blev bevilget 2.152 KB).
Trigramtabellen indeholder en række for hvert særskilt trigram i hver kildetabelrække (identificeret ved id
):
Eksempel på trigramtabel
Vi opretter nu et klynget b-træ-indeks for at understøtte søgning efter trigram-matches:
-- Trigram-søgningsindeks OPRET UNIKT KLUSTERET INDEX [CUQ dbo.ExampleTrigrams (trigram, id)]ON dbo.ExampleTrigrams (trigram, id)WITH (DATA_COMPRESSION =ROW);
Dette tager omkring 45 sekunder , selvom noget af det skyldes den slags spild (min instans er begrænset til 4 GB hukommelse). En forekomst med mere tilgængelig hukommelse kunne formentlig fuldføre den minimalt loggede parallelle indeksbygning en del hurtigere.
Indeksbyggeplan
Bemærk, at indekset er angivet som unikt (ved at bruge begge kolonner i nøglen). Vi kunne have oprettet et ikke-unikt klynget indeks på trigrammet alene, men SQL Server ville alligevel have tilføjet 4-byte uniquifiers til næsten alle rækker. Når vi først har taget højde for, at uniquifiers er gemt i den variabel-længde del af rækken (med den tilhørende overhead), giver det bare mere mening at inkludere id
i nøglen og være færdig med det.
Rækkekomprimering er angivet, fordi den med fordel reducerer størrelsen af trigramtabellen fra 277 MB til 190 MB (til sammenligning er eksempeltabellen 32MB). Hvis du ikke i det mindste bruger SQL Server 2016 SP1 (hvor datakomprimering blev tilgængelig for alle udgaver), kan du om nødvendigt udelade komprimeringsklausulen.
Som en sidste optimering vil vi også lave en indekseret visning over trigramtabellen for at gøre det hurtigt og nemt at finde, hvilke trigrammer der er de mest og mindst almindelige i dataene. Dette trin kan udelades, men anbefales for ydeevne.
-- Selektivitet for hvert trigram (performanceoptimering)OPRET VISNING dbo.ExampleTrigramCountsWITH SCHEMABINDINGASSELECT ET.trigram, cnt =COUNT_BIG(*)FROM dbo.ExampleTrigrams AS ETGROUP BY ET.trigram;GOREED UNIQUE IND. CUQ dbo.ExampleTrigramCounts (trigram)]ON dbo.ExampleTrigramCounts (trigram);
Bygningsplan med indeksvisning
Dette tager kun et par sekunder at fuldføre. Størrelsen af den materialiserede visning er lille, kun 104 KB .
Trigramsøgning
Givet en søgestreng (f.eks. '%find%this%'
), vil vores tilgang være at:
- Generer det komplette sæt trigrammer til søgestrengen
- Brug den indekserede visning til at finde de tre mest selektive trigrammer
- Find de id'er, der matcher alle tilgængelige trigrammer
- Hent strengene efter id
- Anvend det fulde filter på de trigram-kvalificerede rækker
Find selektive trigrammer
De første to trin er ret ligetil. Vi har allerede en funktion til at generere trigrammer for en vilkårlig streng. At finde de mest selektive af disse trigrammer kan opnås ved at slutte sig til den indekserede visning. Følgende kode omslutter implementeringen af vores eksempeltabel i en anden inline-funktion. Det drejer de tre mest selektive trigrammer i en enkelt række for at gøre det lettere at bruge senere:
-- Mest selektive trigrammer for en søgestreng-- Returnerer altid en række (NULL, hvis ingen trigrammer findes)CREATE FUNCTION dbo.Example_GetBestTrigrams (@string varchar(255))RETURNERER tabel MED SKEMABINDING ASRETURN SELECT -- Pivot trigram1 =MAX CASE WHEN BT.rn =1 THEN BT.trigram END), trigram2 =MAX(CASE WHEN BT.rn =2 THEN BT.trigram END), trigram3 =MAX(CASE WHEN BT.rn =3 THEN BT.trigram END) FRA ( -- Generer trigrammer for søgestrengen -- og vælg de mest selektive tre SELECT TOP (3) rn =ROW_NUMBER() OVER ( ORDER BY ETC.cnt ASC), GT.trigram FRA dbo.GenerateTrigrams(@string) AS GT JOIN dbo.ExampleTrigramCounts AS ETC MED (NOEXPAND) ON ETC.trigram =GT.trigram ORDER BY ETC.cnt ASC ) AS BT;
Som et eksempel:
VÆLG EGBT.trigram1, EGBT.trigram2, EGBT.trigram3 FRA dbo.Example_GetBestTrigrams('%1234%5678%') SOM EGBT;
returnerer (for mine eksempeldata):
Udvalgte trigrammer
Udførelsesplanen er:
GetBestTrigrams eksekveringsplan
Dette er den velkendte trigramgenererende plan fra tidligere, efterfulgt af et opslag i den indekserede visning for hvert trigram, sortering efter antallet af matches, nummerering af rækkerne (Sequence Project), begrænsning af sættet til tre rækker (Top) og derefter pivotering resultatet (Stream Aggregate).
Find ID'er, der matcher alle trigrammer
Det næste trin er at finde eksempler på tabelrække-id'er, der matcher alle trigrammer, der ikke er nul, hentet af det forrige trin. Rynken her er, at vi muligvis har nul, et, to eller tre trigrammer til rådighed. Følgende implementering omslutter den nødvendige logik i en multi-sætningsfunktion og returnerer de kvalificerende id'er i en tabelvariabel:
-- Returnerer eksempel-id'er, der matcher alle angivne (ikke-nul) trigrammerCREATE FUNCTION dbo.Example_GetTrigramMatchIDs( @Trigram1 char(3), @Trigram2 char(3), @Trigram3 char(3))RETURNS @IDs table (id-heltal) PRIMÆR NØGLE)MED SKEMABINDING ASBEGIN HVIS @Trigram1 IKKE ER NULL BEGIN HVIS @Trigram2 IKKE ER NULL BEGIN HVIS @Trigram3 IKKE ER NULL BEGIN -- 3 tilgængelige trigrammer INDSÆT @ID'er (id) VÆLG ET1.id FRA dbo.ExampleTrigrams ASET11 .trigram =@Trigram1 SKÆR VÆLG ET2.id FRA dbo.ExampleTrigrams AS ET2 WHERE ET2.trigram =@Trigram2 INTERSECT SELECT ET3.id FROM dbo.ExampleTrigrams AS ET3 WHERE ET3.trigram =@Trigram3 OPTION); ENDE; ELSE BEGIN -- 2 tilgængelige trigrammer INSERT @IDs (id) SELECT ET1.id FROM dbo.ExampleTrigrams AS ET1 WHERE ET1.trigram =@Trigram1 INTERSECT SELECT ET2.id FROM dbo.ExampleTrigrams AS ET2 WHERE ET2.trigram OPTION =@Tri FLET JOIN); ENDE; ENDE; ELSE BEGIN -- 1 trigram tilgængeligt INSERT @IDs (id) SELECT ET1.id FROM dbo.ExampleTrigrams AS ET1 WHERE ET1.trigram =@Trigram1; ENDE; ENDE; RETURN;END;
Den estimerede udførelsesplan for denne funktion viser strategien:
Trigram match-id-plan
Hvis der er et trigram tilgængeligt, udføres en enkelt søgning i trigramtabellen. Ellers udføres to eller tre søgninger, og skæringspunktet mellem id'er findes ved hjælp af en effektiv én-til-mange-fletning(er). Der er ingen hukommelseskrævende operatører i denne plan, så ingen chance for hash eller sortering.
Hvis vi fortsætter eksempelsøgningen, kan vi finde id'er, der matcher de tilgængelige trigrammer ved at anvende den nye funktion:
VÆLG EGTMID.id FRA dbo.Example_GetBestTrigrams('%1234%5678%') SOM EGBTCROSS ANVENDELSE dbo.Example_GetTrigramMatchIDs (EGBT.trigram1, EGBT.trigram2, EGBT.trigram3) SOM EGTMID;>Dette returnerer et sæt som følgende:
Matchende id'er
Den faktiske (efter-udførelse) plan for den nye funktion viser planformen med tre trigram-input, der bruges:
Faktisk ID-matchningsplan
Dette viser kraften ved trigram-matchning ganske godt. Selvom alle tre trigrammer hver identificerer omkring 11.000 rækker i eksempeltabellen, reducerer det første skæringssæt dette sæt til 1.004 rækker, og det andet skæringspunkt reducerer det til kun 7 .
Fuldfør implementering af trigramsøgning
Nu hvor vi har id'erne, der matcher trigrammerne, kan vi slå de matchende rækker op i eksempeltabellen. Vi skal stadig anvende den oprindelige søgebetingelse som en sidste kontrol, fordi trigrammer kan generere falske positiver (men ikke falske negativer). Det sidste problem at tage fat på er, at de foregående faser muligvis ikke har fundet nogen trigrammer. Det kan for eksempel skyldes, at søgestrengen indeholder for lidt information. En søgestreng på
'%FF%'
kan ikke bruge trigramsøgning, fordi to tegn ikke er nok til at generere et enkelt trigram. For at håndtere dette scenarie elegant, vil vores søgning opdage denne tilstand og falde tilbage til en ikke-trigramsøgning.Den følgende sidste inline-funktion implementerer den nødvendige logik:
-- SøgeimplementeringCREATE FUNCTION dbo.Example_TrigramSearch( @Search varchar(255))RETURNER tableWITH SCHEMABINDINGASRETURN SELECT Result.string FROM dbo.Example_GetBestTrigrams(@Search) AS GBT CROSS APPLY ( -- Trigramsøgning, ESELECT. streng FRA dbo.Example_GetTrigramMatchIDs (GBT.trigram1, GBT.trigram2, GBT.trigram3) AS MID JOIN dbo.Example AS E ON E.id =MID.id WHERE -- Mindst ét trigram fundet GBT.trigram1 ER IKKE NULL AND E .string LIKE @Search UNION ALL -- Ikke-trigramsøgning VÆLG E.id, E.string FRA dbo.Eksempel AS E WHERE -- Intet trigram fundet GBT.trigram1 ER NULL OG E.string LIKE @Search ) SOM Resultat;Nøglefunktionen er den ydre reference til
GBT.trigram1
på begge sider afUNION ALL
. Disse oversættes til Filtre med opstartsudtryk i udførelsesplanen. Et opstartsfilter udfører kun sit undertræ, hvis dets tilstand evalueres til sand. Nettoeffekten er, at kun én del af foreningen alle vil blive henrettet, afhængigt af om vi fandt et trigram eller ej. Den relevante del af udførelsesplanen er:Opstartsfiltereffekt
Enten
Example_GetTrigramMatchIDs
funktion vil blive udført (og resultater slået op i eksempel ved hjælp af en søgning på id), eller Clustered Index Scan af eksempel med en resterendeLIKE
prædikat vil køre, men ikke begge dele.Ydeevne
Følgende kode tester ydeevnen af trigramsøgning mod den tilsvarende
LIKE
:SET STATISTICS XML OFFDECLARE @S datetime2 =SYSUTCDATETIME(); SELECT F2.stringFROM dbo.Example AS F2WHERE F2.string LIKE '%1234%5678%'OPTION (MAXDOP 1); SELECT ElapsedMS =DATEDIFF(MILLISEKUND, @S, SYSUTCDATETIME());GOSET STATISTICS XML OFFDECLARE @S datetime2 =SYSUTCDATETIME(); SELECT ETS.stringFROM dbo.Example_TrigramSearch('%1234%5678%') AS ETS; SELECT ElapsedMS =DATEDIFF(MILLISECOND, @S, SYSUTCDATETIME());Disse producerer begge de samme resultatrække(r), men
LIKE
forespørgslen kører i 2100ms , hvorimod trigramsøgningen tager 15ms .Endnu bedre ydeevne er mulig. Generelt forbedres ydeevnen, efterhånden som trigrammerne bliver mere selektive og færre i antal (under maksimum tre i denne implementering). For eksempel:
SET STATISTICS XML OFFDECLARE @S datetime2 =SYSUTCDATETIME(); SELECT ETS.stringFROM dbo.Example_TrigramSearch('%BEEF%') AS ETS; SELECT ElapsedMS =DATEDIFF(MILLISECOND, @S, SYSUTCDATETIME());Denne søgning returnerede 111 rækker til SSMS-gitteret på 4ms .
LIKE
tilsvarende kørte i 1950 ms .Vedligeholdelse af trigrammerne
Hvis måltabellen er statisk, er der åbenbart ikke noget problem at holde basistabellen og den tilhørende trigramtabel synkroniseret. Ligeledes, hvis det ikke kræves, at søgeresultaterne til enhver tid er helt opdaterede, kan en planlagt opdatering af trigramtabellerne fungere godt.
Ellers kan vi bruge nogle ret simple triggere til at holde trigramsøgedataene synkroniseret med de underliggende strenge. Den generelle idé er at generere trigrammer for slettede og indsatte rækker og derefter tilføje eller slette dem i trigramtabellen efter behov. Indsæt-, opdaterings- og sletningstriggerne nedenfor viser denne idé i praksis:
-- Bevar trigrammer efter eksempelindsættelserCREATE TRIGGER MaintainTrigrams_AION dbo.ExampleAFTER INSERTASBEGIN IF @@ROWCOUNT =0 RETURN; IF TRIGGER_NESTLEVEL(@@PROCID, 'AFTER', 'DML')> 1 RETURN; SÆT ANTAL TIL; INDSTIL RÆKKETAL 0; -- Indsæt relaterede trigrammer INSERT dbo.ExampleTrigrams (id, trigram) VÆLG INS.id, GT.trigram FRA Indsat AS INS CROSS APPLY dbo.GenerateTrigrams(INS.string) AS GT;END;-- Bevar trigrammer efter eksempelsletningerCREATE TRIGGER MaintainTrigrams_ADON dbo.ExampleAFTER DELETEASBEGIN IF @@ROWCOUNT =0 RETURN; IF TRIGGER_NESTLEVEL(@@PROCID, 'AFTER', 'DML')> 1 RETURN; SÆT ANTAL TIL; INDSTIL RÆKKETAL 0; -- Slettede relaterede trigrammer SLET ET MED (SERIALISERBAR) FRA Slettet AS DEL CROSS APPLY dbo.GenerateTrigrams(DEL.string) AS GT JOIN dbo.ExampleTrigrams AS ET ON ET.trigram =GT.trigram OG ET.id =DEL.id; SLUT;-- Vedligehold trigrammer efter eksempelopdateringerCREATE TRIGGER MaintainTrigrams_AUON dbo.EksempelEFTER OPDATERINGASBEGIN HVIS @@ROWCOUNT =0 RETURN; IF TRIGGER_NESTLEVEL(@@PROCID, 'AFTER', 'DML')> 1 RETURN; SÆT ANTAL TIL; INDSTIL RÆKKETAL 0; -- Slettede relaterede trigrammer SLET ET MED (SERIALISERBAR) FRA Slettet AS DEL CROSS APPLY dbo.GenerateTrigrams(DEL.string) AS GT JOIN dbo.ExampleTrigrams AS ET ON ET.trigram =GT.trigram OG ET.id =DEL.id; -- Indsæt relaterede trigrammer INSERT dbo.ExampleTrigrams (id, trigram) VÆLG INS.id, GT.trigram FRA Indsat AS INS CROSS APPLY dbo.GenerateTrigrams(INS.string) AS GT;END;Udløserne er ret effektive og vil håndtere både enkelt- og multirækkeændringer (inklusive de flere tilgængelige handlinger, når du bruger en
MERGE
udmelding). Den indekserede visning over trigramtabellen vedligeholdes automatisk af SQL Server, uden at vi behøver at skrive nogen triggerkode.Trigger operation
Kør som et eksempel en sætning for at slette en vilkårlig række fra eksempeltabellen:
-- Enkelt række deleteDELETE TOP (1) dbo.Example;Efterudførelsen (faktisk) eksekveringsplanen inkluderer en post for udløseren efter sletning:
Slet trigger eksekveringsplan
Den gule sektion af planen læser rækker fra de slettede pesudo-tabel, genererer trigrammer for hver slettet eksempelstreng (ved hjælp af den velkendte plan fremhævet med grønt), lokaliserer og sletter derefter de tilknyttede trigramtabelposter. Det sidste afsnit af planen, vist med rødt, tilføjes automatisk af SQL Server for at holde den indekserede visning opdateret.
Planen for indsatsudløseren er yderst ens. Opdateringer håndteres ved at udføre en sletning efterfulgt af en indsættelse. Kør følgende script for at se disse planer og bekræfte, at nye og opdaterede rækker kan findes ved hjælp af trigram-søgefunktionen:
-- Enkelt række insertINSERT dbo.Example (string) VALUES ('SQLPerformance.com'); -- Find den nye rækkeSELECT ETS.stringFROM dbo.Example_TrigramSearch('%perf%') AS ETS; -- Enkelt række opdatering OPDATERING TOP (1) dbo.Eksempel SET string ='12345678901234567890'; -- Multi-row insertINSERT dbo.Eksempel MED (TABLOCKX) (streng)SELECT TOP (1000) REPLACE(STR(RAND(CHECKSUM(NEWID())) * 1e10, 10), MELLEMRUM(1), '0') + HØJRE(NEWID(), 10)FRA master.dbo.spt_values AS SV1; -- Multi-row updateUPDATE TOP (1000) dbo.Eksempel SET string ='12345678901234567890'; -- Søg efter de opdaterede rækkerSELECT ETS.string FROM dbo.Example_TrigramSearch('12345678901234567890') AS ETS;Flet eksempel
Det næste script viser en
MERGE
sætning, der bruges til at indsætte, slette og opdatere eksempeltabellen på én gang:-- MERGE demoDECLARE @MergeData tabel ( id heltal UNIQUE CLUSTERED NULL, operation char(3) NOT NULL, string char(20) NULL); INSERT @MergeData (id, operation, string)VALUES (NULL, 'INS', '11223344556677889900'), -- Indsæt (1001, 'DEL', NULL), -- Slet (2002, 'UPD', '000000000000000'); -- Opdater DECLARE @Actions tabel (action$ nvarchar(10) NOT NULL, old_id heltal NULL, old_string char(20) NULL, new_id heltal NULL, new_string char(20) NULL); FLÉT dbo.Eksempel SOM EUSENDE @MergeData AS MD PÅ MD.id =E.idWHEN MATCHED OG MD.operation ='DEL' SÅ SLETTEHEN MATCHED OG MD.operation ='UPD' SÅ OPDATERES SET E.string =MD.string,NÅR IKKE MATCHED OG MD.operation ='INS' SÅ INDSÆT (string) VALUES (MD.string)OUTPUT $action, Deleted.id, Deleted.string, Inserted.id, Inserted.stringINTO @Actions (action$, old_id, old_string, new_id, ny_streng); VÆLG * FRA @Actions AS A;Outputtet vil vise noget i stil med:
Handlingsoutput
Sidste tanker
Der er måske en vis mulighed for at fremskynde store sletnings- og opdateringsoperationer ved at referere til id'er direkte i stedet for at generere trigrammer. Dette er ikke implementeret her, fordi det ville kræve et nyt ikke-klynget indeks på trigramtabellen, hvilket fordobler den (allerede betydelige) lagerplads, der bruges. Trigramtabellen indeholder et enkelt heltal og en
char(3)
per række; et ikke-klynget indeks på heltalskolonnen ville fåchar(3)
kolonne på alle niveauer (med tilladelse fra det klyngede indeks og behovet for, at indeksnøgler er unikke på hvert niveau). Der er også hukommelsesplads at overveje, da trigramsøgninger fungerer bedst, når alle læsninger er fra cache.Det ekstra indeks ville gøre kaskadende referenceintegritet til en mulighed, men det er ofte mere besvær, end det er værd.
Trigramsøgning er ikke noget universalmiddel. De ekstra lagerkrav, implementeringskompleksiteten og indvirkningen på opdateringsydelsen vejer tungt imod det. Teknikken er også ubrugelig til søgninger, der ikke genererer nogen trigrammer (minimum 3 tegn). Selvom den grundlæggende implementering, der er vist her, kan håndtere mange typer søgning (starter med, indeholder, slutter med flere jokertegn), dækker den ikke alle mulige søgeudtryk, der kan fås til at fungere med
LIKE
. Det fungerer godt for søgestrenge, der genererer AND-type trigrammer; der kræves mere arbejde for at håndtere søgestrenge, der kræver håndtering af OR-typen, eller mere avancerede muligheder som regulære udtryk.Alt det sagt, hvis din ansøgning virkelig skal har hurtige jokertegn-strengsøgninger, er n-gram noget, man seriøst bør overveje.
Relateret indhold:En måde at få et indekssøgning efter et førende %wildcard af Aaron Bertrand.