sql >> Databasteknik >  >> RDS >> Sqlserver

Trigram Wildcard String Search i SQL Server

Att söka efter strängdata efter en godtycklig delsträngsmatchning kan vara en dyr operation i SQL Server. Frågor i formen Column LIKE '%match%' kan inte använda sökförmågan hos ett b-trädindex, så frågeprocessorn måste tillämpa predikatet på varje rad individuellt. Dessutom måste varje test tillämpa hela uppsättningen av komplicerade sorteringsregler korrekt. Om man kombinerar alla dessa faktorer är det ingen överraskning att dessa typer av sökningar kan vara resurskrävande och långsamma.

Full-Text Search är ett kraftfullt verktyg för språklig matchning, och den nyare Statistical Semantic Search är utmärkt för att hitta dokument med liknande betydelser. Men ibland behöver du egentligen bara hitta strängar som innehåller en viss delsträng – en delsträng som kanske inte ens är ett ord, på vilket språk som helst.

Om den sökta informationen inte är stor, eller om svarstidskraven inte är kritiska, använd LIKE '%match%' kan mycket väl vara en lämplig lösning. Men vid ett udda tillfälle där behovet av en supersnabb sökning slår alla andra överväganden (inklusive lagringsutrymme), kan du överväga en anpassad lösning med n-gram. Den specifika varianten som utforskas i den här artikeln är ett trigram med tre tecken.

Jokerteckensökning med trigram

Grundidén med en trigramsökning är ganska enkel:

  1. Bevara tre teckens delsträngar (trigram) av måldata.
  2. Dela upp söktermen/-erna i trigram.
  3. Matcha söktrigram mot de lagrade trigrammen (likhetssökning)
  4. Skär de kvalificerade raderna för att hitta strängar som matchar alla trigram
  5. Använd det ursprungliga sökfiltret på den mycket reducerade korsningen

Vi kommer att arbeta igenom ett exempel för att se exakt hur allt detta fungerar och vilka avvägningar som är.

Exempeltabell och data

Skriptet nedan skapar en exempeltabell och fyller den med en miljon rader med strängdata. Varje sträng är 20 tecken lång, där de första 10 tecknen är numeriska. De återstående 10 tecknen är en blandning av siffror och bokstäver från A till F, genererade med NEWID() . Det är inget särskilt speciellt med denna exempeldata; trigramtekniken är ganska allmän.

-- TesttabellenCREATE TABLE dbo.Example ( id heltal IDENTITY NOT NULL, string char(20) NOT NULL, CONSTRAINT [PK dbo.Example (id)] PRIMARY KEY CLUSTERED (id));GO-- 1 miljon rowsINSERT dbo.Exempel MED (TABLOCKX) (sträng)SELECT TOP (1 * 1000 * 1000) -- 10 numeriska tecken REPLACE(STR(RAND(CHECKSUM(NEWID())) * 1e10, 10), SPACE(1), ' 0') + -- plus 10 blandade numeriska + [A-F] tecken HÖGER(NEWID(), 10)FRÅN master.dbo.spt_values ​​AS SV1CROSS JOIN master.dbo.spt_values ​​AS SV2OPTION (MAXDOP 1);

Det tar cirka 3 sekunder att skapa och fylla i data på min blygsamma bärbara dator. Uppgifterna är pseudoslumpmässiga, men som en indikation kommer det att se ut ungefär så här:

Dataexempel

Genererar trigram

Följande inline-funktion genererar distinkta alfanumeriska trigram från en given inmatningssträng:

