Чи можу я виконати збірну першу букву на двох таблицях?


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

SQL Server. Чи є спосіб зробити цей SARGable / запустити швидше? Я не можу створювати стовпці на таблиці осіб, але я можу створювати стовпці на person2.


3
Ви знаєте, що результат цього запиту насправді буде своєрідним CROSS JOIN?
ypercubeᵀᴹ

1
Наскільки великі столи? Якщо кожен з них скаже лише 10 К рядків, результат буде щонайменше 4 мільйони рядків. Цікаво, якою буде користь від такого запиту.
ypercubeᵀᴹ

1
@ ypercubeᵀᴹ, можливо, початковий внесок у якийсь процес дедупликації, використовуючи нечітке узгодження?
Мартін Сміт

Звучить погана ідея. Чого ви намагаєтеся досягти тут?
David דודו Markovitz

Це було лише для прикладу. Є більше предикатів. У Мартіна Сміта є правильна ідея, це для дедуплікації.
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;

І результати:

+ ---------- + ----------- + ---------- + --------------- - + ---------- + ----------- + ---------- + ------------- ---- +
| ОсобаID | FirstName | LastName | LastNameInitial | ОсобаID | FirstName | LastName | 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

У мене, мабуть, є таблиця з 3,423 рядками та 195 різними значеннями Name. Я зателефоную до цієї таблиці P(людина) і дублюю її для створення P2(person2). У цілому стовпчику ідентифікатора є унікальний кластерний первинний ключ. Я використовую Microsoft SQL Server 2016 (KB3194716) для розробників (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 мс, і план:

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

Менше рядків повертається (3,423) через алгоритм відповідності. Один і той же план і кількість рядків досягається зміною базового запиту на select distinct.

Створюючи індексований обчислений стовпчик

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

create index ix1 on dbo.p2(Name1);

Час, що минув, падає до 45-50мс.

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

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.