¿Puedo hacer una coincidencia de primera letra sargable en dos tablas?

9
select value 
from persons p join persons2 p2 
    on left(p.lastname,1) = left(p2.lastname,1)

Servidor SQL. ¿Hay alguna manera de hacer que este SARGable / funcione más rápido? No puedo crear columnas en la tabla de personas, pero puedo crear columnas en personas2.

lastchancexi
fuente
3
¿Sabes que el resultado de esa consulta será una especie de CROSS JOIN, en realidad?
ypercubeᵀᴹ
1
¿Qué tan grandes son las mesas? Si cada uno dice solo 10K filas, el resultado será al menos 4 millones de filas. Me pregunto cuál será el uso de dicha consulta.
ypercubeᵀᴹ
1
@ ypercubeᵀᴹ ¿tal vez una entrada inicial en algún proceso de deduplicación usando coincidencia difusa?
Martin Smith
Suena como una mala idea. ¿Qué estás tratando de lograr aquí?
David דודו Markovitz
Esto fue solo por ejemplo. Hay más predicados. Martin Smith tiene la idea correcta, es para deduplicación.
lastchancexi

Respuestas:

9

Cree una vista en las tablas con una columna calculada persistente definida como la LEFT(lastname, 1)de cada tabla, luego compare los valores de la columna persistente calculada.

Aquí hay un banco de pruebas que muestra cómo hacerlo:

CREATE TABLE dbo.Persons
(
    PersonID int NOT NULL
        CONSTRAINT PK_Persons
        PRIMARY KEY CLUSTERED
        IDENTITY(1,1)
    , FirstName nvarchar(500) NOT NULL
    , LastName nvarchar(500) NOT NULL
);

CREATE TABLE dbo.Persons2
(
    PersonID int NOT NULL
        CONSTRAINT PK_Persons2
        PRIMARY KEY CLUSTERED
        IDENTITY(1,1)
    , FirstName nvarchar(500) NOT NULL
    , LastName nvarchar(500) NOT NULL
);

GO
CREATE VIEW dbo.PersonsView
WITH SCHEMABINDING
AS
SELECT p1.PersonID
    , p1.FirstName
    , p1.LastName 
    , LastNameInitial = LEFT(p1.LastName, 1)
FROM dbo.Persons p1;
GO
CREATE VIEW dbo.PersonsView2
WITH SCHEMABINDING
AS
SELECT p2.PersonID
    , p2.FirstName
    , p2.LastName 
    , LastNameInitial = LEFT(p2.LastName, 1)
FROM dbo.Persons p2;
GO
CREATE UNIQUE CLUSTERED INDEX CX_PersonsView
ON dbo.PersonsView(PersonID);
CREATE NONCLUSTERED INDEX IX_PersonsView_LastNameInitial
ON dbo.PersonsView(LastNameInitial)
INCLUDE (FirstName, LastName);

CREATE UNIQUE CLUSTERED INDEX CX_PersonsView2
ON dbo.PersonsView2(PersonID);
CREATE NONCLUSTERED INDEX IX_PersonsView2_LastNameInitial
ON dbo.PersonsView2(LastNameInitial)
INCLUDE (FirstName, LastName);

CREATE STATISTICS ST_PersonsView_001
ON dbo.PersonsView(LastName);

CREATE STATISTICS ST_PersonsView2_001
ON dbo.PersonsView2(LastName);

Aquí, insertaremos algunos datos de muestra:

INSERT INTO dbo.Persons(FirstName, LastName)
VALUES ('Max', 'Vernon')
    , ('Joe', 'Black');

INSERT INTO dbo.Persons2(FirstName, LastName)
VALUES ('Max', 'Vernon')
    , ('Joe', 'Black');

Aquí está la SELECTconsulta:

SELECT *
FROM dbo.PersonsView pv1
    INNER JOIN dbo.PersonsView2 pv2 ON pv1.LastNameInitial = pv2.LastNameInitial;

Y los resultados:

+ ---------- + ----------- + ---------- + --------------- - + ---------- + ----------- + ---------- + ------------- ---- +
El | PersonID | Nombre | Apellido | LastNameInitial | PersonID | Nombre | Apellido | LastNameInitial |
+ ---------- + ----------- + ---------- + --------------- - + ---------- + ----------- + ---------- + ------------- ---- +
El | 2 | Joe | Negro | B | 2 | Joe | Negro | B |
El | 1 | Max | Vernon | V | 1 | Max | Vernon | V |
+ ---------- + ----------- + ---------- + --------------- - + ---------- + ----------- + ---------- + ------------- ---- +

El plan de ejecución, con solo dos filas por tabla (¡ciertamente no hay muchas filas!)

ingrese la descripción de la imagen aquí

Max Vernon
fuente
11

Si la lastnamecolumna está indexada en al menos una de las tablas, entonces también podría usarLIKE

SELECT *
FROM   persons p
       INNER JOIN persons2 p2
               ON p2.lastname LIKE LEFT(p.lastname, 1) + '%' 

ingrese la descripción de la imagen aquí

El plan para esto puede tener una búsqueda en la tabla especificada a la izquierda del similar.

es decir ON p.lastname LIKE LEFT(p2.lastname, 1) + '%', no podría hacer uso del índice persons2que se utilizó anteriormente, pero podría buscar uno persons.

Sin embargo, la sugerencia en la otra respuesta de indexar una columna calculada en ambos lados es más flexible. En cuanto a un plan de bucles anidados, cualquier tabla puede estar en el interior y también permitiría una combinación de combinación de muchos a muchos sin requerir una clasificación.

Martin Smith
fuente
¿Qué pasa con este enfoque ? Siéntase libre de agregarlo en su respuesta si tiene algún beneficio. ¿Usaría índices en ambas tablas y, de ser así, sería más eficiente?
ypercubeᵀᴹ
@ ypercubeᵀᴹ Podría dar un plan como este si los índices están cubriendo i.stack.imgur.com/RSzcT.png . Sin embargo, no veo ninguna ventaja sobre el plan en mi respuesta. Como todavía terminará necesitando leer todas las filas de la tabla exterior, justo ahora a través de 26 búsquedas en lugar de un escaneo.
Martin Smith
2

Resulta que tengo una tabla con 3.423 filas y 195 valores distintos Name. Llamaré a esta tabla P(persona) y la duplicaré para crear P2(persona2). Hay una clave primaria única y agrupada en una columna de ID de entero. Estoy usando Microsoft SQL Server 2016 (KB3194716) Developer Edition (64 bits) en Windows 10 Pro 6.3 con 32 GB de RAM.

Con la consulta base

select
    p.pid
from dbo.p
inner join dbo.p2 
    on LEFT(p.name, 1) = LEFT(p2.name, 1);

Me devuelven 1,5 millones de filas en 3200-3300 ms (de estadísticas io).

ingrese la descripción de la imagen aquí

Al reescribir así:

select
    p.pid
from dbo.p
where exists
(
    select 1
    from dbo.p2 
    where LEFT(p.name, 1) = LEFT(p2.name, 1)
);

el tiempo transcurrido se reduce a 50-60 ms y el plan es:

ingrese la descripción de la imagen aquí

Se devuelven menos filas (3,423) debido al algoritmo de coincidencia. El mismo plan y recuento de filas se logra cambiando la consulta base a select distinct.

Al crear una columna indexada y calculada

alter table dbo.p2
add Name1 as Left(Name, 1);

create index ix1 on dbo.p2(Name1);

El tiempo transcurrido cae a 45-50 ms.

ingrese la descripción de la imagen aquí

Michael Green
fuente