--- Generera trigram från en strängCREATE FUNCTION dbo.GenerateTrigrams (@string varchar(255))RETURNER tabell MED SCHEMABINDINGAS RETURNER MED N16 AS ( VÄLJ V.v FRÅN ( VÄRDEN (0),(0),(0),(0) ),(0),(0),(0),(0), (0),(0),(0),(0),(0),(0),(0),(0) ) AS V (v)), -- Nummertabell (256) Nums AS ( VÄLJ n =ROW_NUMBER() ÖVER (ORDNING EFTER A.v) FRÅN N16 SOM EN KORSAJN N16 AS B ), Trigram AS ( -- Varje 3-teckens delsträng 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 ) -- Ta bort dubbletter och se till att alla tre tecken är alfanumeriska VÄLJ DISTINKT T.trigram FRÅN Trigram SOM T WHERE -- Binär sammanställningsjämförelse så r anges fungerar som förväntat T.trigram COLLATE Latin1_General_BIN2 INTE LIKE '%[^A-Z0-9a-z]%';

Som ett exempel på dess användning, följande anrop:

VÄLJ GT.trigramFROM dbo.GenerateTrigrams('SQLperformance.com') AS GT;

Ger följande trigram:

SQLperformance.com-trigram

Utförandeplanen är en ganska direkt översättning av T-SQL i detta fall:

  • Genererar rader (korskoppling av konstanta skanningar)
  • Radnumrering (segment- och sekvensprojekt)
  • Begränsa antalet nödvändiga baserat på längden på strängen (Överst)
  • Ta bort trigram med icke-alfanumeriska tecken (Filter)
  • Ta bort dubbletter (Distinct Sort)

Planera för att generera trigram

Ladda in trigrammen

Nästa steg är att bevara trigram för exempeldata. Trigrammen kommer att hållas i en ny tabell, fylld med hjälp av inline-funktionen vi just skapade:

-- Trigram för Exempel tableCREATE TABLE dbo.ExampleTrigrams( id heltal NOT NULL, trigram char(3) NOT NULL);GO-- Generate trigramsINSERT dbo.ExampleTrigrams WITH (TABLOCKX) (id, trigram)SELECT E.id, GT.trigramFROM dbo.Example AS ECROSS APPLY dbo.GenerateTrigrams(E.string) AS GT;

Det tar ungefär 20 sekunder att köra på min SQL Server 2016 laptop-instans. Denna speciella körning gav 17 937 972 rader trigram för de 1 miljon raderna med 20-teckens testdata. Utförandeplanen visar i huvudsak funktionsplanen som utvärderas för varje rad i exempeltabellen:

Fylla trigramtabellen

Eftersom detta test utfördes på SQL Server 2016 (laddar en heaptabell, under databaskompatibilitetsnivå 130 och med en TABLOCK tips), drar planen nytta av parallell infogning. Rader fördelas mellan trådar genom den parallella skanningen av exempeltabellen och förblir på samma tråd därefter (inga ompartitioneringsutbyten).

Sorteringsoperatorn kan se lite imponerande ut, men siffrorna visar det totala antalet sorterade rader, över alla iterationer av den kapslade loop-join. Faktum är att det finns en miljon separata sorter, med 18 rader vardera. Vid en grad av parallellitet på fyra (två kärnor hypertrådade i mitt fall), finns det maximalt fyra små sorteringar som pågår åt gången, och varje sorteringsinstans kan återanvända minnet. Detta förklarar varför den maximala minnesanvändningen för denna exekveringsplan är bara 136KB (även om 2 152 KB beviljades).

Trigramtabellen innehåller en rad för varje distinkt trigram i varje källtabellsrad (identifierad med id ):

Trigramtabellexempel

Vi skapar nu ett klustrat b-trädindex för att stödja sökning efter trigrammatchningar:

-- Trigramsökningsindex SKAPA UNIKT CLUSTERED INDEX [CUQ dbo.ExampleTrigrams (trigram, id)]ON dbo.ExampleTrigrams (trigram, id)WITH (DATA_COMPRESSION =ROW);

Detta tar cirka 45 sekunder , även om en del av det beror på sortens spill (min instans är begränsad till 4 GB minne). En instans med mer minne tillgängligt skulle förmodligen kunna slutföra det minimalt loggade parallellindexbygget ganska mycket snabbare.

