Могу ли я провести сопоставление первых букв на двух столах?

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

SQL Server. Есть ли способ сделать этот SARGable / работать быстрее? Я не могу создать столбцы на персонале таблицы, но я могу создать столбцы на персоны2.

lastchancexi
источник
3
Вы знаете, что результат этого запроса будет своего рода CROSS JOIN, на самом деле?
ypercubeᵀᴹ
1
Насколько большие таблицы? Если каждый из них, скажем, просто 10K строк, результат будет не менее 4 миллионов строк. Интересно, каким будет использование такого запроса.
ypercubeᵀᴹ
1
@ ypercubeᵀᴹ может быть, первоначальный вклад в некоторый процесс дедупликации с использованием нечеткого сопоставления?
Мартин Смит
Похоже, плохая идея. Чего ты здесь пытаешься достичь?
Дэвид דודו Марковиц
Это было только для примера. Есть больше предикатов. У Мартина Смита правильная идея, это для дедупликации.
lastchancexi

Ответы:

9

Создайте представление для таблиц с сохраненным вычисляемым столбцом, определенным как LEFT(lastname, 1)для каждой таблицы, затем сравните вычисленные постоянные значения столбца.

Вот тестовый стенд, показывающий, как это сделать:

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

Здесь мы вставим пример данных:

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

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

Вот SELECTзапрос:

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

И результаты:

+ ---------- + ----------- + ---------- + --------------- - + ---------- + ----------- + ---------- + ------------- ---- +
| PersonID | FirstName | Фамилия | LastNameInitial | PersonID | FirstName | Фамилия | LastNameInitial |
+ ---------- + ----------- + ---------- + --------------- - + ---------- + ----------- + ---------- + ------------- ---- +
| 2 | Джо | Черный | Б | 2 | Джо | Черный | Б |
| 1 | Макс | Вернон | V | 1 | Макс | Вернон | V |
+ ---------- + ----------- + ---------- + --------------- - + ---------- + ----------- + ---------- + ------------- ---- +

План выполнения, только две строки в таблице (правда, не много строк!)

введите описание изображения здесь

Макс Вернон
источник
11

Если lastnameстолбец проиндексирован хотя бы в одной из таблиц, вы также можете использоватьLIKE

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

введите описание изображения здесь

План для этого может иметь поиск на столе, указанном слева от подобного.

т. е. ON p.lastname LIKE LEFT(p2.lastname, 1) + '%'не сможет использовать индекс, persons2который использовался выше, но может искать его persons.

Однако предложение в другом ответе об индексировании вычисляемого столбца с обеих сторон является более гибким. Что касается плана вложенных циклов, то любая таблица может быть внутри, и это также позволило бы объединить слияние многих-многих без необходимости сортировки.

Мартин Смит
источник
как насчет этого подхода ? Не стесняйтесь добавлять это в своем ответе, если это имеет какую-либо выгоду. Будет ли он использовать индексы для обеих таблиц - и если да, будет ли он более эффективным?
ypercubeᵀᴹ
@ ypercubeᵀᴹ Такой план может быть таким, если индексы охватывают i.stack.imgur.com/RSzcT.png . Я не вижу никакого преимущества перед планом в своем ответе. Так как все равно придется читать все строки во внешней таблице, просто через 26 запросов, а не одно сканирование.
Мартин Смит
2

У меня есть таблица с 3423 строками и 195 различными значениями в Name. Я назову эту таблицу P(person) и продублирую ее P2(person2). В столбце с целочисленным идентификатором имеется уникальный кластерный первичный ключ. Я использую Microsoft SQL Server 2016 (KB3194716) Developer Edition (64-разрядная версия) в Windows 10 Pro 6.3 с 32 ГБ ОЗУ.

С базовым запросом

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

Я получаю 1,5 млн строк, возвращаемых за 3200-3300 мс (из статистики io).

введите описание изображения здесь

Переписав таким образом -

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

истекло, сокращается до 50-60мс, и план:

введите описание изображения здесь

Из-за алгоритма сопоставления возвращается меньше строк (3423). Тот же план и количество строк достигается путем изменения базового запроса на select distinct.

Создав индексированный, вычисляемый столбец

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

create index ix1 on dbo.p2(Name1);

Прошедшее время падает до 45-50 мс.

введите описание изображения здесь

Майкл Грин
источник