Ve svém posledním příspěvku „Jeden způsob, jak získat hledání indexu pro hlavní zástupný znak“, jsem zmínil, že bude potřebovat spouštěče, aby se vypořádal s udržováním fragmentů, které jsem doporučil. Pár lidí mě kontaktovalo se žádostí, zda bych mohl tyto spouštěče předvést.
Pro zjednodušení z předchozího příspěvku předpokládejme, že máme následující tabulky – sadu společností a pak tabulku CompanyNameFragments, která umožňuje vyhledávání pseudo-zástupných znaků proti libovolnému podřetězci názvu společnosti:
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);
Vzhledem k této funkci pro generování fragmentů (jediná změna oproti původnímu článku je, že jsem zvýšil @input
pro podporu 100 znaků):
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
Můžeme vytvořit jediný spouštěč, který zvládne všechny tři operace:
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
Funguje to bez jakékoli kontroly, který typ operace se stal, protože:
- U AKTUALIZACE nebo DELETE dojde k DELETE – v případě AKTUALIZACE se nebudeme obtěžovat snahou porovnat fragmenty, které zůstanou stejné; jen je všechny vyhodíme, aby se daly hromadně vyměnit. U příkazu INSERT nebude mít příkaz DELETE žádný účinek, protože v
deleted
nebudou žádné řádky . - V případě INSERT nebo AKTUALIZACE proběhne INSERT. U příkazu DELETE nebude mít příkaz INSERT žádný účinek, protože v
inserted
nebudou žádné řádky. .
Nyní, abychom se ujistili, že to funguje, proveďte nějaké změny v Companies
stůl a poté si prohlédněte naše dva stoly.
-- 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;
Obrázek 1: Počáteční obsah tabulky | Obrázek 2: Obsah tabulky po aktualizaci | Obrázek 3: Obsah tabulky po smazání |
Upozornění (pro lidi z referenční integrity)
Všimněte si, že pokud mezi těmito dvěma tabulkami nastavíte správné cizí klíče, budete muset ke zpracování mazání použít místo triggeru, jinak budete mít problém slepice a vejce – nemůžete čekat, až *po* rodič řádek je odstraněn, aby se odstranily podřízené řádky. Takže byste museli nastavit ON DELETE CASCADE
(což se mi osobně nelíbí), nebo by vaše dva spouštěče vypadaly takto (spouštěč after by v případě AKTUALIZACE musel stále provádět pár DELETE/INSERT):
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
Shrnutí
Cílem tohoto příspěvku bylo ukázat, jak snadné je nastavit spouštěče, které udrží vyhledatelné fragmenty řetězců pro zlepšení vyhledávání zástupných znaků, alespoň pro středně velké řetězce. Nyní stále vím, že tento druh přichází jako šílený nápad, ale stále o tom mluvím, protože jsem přesvědčen, že existují dobré případy použití.
V mém dalším příspěvku ukážu, jak vidět dopad této volby:Můžete snadno nastavit reprezentativní pracovní zátěže, abyste porovnali náklady na prostředky na údržbu fragmentů s úsporami výkonu v době dotazu. Podívám se na různé délky řetězců a také na různé vyvážení pracovní zátěže (většinou čtení vs. většinou zápis) a pokusím se najít sladká místa a nebezpečné zóny.