Indexbyggnadsplan

Observera att indexet är specificerat som unikt (med båda kolumnerna i nyckeln). Vi kunde ha skapat ett icke-unikt klustrat index enbart på trigrammet, men SQL Server skulle ha lagt till 4-byte uniquiifiers till nästan alla rader ändå. När vi väl tar hänsyn till att uniquiifiers lagras i radens del med variabel längd (med tillhörande overhead), är det bara mer meningsfullt att inkludera id i nyckeln och var klar med den.

Radkomprimering är specificerad eftersom den reducerar storleken på trigramtabellen från 277 MB till 190 MB (som jämförelse är exempeltabellen 32MB). Om du inte åtminstone använder SQL Server 2016 SP1 (där datakomprimering blev tillgänglig för alla utgåvor) kan du utelämna komprimeringssatsen om det behövs.

Som en sista optimering kommer vi också att skapa en indexerad vy över trigramtabellen för att göra det snabbt och enkelt att hitta vilka trigram som är de vanligaste och minst vanliga i data. Detta steg kan utelämnas, men rekommenderas för prestanda.

-- Selektivitet för varje trigram (prestandaoptimering)SKAPA VY dbo.ExampleTrigramCountsWITH SCHEMABINDINGASSELECT ET.trigram, cnt =COUNT_BIG(*)FRÅN dbo.ExampleTrigrams AS ETGROUP BY ET.trigram;GOREATE UNIQUE INDI [CLUSTERED. CUQ dbo.ExampleTrigramCounts (trigram)]PÅ dbo.ExampleTrigramCounts (trigram);

Byggnadsplan med indexvy

Detta tar bara ett par sekunder att slutföra. Storleken på den materialiserade vyn är liten, bara 104KB .

Trigramsökning

Givet en söksträng (t.ex. '%find%this%' ), kommer vårt tillvägagångssätt att vara att:

  1. Generera hela uppsättningen trigram för söksträngen
  2. Använd den indexerade vyn för att hitta de tre mest selektiva trigrammen
  3. Hitta ID:n som matchar alla tillgängliga trigram
  4. Hämta strängarna efter id
  5. Använd hela filtret på de trigramkvalificerade raderna

Hitta selektiva trigram

De två första stegen är ganska enkla. Vi har redan en funktion för att generera trigram för en godtycklig sträng. Att hitta det mest selektiva av dessa trigram kan uppnås genom att gå med i den indexerade vyn. Följande kod omsluter implementeringen för vår exempeltabell i en annan inline-funktion. Den svänger de tre mest selektiva trigrammen till en enda rad för enkel användning senare:

