Posso fazer uma correspondência sargável da primeira letra em duas mesas?

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

Servidor SQL. Existe alguma maneira de tornar este SARGable / executado mais rápido? Não consigo criar colunas na tabela de pessoas, mas posso criar colunas na pessoa2.

lastchancexi
fonte
3
Você sabe que o resultado dessa consulta será um tipo de CROSS JOIN, na verdade?
precisa saber é o seguinte
11
Qual o tamanho das mesas? Se cada um deles diz apenas meras 10 mil linhas, o resultado será de pelo menos 4 milhões de linhas. Gostaria de saber qual será o uso dessa consulta.
precisa saber é o seguinte
11
@ ypercubeᵀᴹ talvez seja uma entrada inicial para algum processo de desduplicação usando correspondência difusa?
Martin Smith
Parece uma má ideia. O que você está tentando alcançar aqui?
David Markovitz
Isso foi apenas por exemplo. Existem mais predicados. Martin Smith tem a idéia certa, é para desduplicação.
lastchancexi

Respostas:

9

Crie uma visualização nas tabelas com uma coluna computada persistente definida como a LEFT(lastname, 1)de cada tabela e compare os valores da coluna persistida calculada.

Aqui está uma bancada de testes mostrando como fazer isso:

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

Aqui, inseriremos alguns dados de amostra:

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

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

Aqui está a SELECTconsulta:

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

E os resultados:

+ ---------- + ----------- + ---------- + --------------- - + ---------- + ----------- + ---------- + ------------- ---- +
| PersonID | Nome | Sobrenome | LastNameInitial | PersonID | Nome | Sobrenome | LastNameInitial |
+ ---------- + ----------- + ---------- + --------------- - + ---------- + ----------- + ---------- + ------------- ---- +
| 2 Joe Preto B 2 Joe Preto B
| 1 | Max Vernon V 1 | Max Vernon V
+ ---------- + ----------- + ---------- + --------------- - + ---------- + ----------- + ---------- + ------------- ---- +

O plano de execução, com apenas duas linhas por tabela (reconhecidamente não muitas linhas!)

insira a descrição da imagem aqui

Max Vernon
fonte
11

Se a lastnamecoluna estiver indexada em pelo menos uma das tabelas, você também poderá usarLIKE

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

insira a descrição da imagem aqui

O plano para isso pode ter uma busca na tabela especificada à esquerda do gosto.

ou seja ON p.lastname LIKE LEFT(p2.lastname, 1) + '%', não seria capaz de usar o índice persons2usado anteriormente, mas poderia buscá-lo persons.

A sugestão na outra resposta de indexar uma coluna calculada de ambos os lados é mais flexível, no entanto. Quanto a um plano de loops aninhados, qualquer tabela pode estar no interior e também permitiria a junção de muitos a muitos sem precisar de uma classificação.

Martin Smith
fonte
e essa abordagem ? Sinta-se à vontade para adicioná-lo em sua resposta, se houver algum benefício. Seria usar índices em ambas as tabelas - e se sim, seria mais eficiente?
precisa saber é o seguinte
@ ypercubeᵀᴹ Poderia dar um plano como esse se os índices cobrirem i.stack.imgur.com/RSzcT.png . Não estou vendo nenhuma vantagem sobre o plano na minha resposta. Como ainda será necessário ler todas as linhas da tabela externa, agora, através de 26 buscas, em vez de uma verificação.
Martin Smith
2

Por acaso, tenho uma tabela com 3.423 linhas e 195 valores distintos Name. Vou chamar essa tabela P(pessoa) e duplicá-la para criar P2(pessoa2). Há uma chave primária em cluster exclusiva em uma coluna de ID inteiro. Estou usando o Microsoft SQL Server 2016 (KB3194716) Developer Edition (64 bits) no Windows 10 Pro 6.3 com 32 GB de RAM.

Com a consulta base

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

Recebo 1.5M linhas retornadas em 3200-3300ms (das estatísticas io).

insira a descrição da imagem aqui

Reescrevendo assim -

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

decorrido reduz para 50-60ms e o plano é:

insira a descrição da imagem aqui

Menos linhas são retornadas (3.423) devido ao algoritmo de correspondência. O mesmo plano e contagem de linhas são alcançados alterando a consulta base para select distinct.

Criando coluna computada indexada

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

create index ix1 on dbo.p2(Name1);

O tempo decorrido cai para 45-50ms.

insira a descrição da imagem aqui

Michael Green
fonte