No meu último post, "Uma maneira de obter uma busca de índice por um curinga principal", mencionei que você precisaria de gatilhos para lidar com a manutenção dos fragmentos que recomendei. Algumas pessoas entraram em contato comigo para perguntar se eu poderia demonstrar esses gatilhos.
Para simplificar a partir do post anterior, vamos supor que temos as seguintes tabelas - um conjunto de empresas e, em seguida, uma tabela CompanyNameFragments que permite a pesquisa de pseudo-curinga em qualquer substring do nome da empresa:
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);
Dada esta função para gerar fragmentos (a única mudança do artigo original é que eu aumentei
@input
para suportar 100 caracteres):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
Podemos criar um único gatilho que pode lidar com todas as três operações:
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
Isso funciona sem qualquer verificação de qual tipo de operação aconteceu porque:
- Para um UPDATE ou um DELETE, o DELETE acontecerá – para um UPDATE, não vamos nos incomodar em tentar combinar fragmentos que permanecerão os mesmos; nós vamos explodi-los todos, para que possam ser substituídos em massa. Para um INSERT, a instrução DELETE não terá efeito, porque não haverá linhas em
deleted
. - Para um INSERT ou um UPDATE, o INSERT acontecerá. Para um DELETE, a instrução INSERT não terá efeito, pois não haverá linhas em
inserted
.
Agora, só para ter certeza de que funciona, vamos fazer algumas alterações nas
Companies
table e, em seguida, inspecione nossas duas tabelas. -- 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;
Figura 1: Conteúdo inicial da tabela | Figura 2: Conteúdo da tabela após atualização | Figura 3: Conteúdo da tabela após a exclusão |
Aviso (para o pessoal de integridade referencial)
Observe que, se você configurar chaves estrangeiras adequadas entre essas duas tabelas, terá que usar um em vez de um gatilho para lidar com exclusões, caso contrário, terá um problema de galinha e ovo - você não pode esperar até *depois* do pai linha é excluída para remover as linhas filhas. Então você teria que configurar
ON DELETE CASCADE
(o que eu pessoalmente não costumo gostar), ou seus dois gatilhos ficariam assim (o gatilho posterior ainda teria que executar um par DELETE/INSERT no caso de um UPDATE):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
Resumo
Esta postagem teve como objetivo mostrar como é fácil configurar acionadores que manterão pesquisável fragmentos de string para melhorar as pesquisas com curingas, pelo menos para strings de tamanho moderado. Agora, eu ainda sei que isso parece uma ideia maluca, mas continuo falando sobre isso porque estou convencido de que existem bons casos de uso por aí.
Na minha próxima postagem, mostrarei como ver o impacto dessa escolha:você pode configurar facilmente cargas de trabalho representativas para comparar os custos de recursos de manutenção dos fragmentos com a economia de desempenho no momento da consulta. Examinarei comprimentos de string variados, bem como diferentes equilíbrios de carga de trabalho (principalmente leitura vs. principalmente gravação) e tentarei encontrar pontos ideais e zonas de perigo.