-- Mest selektiva trigram för en söksträng-- Returnerar alltid en rad (NULLs om inga trigram hittas)SKAPA FUNKTION dbo.Example_GetBestTrigrams (@string varchar(255))RETURNERAR tabell MED SCHEMABINDING ASRETURN SELECT -- Pivot trigram1 =MAX CASE NÄR BT.rn =1 DÅ BT.trigram SLUT), trigram2 =MAX(CASE NÄR BT.rn =2 DÅ BT.trigram SLUT), trigram3 =MAX(FALL NÄR BT.rn =3 DÅ BT.trigram SLUT) FRÅN ( -- Generera trigram för söksträngen -- och välj de mest selektiva tre SELECT TOP (3) rn =ROW_NUMBER() OVER ( ORDER BY ETC.cnt ASC), GT.trigram FROM 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 ett exempel:

VÄLJ EGBT.trigram1, EGBT.trigram2, EGBT.trigram3 FROM dbo.Example_GetBestTrigrams('%1234%5678%') AS EGBT;

returnerar (för mina exempeldata):

Utvalda trigram

Utförandeplanen är:

Utförandeplan för GetBestTrigrams

Detta är den välbekanta trigramgenererande planen från tidigare, följt av en uppslagning i den indexerade vyn för varje trigram, sortering efter antal matchningar, numrering av raderna (Sequence Project), begränsa uppsättningen till tre rader (Överst), sedan pivotering resultatet (Stream Aggregate).

Hitta ID:n som matchar alla trigram

Nästa steg är att hitta exempel på rad-ID för tabeller som matchar alla trigram utan noll som hämtats av föregående steg. Rynkan här är att vi kan ha noll, ett, två eller tre trigram tillgängliga. Följande implementering omsluter den nödvändiga logiken i en funktion med flera påståenden och returnerar de kvalificerande ID:n i en tabellvariabel:

-- Returnerar exempel-id som matchar alla angivna (icke-null) trigramCREATE FUNCTION dbo.Example_GetTrigramMatchIDs( @Trigram1 char(3), @Trigram2 char(3), @Trigram3 char(3))RETURNS @IDs table (id heltal) PRIMÄRNYCKEL)MED SCHEMABINDING ASBEGIN OM @Trigram1 INTE ÄR NULL BÖRJAN OM @Trigram2 INTE ÄR NULL BÖRJAN OM @Trigram3 INTE ÄR NULL BEGIN -- 3 trigram tillgängliga SÄTT IN @IDs (id) VÄLJ ET1.id FRÅN dbo.ExampleTrigrams ASET11 .trigram =@Trigram1 INTERSECT SELECT ET2.id FROM dbo.ExampleTrigrams AS ET2 WHERE ET2.trigram =@Trigram2 INTERSECT SELECT ET3.id FROM dbo.ExampleTrigrams AS ET3 WHERE ET3.trigram =@Trigram3 OPTION); SLUTET; ELSE BEGIN -- 2 trigram tillgängliga 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 SAMMANFATTNING JOIN); SLUTET; SLUTET; ELSE BEGIN -- 1 trigram tillgängligt INSERT @IDs (id) SELECT ET1.id FROM dbo.ExampleTrigrams AS ET1 WHERE ET1.trigram =@Trigram1; SLUTET; SLUTET; RETURN;END;

Den beräknade genomförandeplanen för denna funktion visar strategin:

Trigram match id plan

Om det finns ett trigram tillgängligt, utförs en enda sökning i trigramtabellen. Annars utförs två eller tre sökningar och skärningspunkten av id hittas med hjälp av en effektiv en-till-många sammanfogning. Det finns inga minneskrävande operatörer i den här planen, så ingen risk för hash eller sortering.

Om vi ​​fortsätter med exempelsökningen kan vi hitta ID som matchar de tillgängliga trigrammen genom att använda den nya funktionen:

VÄLJ EGTMID.id FRÅN dbo.Example_GetBestTrigrams('%1234%5678%') SOM EGBTCROSS TILLÄMPA dbo.Example_GetTrigramMatchIDs (EGBT.trigram1, EGBT.trigram2, EGBT.trigram3) SOM EGTMID;EGTMID; 

Detta returnerar en uppsättning som följande:

Matchande ID

Den faktiska (efterutförande) planen för den nya funktionen visar planformen med tre trigramingångar som används:

Faktisk ID-matchningsplan

Detta visar kraften i trigrammatchning ganska bra. Även om alla tre trigram var och en identifierar cirka 11 000 rader i exempeltabellen, reducerar den första skärningen denna uppsättning till 1 004 rader, och den andra skärningen reducerar den till bara 7 .

Slutför implementering av trigramsökning

Nu när vi har ID som matchar trigrammen kan vi slå upp de matchande raderna i exempeltabellen. Vi måste fortfarande tillämpa det ursprungliga sökvillkoret som en sista kontroll, eftersom trigram kan generera falska positiva (men inte falska negativa). Det sista problemet att ta itu med är att de tidigare stegen kanske inte har hittat några trigram. Det kan till exempel bero på att söksträngen innehåller för lite information. En söksträng av '%FF%' kan inte använda trigramsökning eftersom två tecken inte räcker för att generera ens ett enda trigram. För att hantera detta scenario på ett elegant sätt kommer vår sökning att upptäcka detta tillstånd och falla tillbaka till en icke-trigramsökning.

