Por que o plano de execução da consulta SELECT COUNT () inclui a tabela unida à esquerda?

9

No SQL Server 2012, tenho a função com valor de tabela com ingresso em outra tabela e preciso contar o número de linhas para essa 'função com valor de tabela'. Quando inspeciono o plano de execução, posso ver a tabela de junção esquerda. Por quê? Como a tabela unida à esquerda influencia o número de linhas retornadas? Eu esperaria que o mecanismo db não precise avaliar a tabela de junção esquerda na consulta SELECT count (..).

Select count(realtyId) FROM [dbo].[GetFilteredRealtyFulltext]('"praha"')

O plano de execução:

insira a descrição da imagem aqui

A função com valor de tabela:

CREATE FUNCTION [dbo].[GetFilteredRealtyFulltext]
(@criteria nvarchar(4000))
RETURNS TABLE
AS
RETURN (SELECT 
realty.Id AS realtyId,
realty.OwnerId,
realty.Caption AS realtyCaption,
realty.BusinessCategory,
realty.Created,
realty.LastChanged,
realty.LastChangedType,
realty.Price,
realty.Pricing,
realty.PriceCurrency,
realty.PriceNote,
realty.PricePlus,
realty.OfferState,
realty.OrderCode,
realty.PublishAddress,
realty.PublishMap,
realty.AreaLand,
realty.AreaCover,
realty.AreaFloor,
realty.Views,
realty.TopPoints,
realty.Radius,
COALESCE(realty.Wgs84X, ruian_cobce.Wgs84X, ruian_obec.Wgs84X) as Wgs84X,
COALESCE(realty.Wgs84Y, ruian_cobce.Wgs84Y, ruian_obec.Wgs84Y) as Wgs84Y,
realty.krajId,
realty.okresId,
realty.obecId,
realty.cobceId,
IsNull(CONVERT(int,realty.Ranking),0) as Ranking,

realty.energy_efficiency_rating,
realty.energy_performance_attachment,
realty.energy_performance_certificate,
realty.energy_performance_summary,

Category.Id AS CategoryId,
Category.ParentCategoryId,
Category.WholeName,
okres.nazev AS okres,
ruian_obec.nazev AS obec,
ruian_cobce.nazev AS cobce,
ExternFile.ServerPath,
Person.ParentPersonId,
( COALESCE(ftR.Rank,0) + COALESCE(ftObec.Rank,0) + COALESCE(ftOkres.Rank,0) + COALESCE(ftpobvod.Rank,0)) AS FtRank

FROM realty
JOIN Category ON realty.CategoryId = Category.Id
LEFT JOIN ruian_cobce ON realty.cobceId = ruian_cobce.cobce_kod
LEFT JOIN ruian_obec ON realty.obecId = ruian_obec.obec_kod
LEFT JOIN okres ON realty.okresId = okres.okres_kod
LEFT JOIN ExternFile ON realty.Id = ExternFile.ForeignId AND ExternFile.IsMain = 1 AND ExternFile.ForeignTable = 5
INNER JOIN Person ON realty.OwnerId = Person.Id
Left JOIN CONTAINSTABLE(Realty, *, @criteria) ftR ON realty.Id = ftR.[Key] 
Left JOIN CONTAINSTABLE(ruian_obec, *, @criteria) ftObec ON realty.obecId = ftObec.[Key] 
Left JOIN CONTAINSTABLE(Okres, *, @criteria) ftOkres ON realty.okresId = ftOkres.[Key]
Left JOIN CONTAINSTABLE(pobvod, *, @criteria) ftpobvod ON realty.pobvodId = ftpobvod.[Key]
WHERE Person.ConfirmStatus = 1
AND ( COALESCE(ftR.Rank,0) + COALESCE(ftObec.Rank,0) + COALESCE(ftOkres.Rank,0) + COALESCE(ftpobvod.Rank,0))  > 0
)

ATUALIZAR:

Eu adiciono índice exclusivo para seguir a ideia de Rob Farley:

 Create unique nonclustered index ExternFileIsMainUnique ON ExternFile(ForeignId) WHERE IsMain = 1 AND ForeignTable = 5

E indexado sugerido pelo DB Engine:

CREATE NONCLUSTERED INDEX [RealtyOwnerLocation] ON [dbo].[Realty]

([OwnerId] ASC) INCLUI ([Id], [okresId], [obecId], [pobvodId]) GO

Para simplificar, removo a condição

WHERE Person.ConfirmStatus = 1

da função avaliada apresentada acima.

Agora, o plano de execução é muito mais simples, mas ainda toca a tabela ExternFile:

insira a descrição da imagem aqui

Talvez o servidor sql não seja inteligente o suficiente?

Tomas Kubes
fonte

Respostas:

12

Se ForeignId, ForeignTable, IsMainnão for conhecido * como exclusivo ExternFile, o QO precisará incluir essa tabela para calcular a contagem. Sempre que várias linhas corresponderem, a contagem será afetada.

Junte-se à simplificação no SQL Server
Designing para simplificação (gravação SQLBits)


* O otimizador atualmente não reconhece índices exclusivos filtrados como exclusivos

UPDATE (por OP) : A solução é alterar a linha na consulta de LEFT JOIN (que pode produzir várias linhas):

LEFT JOIN ExternFile ON realty.Id = ExternFile.ForeignId AND ExternFile.IsMain = 1 AND ExternFile.ForeignTable = 5

EXTERIOR APLICAR com TOP (que produz uma linha e não afeta COUNT)

OUTER APPLY (SELECT TOP (1) ServerPath FROM ExternFile WHERE ForeignId = realty.Id AND IsMain = 1 AND ForeignTable = 5) AS ExternFile

A consulta agora é mais eficaz. Não foi possível adicionar um índice exclusivo, porque os valores não eram únicos, eles eram únicos apenas para combinação na condição e isso não é considerado tão exclusivo como mencionado acima.

Rob Farley
fonte