Pourquoi le plan d'exécution de requête SELECT COUNT () inclut une table jointe à gauche?

9

Dans SQL Server 2012, j'ai une fonction de valeur de table avec jointure à une autre table, je dois compter le nombre de lignes pour cette «fonction de valeur de table». Lorsque j'inspecte le plan d'exécution, je peux voir la table de jointure gauche. Pourquoi? Comment le tableau joint à gauche peut-il influencer le nombre de lignes renvoyées? Je m'attendrais à ce que le moteur db n'ait pas besoin d'évaluer la table conjointe gauche dans la requête SELECT count (..).

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

Le plan d'exécution:

entrez la description de l'image ici

La fonction table value:

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
)

MISE À JOUR:

J'ajoute un index unique pour suivre l'idée de Rob Farley:

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

Et indexé suggéré par DB Engine:

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

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

Pour plus de simplicité, je supprime la condition

WHERE Person.ConfirmStatus = 1

de la fonction de valeur présentée ci-dessus.

Maintenant, le plan d'exécution est beaucoup plus simple mais il touche toujours la table ExternFile:

entrez la description de l'image ici

Peut-être que le serveur SQL n'est pas assez intelligent?

Tomas Kubes
la source

Réponses:

12

Si l' ForeignId, ForeignTable, IsMainon ne sait pas * qu'il est unique ExternFile, le QO devra inclure cette table pour calculer le nombre. Chaque fois que plusieurs lignes correspondent, le nombre sera affecté.

Rejoignez la simplification dans SQL Server
Designing for simplification (enregistrement SQLBits)


* L'optimiseur ne reconnaît pas actuellement les index uniques filtrés comme uniques

MISE À JOUR (par OP) : La solution est de changer la ligne dans la requête de LEFT JOIN (qui peut produire plusieurs lignes):

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

à OUTER APPLY avec TOP (qui produit une ligne et n'affecte pas COUNT)

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

La requête est désormais plus efficace. L'ajout d'un index unique n'a pas pu être effectué, car les valeurs n'étaient pas uniques, elles étaient uniques uniquement pour la combinaison dans la condition et cela n'est pas considéré comme unique comme mentionné ci-dessus.

Rob Farley
la source