Kann ich an zwei Tischen ein Sargable First Letter Match machen?

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

SQL Server. Gibt es eine Möglichkeit, dieses SARGable / Run schneller zu machen? Ich kann keine Spalten für die Personentabelle erstellen, aber ich kann Spalten für Personen2 erstellen.

lastchancexi
quelle
3
Sie wissen, dass das Ergebnis dieser Abfrage tatsächlich eine Art CROSS JOIN sein wird?
Ypercubeᵀᴹ
1
Wie groß sind die Tische? Wenn es sich jeweils nur um 10K-Zeilen handelt, ergibt sich ein Ergebnis von mindestens 4 Millionen Zeilen. Ich frage mich, wie die Verwendung einer solchen Abfrage aussehen wird.
Ypercubeᵀᴹ
1
@ ypercubeᵀᴹ vielleicht eine erste Eingabe in einen Deduplizierungsprozess unter Verwendung von Fuzzy Matching?
Martin Smith
Klingt nach einer schlechten Idee. Was versuchst du hier zu erreichen?
David Markovitz
Dies war nur zum Beispiel. Es gibt mehr Prädikate. Martin Smith hat die richtige Idee, es ist für die Deduplizierung.
Lastchancexi

Antworten:

9

Erstellen Sie eine Ansicht der Tabellen mit einer persistierten berechneten Spalte, die als die LEFT(lastname, 1)jeder Tabelle definiert ist, und vergleichen Sie dann die berechneten persistierten Spaltenwerte.

Hier ist ein Prüfstand, der zeigt, wie das geht:

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

Hier fügen wir einige Beispieldaten ein:

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

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

Hier ist die SELECTAbfrage:

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

Und die Ergebnisse:

+ ---------- + ----------- + ---------- + --------------- - + ---------- + ----------- + ---------- + ------------- ---- +
| PersonID | Vorname | Nachname | LastNameInitial | PersonID | Vorname | Nachname | LastNameInitial |
+ ---------- + ----------- + ---------- + --------------- - + ---------- + ----------- + ---------- + ------------- ---- +
| 2 | Joe | Schwarz | B | 2 | Joe | Schwarz | B |
| 1 | Max | Vernon | V | 1 | Max | Vernon | V |
+ ---------- + ----------- + ---------- + --------------- - + ---------- + ----------- + ---------- + ------------- ---- +

Der Ausführungsplan mit nur zwei Zeilen pro Tabelle (zugegebenermaßen nicht viele Zeilen!)

Geben Sie hier die Bildbeschreibung ein

Max Vernon
quelle
11

Wenn die lastnameSpalte in mindestens einer der Tabellen indiziert ist, können Sie sie auch verwendenLIKE

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

Geben Sie hier die Bildbeschreibung ein

Der Plan hierfür kann eine Suche in der Tabelle enthalten, die links davon angegeben ist.

dh ON p.lastname LIKE LEFT(p2.lastname, 1) + '%'wäre nicht in der Lage, den oben verwendeten Index zu persons2verwenden, könnte aber einen suchen persons.

Der Vorschlag in der anderen Antwort, eine berechnete Spalte auf beiden Seiten zu indizieren, ist jedoch flexibler. Bei einem Plan für verschachtelte Schleifen kann sich jede Tabelle auf der Innenseite befinden, und es können auch viele bis viele Zusammenführungsverbindungen erstellt werden, ohne dass eine Sortierung erforderlich ist.

Martin Smith
quelle
Was ist mit diesem Ansatz ? Fühlen Sie sich frei, es in Ihre Antwort aufzunehmen, wenn es irgendeinen Nutzen hat. Würde es Indizes für beide Tabellen verwenden - und wenn ja, wäre es effizienter?
Ypercubeᵀᴹ
@ ypercubeᵀᴹ Es könnte einen Plan wie diesen geben, wenn die Indizes i.stack.imgur.com/RSzcT.png abdecken . In meiner Antwort sehe ich jedoch keinen Vorteil gegenüber dem Plan. Da immer noch alle Zeilen in der äußeren Tabelle gelesen werden müssen, erfolgt gerade über 26 Suchvorgänge statt über einen Scan.
Martin Smith
2

Ich habe zufällig eine Tabelle mit 3.423 Zeilen und 195 verschiedenen Werten in Name. Ich werde diese Tabelle P(Person) aufrufen und sie duplizieren, um sie P2(Person2) zu erstellen . In einer Ganzzahl-ID-Spalte befindet sich ein eindeutiger, gruppierter Primärschlüssel. Ich verwende Microsoft SQL Server 2016 (KB3194716) Developer Edition (64-Bit) unter Windows 10 Pro 6.3 mit 32 GB RAM.

Mit der Basisabfrage

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

Ich erhalte 1,5 Millionen Zeilen in 3200-3300 ms (aus Statistik io).

Geben Sie hier die Bildbeschreibung ein

Durch Umschreiben also -

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

verstrichen reduziert sich auf 50-60ms und der Plan ist:

Geben Sie hier die Bildbeschreibung ein

Aufgrund des Übereinstimmungsalgorithmus werden weniger Zeilen zurückgegeben (3.423). Die gleiche Plan- und Zeilenanzahl wird erreicht, indem die Basisabfrage in geändert wird select distinct.

Durch Erstellen einer indizierten, berechneten Spalte

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

create index ix1 on dbo.p2(Name1);

Die verstrichene Zeit sinkt auf 45-50 ms.

Geben Sie hier die Bildbeschreibung ein

Michael Green
quelle