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.