Database
 sql >> Database >  >> RDS >> Database

Follow-up n. 1 sulle principali ricerche con caratteri jolly

Nel mio ultimo post, "Un modo per ottenere un indice cerca un carattere jolly principale", ho detto che tu avrebbe bisogno di trigger per gestire il mantenimento dei frammenti che ho raccomandato. Un paio di persone mi hanno contattato per chiedermi se potevo dimostrare quei fattori scatenanti.

Per semplificare rispetto al post precedente, supponiamo di avere le seguenti tabelle:un insieme di aziende e quindi una tabella CompanyNameFragments che consente la ricerca con pseudo caratteri jolly rispetto a qualsiasi sottostringa del nome dell'azienda:

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);

Data questa funzione per generare frammenti (l'unica modifica rispetto all'articolo originale è che ho aumentato @input per supportare 100 caratteri):

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

Possiamo creare un singolo trigger in grado di gestire tutte e tre le operazioni:

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

Funziona senza alcun controllo per quale tipo di operazione è avvenuta perché:

  • Per un UPDATE o un DELETE, il DELETE avverrà – per un UPDATE, non ci preoccuperemo di cercare di abbinare i frammenti che rimarranno gli stessi; li faremo esplodere tutti, così possono essere sostituiti in massa. Per un INSERT, l'istruzione DELETE non avrà alcun effetto, perché non ci saranno righe in deleted .
  • Per un INSERT o un AGGIORNAMENTO, l'INSERTO avverrà. Per un DELETE, l'istruzione INSERT non avrà alcun effetto, perché non ci saranno righe in inserted .

Ora, solo per assicurarci che funzioni, eseguiamo alcune modifiche alle Companies tabella e quindi ispeziona le nostre due tabelle.

-- 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: Contenuto della tabella iniziale Figura 2: Contenuto della tabella dopo l'aggiornamento Figura 3: Contenuto della tabella dopo l'eliminazione

Avvertimento (per la gente dell'integrità referenziale)

Nota che se imposti le chiavi esterne corrette tra queste due tabelle, dovrai usare un trigger invece di un trigger per gestire le eliminazioni, altrimenti avrai un problema con pollo e uova - non puoi aspettare fino a *dopo* il genitore la riga viene eliminata per rimuovere le righe figlio. Quindi dovresti impostare ON DELETE CASCADE (cosa che personalmente non mi piace), o i tuoi due trigger sarebbero simili a questo (il trigger successivo dovrebbe comunque eseguire una coppia DELETE/INSERT nel caso di un 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

Riepilogo

Questo post aveva lo scopo di mostrare quanto sia facile impostare attivatori che manterranno ricercabili frammenti di stringa per migliorare le ricerche con caratteri jolly, almeno per stringhe di dimensioni moderate. Ora, so ancora che questo tipo di idea si presenta come un'idea stravagante, ma continuo a parlarne perché sono convinto che ci siano buoni casi d'uso là fuori.

Nel mio prossimo post, mostrerò come vedere l'impatto di questa scelta:puoi facilmente impostare carichi di lavoro rappresentativi per confrontare i costi delle risorse di mantenimento dei frammenti con i risparmi sulle prestazioni al momento della query. Esaminerò le diverse lunghezze delle stringhe e i diversi bilanciamenti del carico di lavoro (per lo più letti e per lo più scritti) e cercherò di trovare punti deboli e zone di pericolo.