Eu tenho as seguintes tabelas em meu banco de dados.
Tabela de Sobrenomes
CREATE TABLE [dbo].[LastNames](
[LastNameID] [int] IDENTITY(1,1) NOT NULL,
[LastName] [varchar](50) NOT NULL
) ON [PRIMARY]
GO
CREATE UNIQUE CLUSTERED INDEX [CIX_LastNames_LastName] ON [dbo].[LastNames]
(
[LastName] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, SORT_IN_TEMPDB = OFF, IGNORE_DUP_KEY = OFF, DROP_EXISTING = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
Tabela de nomes
CREATE TABLE [dbo].[FirstNames](
[FirstNameID] [int] IDENTITY(1,1) NOT NULL,
[FirstName] [varchar](50) NOT NULL
) ON [PRIMARY]
GO
CREATE UNIQUE CLUSTERED INDEX [CIX_FirstNames_FirstName] ON [dbo].[FirstNames]
(
[FirstName] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, SORT_IN_TEMPDB = OFF, IGNORE_DUP_KEY = OFF, DROP_EXISTING = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
Tabela PessoasAnon
CREATE TABLE [dbo].[PersonsAnon](
[PersonID] [int] IDENTITY(1,1) NOT NULL,
[LastNameID] [int] NOT NULL,
[FirstNameID] [int] NOT NULL,
[Info1] [bit] NULL,
[Info2] [char](1) NULL,
[Info3] [nchar](50) NULL,
[AdressID] [int] NULL
) ON [PRIMARY]
GO
CREATE UNIQUE CLUSTERED INDEX [CIX_PersonsAnon_PersonID_LastNameID_FirstNameID] ON [dbo].[PersonsAnon]
(
[PersonID] ASC,
[LastNameID] ASC,
[FirstNameID] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, SORT_IN_TEMPDB = OFF, IGNORE_DUP_KEY = OFF, DROP_EXISTING = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
GO
CREATE NONCLUSTERED INDEX [NIX_PersonsAnon_LastNameID_FirstNameID] ON [dbo].[PersonsAnon]
(
[LastNameID] ASC,
[FirstNameID] ASC
)
INCLUDE([PersonID]) WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, SORT_IN_TEMPDB = OFF, DROP_EXISTING = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
GO
Geração de dados
Para aqueles que possuem um banco de dados AdventureWorks2014 disponível, usei as seguintes instruções para preencher as tabelas:
INSERT INTO dbo.LastNames (LastName) SELECT DISTINCT app.LastName FROM AdventureWorks2016.Person.Person AS app
INSERT INTO dbo.FirstNames (FirstName) SELECT DISTINCT app.FirstName FROM AdventureWorks2016.Person.Person AS app
INSERT INTO dbo.PersonsAnon (LastNameID, FirstNameID)
SELECT ln.LastNameID, fn.FirstNameID FROM LastNames ln CROSS APPLY FirstNames fn
A declaração
A seguinte declaração foi usada para ver que tipo de plano de execução seria criado:
SELECT pa.PersonID,
fn.FirstName,
ln.LastName,
pa.Info2
FROM PersonsAnon AS pa
JOIN LastNames AS ln
ON pa.LastNameID = ln.LastNameID
JOIN FirstNames AS fn
ON pa.FirstNameID = fn.FirstNameID
WHERE ln.LastName LIKE 'Pet%'
AND fn.FirstName LIKE 'John%'
ORDER BY
ln.LastName,
fn.FirstName;
O Plano de Execução de Consulta
Questões
Por que o JOIN é realizado entre Sobrenomes e Nomes mesmo que não haja predicado JOIN? O que está fazendo com que o QO JUNTE as duas tabelas?
Situação Gráfica
Aqui está o que parece:
E os detalhes mostram:
O otimizador decidiu que sua consulta era uma consulta estrela seletiva em tabelas de fatos e dimensões .
O diferencial é a presença de
StarJoinInfo
atributos nas operadoras join do plano (exceto o produto cartesiano, infelizmente). Do meu artigo StarJoinInfo em Planos de Execução :Uma das estratégias disponíveis é “Produto Cartesiano mais Pesquisa de Índice Multicoluna”. A ideia é aplicar um predicado separado a cada uma das tabelas de dimensão , pegar o produto cartesiano dos resultados e usá-lo para buscar ambas as chaves do índice de múltiplas colunas na tabela de fatos . O plano de consulta então executa uma pesquisa (RID ou chave) na tabela de fatos usando identificadores de linha das operações anteriores.
Em outras palavras:
Se você seguir a lógica, verá que essa estratégia retorna os resultados solicitados pela especificação da consulta.