Följande sista inline-funktion implementerar den nödvändiga logiken:

-- SökimplementeringCREATE FUNCTION dbo.Example_TrigramSearch( @Search varchar(255))RETURNER tabell MED SCHEMABINDINGASRETURN SELECT Result.string FROM dbo.Example_GetBestTrigrams(@Search) AS GBT CROSS APPLY ( -- Trigramsökning, E. SELECT). sträng FRÅN dbo.Example_GetTrigramMatchIDs (GBT.trigram1, GBT.trigram2, GBT.trigram3) AS MID JOIN dbo.Example AS E ON E.id =MID.id WHERE -- Minst ett trigram hittades GBT.trigram1 ÄR INTE NULL OCH E .string LIKE @Search UNION ALL -- Non-trigram search SELECT E.id, E.string FROM dbo.Example AS E WHERE -- Inget trigram hittades GBT.trigram1 IS NULL OCH E.string LIKE @Search ) SOM Resultat;

Nyckelfunktionen är den yttre referensen till GBT.trigram1 på båda sidor av UNION ALL . Dessa översätts till Filter med startuttryck i exekveringsplanen. Ett startfilter exekverar endast sitt underträd om dess tillstånd utvärderas till sant. Nettoeffekten är att endast en del av förbundet alla kommer att avrättas, beroende på om vi hittade ett trigram eller inte. Den relevanta delen av genomförandeplanen är:

Effekt för startfilter

Antingen Example_GetTrigramMatchIDs funktionen kommer att exekveras (och resultat slås upp i exempel med hjälp av en sökning på id), eller Clustered Index Scan av exempel med en återstående LIKE predikatet kommer att köras, men inte båda.

Prestanda

Följande kod testar prestandan för trigramsökning mot motsvarande 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(MILLISECOND, @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());

Dessa ger båda samma resultatrad(er) men LIKE frågan körs i 2100ms , medan trigramsökningen tar 15 ms .

Ännu bättre prestanda är möjlig. Generellt sett förbättras prestandan när trigrammen blir mer selektiva och färre i antal (under maxvärdet på tre i denna implementering). Till exempel:

SET STATISTICS XML OFFDECLARE @S datetime2 =SYSUTCDATETIME(); SELECT ETS.stringFROM dbo.Example_TrigramSearch('%BEEF%') AS ETS; SELECT ElapsedMS =DATEDIFF(MILLISECOND, @S, SYSUTCDATETIME());

Den sökningen returnerade 111 rader till SSMS-rutnätet på 4ms . LIKE motsvarande körde i 1950 ms .

Underhålla trigrammen

Om måltabellen är statisk är det uppenbarligen inga problem att hålla bastabellen och tillhörande trigramtabell synkroniserade. På samma sätt, om sökresultaten inte alltid måste vara helt uppdaterade, kan en schemalagd uppdatering av trigramtabellerna fungera bra.

Annars kan vi använda några ganska enkla triggers för att hålla trigramsökningsdata synkroniserade med de underliggande strängarna. Den allmänna idén är att generera trigram för raderade och infogade rader och sedan lägga till eller ta bort dem i trigramtabellen efter behov. Utlösaren för att infoga, uppdatera och ta bort nedan visar denna idé i praktiken:

