Puis-je faire une correspondance de première lettre discutable sur deux tables?

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

Serveur SQL. Existe-t-il un moyen de rendre ce SARGable / plus rapide? Je ne peux pas créer de colonnes sur la table des personnes, mais je peux créer des colonnes sur les personnes2.

lastchancexi
la source
3
Vous savez que le résultat de cette requête va être une sorte de CROSS JOIN, en fait?
ypercubeᵀᴹ
1
Quelle est la taille des tables? S'ils ne disent chacun que 10 000 lignes, le résultat sera d'au moins 4 millions de lignes. Je me demande quelle sera l'utilité d'une telle requête.
ypercubeᵀᴹ
1
@ ypercubeᵀᴹ peut-être une entrée initiale dans un processus de déduplication utilisant une correspondance floue?
Martin Smith
Cela ressemble à une mauvaise idée. Qu'essayez-vous de réaliser ici?
David דודו Markovitz
C'était juste par exemple. Il y a plus de prédicats. Martin Smith a la bonne idée, c'est pour la déduplication.
lastchancexi

Réponses:

9

Créez une vue sur les tables avec une colonne calculée persistante définie comme la LEFT(lastname, 1)de chaque table, puis comparez les valeurs des colonnes persistantes calculées.

Voici un banc d'essai montrant comment procéder:

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

Ici, nous allons insérer quelques exemples de données:

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

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

Voici la SELECTrequête:

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

Et les résultats:

+ ---------- + ----------- + ---------- + --------------- - + ---------- + ----------- + ---------- + ------------- ---- +
| PersonID | Prénom | Nom | LastNameInitial | PersonID | Prénom | Nom | LastNameInitial |
+ ---------- + ----------- + ---------- + --------------- - + ---------- + ----------- + ---------- + ------------- ---- +
| 2 | Joe | Noir | B | 2 | Joe | Noir | B |
| 1 | Max | Vernon | V | 1 | Max | Vernon | V |
+ ---------- + ----------- + ---------- + --------------- - + ---------- + ----------- + ---------- + ------------- ---- +

Le plan d'exécution, avec seulement deux lignes par table (certes pas beaucoup de lignes!)

entrez la description de l'image ici

Max Vernon
la source
11

Si la lastnamecolonne est indexée dans au moins une des tables, vous pouvez également utiliserLIKE

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

entrez la description de l'image ici

Le plan pour cela peut avoir une recherche sur la table spécifiée à gauche de similaire.

c.-à ON p.lastname LIKE LEFT(p2.lastname, 1) + '%'-d. ne serait pas en mesure de faire usage de l'index persons2utilisé ci-dessus mais pourrait en rechercher un persons.

La suggestion dans l'autre réponse d'indexer une colonne calculée des deux côtés est cependant plus flexible. En ce qui concerne un plan de boucles imbriquées, chaque table peut être à l'intérieur et cela permettrait également une fusion de plusieurs à plusieurs sans nécessiter de tri.

Martin Smith
la source
qu'en est-il de cette approche ? N'hésitez pas à l'ajouter dans votre réponse si cela présente un quelconque avantage. Serait-il en utilisant des index sur les deux tables - et si oui, serait-il plus efficace?
ypercubeᵀᴹ
@ ypercubeᵀᴹ Cela pourrait donner un plan comme celui-ci si les index couvrent i.stack.imgur.com/RSzcT.png . Je ne vois cependant aucun avantage par rapport au plan dans ma réponse. Comme il finira toujours par avoir besoin de lire toutes les lignes de la table externe, tout à l'heure via 26 cherche plutôt qu'un scan.
Martin Smith
2

Il se trouve que j'ai une table avec 3 423 lignes et 195 valeurs distinctes Name. Je vais appeler cette table P(personne) et la dupliquer pour créer P2(personne2). Il existe une clé primaire unique en cluster sur une colonne d'ID entier. J'utilise Microsoft SQL Server 2016 (KB3194716) Developer Edition (64 bits) sur Windows 10 Pro 6.3 avec 32 Go de RAM.

Avec la requête de base

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

J'obtiens 1,5M de lignes retournées en 3200-3300ms (à partir des statistiques io).

entrez la description de l'image ici

En réécrivant ainsi -

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

écoulé se réduit à 50-60 ms et le plan est le suivant:

entrez la description de l'image ici

Moins de lignes sont renvoyées (3 423) en raison de l'algorithme de correspondance. Le même nombre de plans et de lignes est obtenu en remplaçant la requête de base par select distinct.

En créant une colonne indexée et calculée

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

create index ix1 on dbo.p2(Name1);

Le temps écoulé tombe à 45-50 ms.

entrez la description de l'image ici

Michael Green
la source