sql >> Database >  >> RDS >> Database

Follow-up #1 op leidende wildcard-zoekopdrachten

In mijn laatste bericht, "Een manier om een ​​index te laten zoeken naar een leidende wildcard," zei ik dat je zou triggers nodig hebben om de door mij aanbevolen fragmenten te onderhouden. Een paar mensen hebben contact met me opgenomen om te vragen of ik die triggers kan demonstreren.

Ter vereenvoudiging van het vorige bericht, laten we aannemen dat we de volgende tabellen hebben:een reeks bedrijven en vervolgens een tabel CompanyNameFragments die pseudo-jokertekens zoeken mogelijk maakt op elke subtekenreeks van de bedrijfsnaam:

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

Gezien deze functie voor het genereren van fragmenten (de enige verandering ten opzichte van het originele artikel is dat ik @input heb verhoogd om 100 tekens te ondersteunen):

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

We kunnen een enkele trigger maken die alle drie de bewerkingen aankan:

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

Dit werkt zonder te controleren welk type bewerking is uitgevoerd, omdat:

  • Voor een UPDATE of een VERWIJDERING zal de VERWIJDERING plaatsvinden - voor een UPDATE gaan we niet de moeite nemen om fragmenten te matchen die hetzelfde blijven; we gaan ze gewoon allemaal wegblazen, zodat ze massaal kunnen worden vervangen. Voor een INSERT heeft de DELETE-instructie geen effect, omdat er geen rijen zijn in deleted .
  • Voor een INSERT of een UPDATE zal de INSERT plaatsvinden. Voor een DELETE heeft de INSERT-instructie geen effect, omdat er geen rijen zijn in inserted .

Laten we nu, om er zeker van te zijn dat het werkt, enkele wijzigingen aanbrengen in de Companies tafel en inspecteer vervolgens onze twee tafels.

-- 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;
Figuur 1: Initiële tabelinhoud Figuur 2: Tabelinhoud na update Figuur 3: Tabelinhoud na verwijderen

Voorbehoud (voor de referentiële integriteitsmensen)

Merk op dat als je de juiste externe sleutels tussen deze twee tabellen instelt, je een in plaats van een trigger moet gebruiken om verwijderingen af ​​te handelen, anders heb je een kip-en-ei-probleem - je kunt niet wachten tot *na* de ouder rij wordt verwijderd om de onderliggende rijen te verwijderen. U moet dus ON DELETE CASCADE . instellen (wat ik persoonlijk niet leuk vind), of je twee triggers zouden er zo uitzien (de after-trigger zou nog steeds een DELETE/INSERT-paar moeten uitvoeren in het geval van een 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

Samenvatting

Dit bericht was bedoeld om te laten zien hoe gemakkelijk het is om triggers in te stellen die zoekbaar blijven tekenreeksfragmenten om zoekopdrachten met jokertekens te verbeteren, in ieder geval voor tekenreeksen van gemiddelde grootte. Nu weet ik nog steeds dat dit een gek idee overkomt, maar ik blijf erover praten omdat ik ervan overtuigd ben dat er goede gebruiksscenario's zijn.

In mijn volgende bericht zal ik laten zien hoe u de impact van deze keuze kunt zien:u kunt eenvoudig representatieve workloads instellen om de resourcekosten voor het onderhouden van de fragmenten te vergelijken met de prestatiebesparingen op het moment van query's. Ik zal kijken naar verschillende snaarlengtes en verschillende werklastbalansen (meestal lezen versus meestal schrijven) en proberen goede plekken en gevarenzones te vinden.


  1. Snelste manier om grote CSV-bestanden in MySql te importeren met MySql CLI

  2. MySQL en JDBC met rewriteBatchedStatements=true

  3. orakel - welke uitspraken moeten worden gedaan?

  4. datetime2 vs datetimeoffset in SQL Server:wat is het verschil?