Database
 sql >> Base de Dados >  >> RDS >> Database

Acompanhamento nº 1 nas principais buscas de curingas


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.