-- Behåll trigram efter exempel insertsCREATE TRIGGER MaintainTrigrams_AION dbo.ExampleAFTER INSERTASBEGIN IF @@ROWCOUNT =0 RETURN; IF TRIGGER_NESTLEVEL(@@PROCID, 'AFTER', 'DML')> 1 RETURN; STÄLL IN ANTALT PÅ; STÄLL IN RADANTAL 0; -- Infoga relaterade trigram INSERT dbo.ExampleTrigrams (id, trigram) SELECT INS.id, GT.trigram FROM Inserted AS INS CROSS APPLY dbo.GenerateTrigrams(INS.string) AS GT;END;
-- Behåll trigram efter exempel raderingarCREATE TRIGGER MaintainTrigrams_ADON dbo.ExampleAFTER DELETEASBEGIN IF @@ROWCOUNT =0 RETURN; IF TRIGGER_NESTLEVEL(@@PROCID, 'AFTER', 'DML')> 1 RETURN; STÄLL IN ANTALT PÅ; STÄLL IN RADANTAL 0; -- Raderade relaterade trigram DELETE ET WITH (SERIALISERBAR) FRÅN Deleted AS DEL CROSS APPLY dbo.GenerateTrigrams(DEL.string) AS GT JOIN dbo.ExampleTrigrams AS ET ON ET.trigram =GT.trigram AND ET.id =DEL.id; END;
-- Underhåll trigram efter exempeluppdateringarCREATE TRIGGER MaintainTrigrams_AUON dbo.ExempelEFTER UPPDATERINGASBÖRJAN OM @@ROWCOUNT =0 RETURN; IF TRIGGER_NESTLEVEL(@@PROCID, 'AFTER', 'DML')> 1 RETURN; STÄLL IN ANTALT PÅ; STÄLL IN RADANTAL 0; -- Raderade relaterade trigram DELETE ET WITH (SERIALISERBAR) FRÅN Deleted AS DEL CROSS APPLY dbo.GenerateTrigrams(DEL.string) AS GT JOIN dbo.ExampleTrigrams AS ET ON ET.trigram =GT.trigram AND ET.id =DEL.id; -- Infoga relaterade trigram INSERT dbo.ExampleTrigrams (id, trigram) SELECT INS.id, GT.trigram FROM Inserted AS INS CROSS APPLY dbo.GenerateTrigrams(INS.string) AS GT;END;

Utlösarna är ganska effektiva och kommer att hantera både en- och flera radsändringar (inklusive de flera tillgängliga åtgärderna när du använder en MERGE påstående). Den indexerade vyn över trigramtabellen kommer att underhållas automatiskt av SQL Server utan att vi behöver skriva någon triggerkod.

Triggar operation

Som ett exempel, kör en sats för att ta bort en godtycklig rad från exempeltabellen:

-- Single rad deleteDELETE TOP (1) dbo.Example;

Efterkörningsplanen (faktisk) inkluderar en post för utlösaren efter borttagning:

Ta bort utlösarexekveringsplan

Den gula delen av planen läser rader från de borttagna pesudo-tabell, genererar trigram för varje raderad exempelsträng (med hjälp av den välbekanta planen markerad i grönt), lokaliserar och tar sedan bort de associerade trigramtabellposterna. Den sista delen av planen, som visas i rött, läggs automatiskt till av SQL Server för att hålla den indexerade vyn uppdaterad.

Planen för insatsutlösaren är extremt lika. Uppdateringar hanteras genom att utföra en radering följt av en infogning. Kör följande skript för att se dessa planer och bekräfta att nya och uppdaterade rader kan hittas med hjälp av trigramsökfunktionen:

-- Single row insertINSERT dbo.Example (string) VALUES ('SQLPerformance.com'); -- Hitta den nya radenSELECT ETS.stringFROM dbo.Example_TrigramSearch('%perf%') AS ETS; -- En rad uppdateringUPDATE TOP (1) dbo.Exempel SET string ='12345678901234567890'; -- Infoga med flera raderINSERT dbo.Exempel MED (TABLOCKX) (sträng)SELECT TOP (1000) REPLACE(STR(RAND(CHECKSUM(NEWID())) * 1e10, 10), SPACE(1), '0') + RIGHT(NEWID(), 10)FRÅN master.dbo.spt_values ​​AS SV1; -- FlerradsuppdateringUPDATE TOP (1000) dbo.Exempel SET-sträng ='12345678901234567890'; -- Sök efter de uppdaterade radernaSELECT ETS.string FROM dbo.Example_TrigramSearch('12345678901234567890') AS ETS;

