sql >> Base de Datos >  >> RDS >> Database

Seguimiento n.º 1 sobre las principales búsquedas de comodines

En mi última publicación, "Una forma de obtener una búsqueda de índice para un comodín principal", mencioné que necesitaría disparadores para lidiar con el mantenimiento de los fragmentos que recomendé. Un par de personas se han puesto en contacto conmigo para preguntarme si podía demostrar esos factores desencadenantes.

Para simplificar la publicación anterior, supongamos que tenemos las siguientes tablas:un conjunto de empresas y luego una tabla CompanyNameFragments que permite la búsqueda de pseudo-comodines contra cualquier subcadena del nombre de la 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 función para generar fragmentos (el único cambio con respecto al artículo original es que aumenté @input para admitir 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 crear un disparador único que pueda manejar las tres operaciones:

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

Esto funciona sin verificar qué tipo de operación ocurrió porque:

  • Para una ACTUALIZACIÓN o una ELIMINACIÓN, la ELIMINACIÓN ocurrirá; para una ACTUALIZACIÓN, no nos molestaremos en tratar de hacer coincidir los fragmentos que seguirán siendo los mismos; simplemente los vamos a volar por los aires, para que puedan ser reemplazados en masa. Para un INSERT, la declaración DELETE no tendrá efecto, porque no habrá filas en deleted .
  • Para una INSERCIÓN o una ACTUALIZACIÓN, se realizará la INSERCIÓN. Para DELETE, la declaración INSERT no tendrá efecto, porque no habrá filas en inserted .

Ahora, solo para asegurarnos de que funciona, realicemos algunos cambios en las Companies y luego inspeccione nuestras dos mesas.

-- 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: Contenido de la tabla inicial Figura 2: Contenido de la tabla después de la actualización Figura 3: Contenido de la tabla después de eliminar

Advertencia (para la gente de integridad referencial)

Tenga en cuenta que si configura claves foráneas adecuadas entre estas dos tablas, tendrá que usar un desencadenador en lugar de un disparador para manejar las eliminaciones; de lo contrario, tendrá un problema con el huevo y la gallina:no puede esperar hasta *después* del padre la fila se elimina para eliminar las filas secundarias. Entonces tendría que configurar ON DELETE CASCADE (que personalmente no me suele gustar), o sus dos disparadores se verían así (el disparador posterior aún tendría que realizar un par ELIMINAR/INSERTAR en el caso de una ACTUALIZACIÓN):

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

Resumen

Esta publicación tenía como objetivo mostrar lo fácil que es configurar disparadores que mantendrán buscable fragmentos de cadena para mejorar las búsquedas con comodines, al menos para cadenas de tamaño moderado. Ahora, todavía sé que esto parece una idea descabellada, pero sigo hablando de ello porque estoy convencido de que existen buenos casos de uso.

En mi próxima publicación, mostraré cómo ver el impacto de esta elección:puede configurar fácilmente cargas de trabajo representativas para comparar los costos de recursos de mantener los fragmentos con los ahorros de rendimiento en el momento de la consulta. Examinaré diferentes longitudes de cadena, así como diferentes equilibrios de carga de trabajo (principalmente lectura frente a escritura) y trataré de encontrar puntos óptimos y zonas peligrosas.