Sammanfogningsexempel

Nästa skript visar en MERGE sats som används för att infoga, ta bort och uppdatera exempeltabellen på en gång:

-- MERGE demoDECLARE @MergeData-tabell (id heltal UNIQUE CLUSTERED NULL, operation char(3) NOT NULL, string char(20) NULL); INSERT @MergeData (id, operation, sträng)VÄRDEN (NULL, 'INS', '11223344556677889900'), -- Infoga (1001, 'DEL', NULL), -- Ta bort (2002, 'UPD', '000000000000000'); -- Uppdatera DECLARE @Actions-tabellen (action$ nvarchar(10) NOT NULL, old_id heltal NULL, old_string char(20) NULL, new_id heltal NULL, new_string char(20) NULL); MERGE dbo.Exempel SOM EUSAR @MergeData AS MD PÅ MD.id =E.idWHEN MATCHED AND MD.operation ='DEL' DELETEWENHEN MATCHED OCH MD.operation ='UPD' DÅ UPPDATERA SET E.string =MD.stringNÄR INTE MATCHED OCH MD.operation ='INS' SEDAN INFOGA (sträng) VÄRDEN (MD.string)OUTPUT $action, Deleted.id, Deleted.string, Inserted.id, Inserted.stringINTO @Actions (action$, old_id, old_string, new_id, ny_sträng); VÄLJ * FRÅN @Actions AS A;

Utdata kommer att visa något i stil med:

Åtgärdsutdata

Sluta tankar

Det finns kanske ett visst utrymme att påskynda stora raderings- och uppdateringsoperationer genom att referera till ID direkt istället för att generera trigram. Detta är inte implementerat här eftersom det skulle kräva ett nytt icke-klustrat index på trigramtabellen, vilket fördubblar det (redan betydande) lagringsutrymmet som används. Trigramtabellen innehåller ett enda heltal och en char(3) per rad; ett icke-klustrat index på heltalskolumnen skulle få char(3) kolumn på alla nivåer (med tillstånd av det klustrade indexet och behovet av att indexnycklar är unika på varje nivå). Det finns också minnesutrymme att tänka på, eftersom trigramsökningar fungerar bäst när alla läsningar är från cache.

Det extra indexet skulle göra kaskadreferensintegritet till ett alternativ, men det är ofta mer problem än det är värt.

Trigramsökning är inget universalmedel. De extra lagringskraven, implementeringens komplexitet och inverkan på uppdateringsprestanda väger tungt mot det. Tekniken är också värdelös för sökningar som inte genererar några trigram (minst 3 tecken). Även om den grundläggande implementeringen som visas här kan hantera många typer av sökning (börjar med, innehåller, slutar med flera jokertecken) täcker den inte alla möjliga sökuttryck som kan fås att fungera med LIKE . Det fungerar bra för söksträngar som genererar AND-trigram; mer arbete krävs för att hantera söksträngar som kräver hantering av OR-typ, eller mer avancerade alternativ som reguljära uttryck.

Allt som sagt, om din ansökan verkligen måste har snabba jokerteckensträngsökningar, n-gram är något att seriöst överväga.

Relaterat innehåll:Ett sätt att få en indexsökning för ett ledande %wildcard av Aaron Bertrand.


  1. Konvertera från DateTime till INT

  2. Varför kan jag inte skapa utlösare på objekt som ägs av SYS?

  3. Förstör en Postgres DB på Heroku

  4. Återställ sekvens i oracle 11g