¿Por qué el plan de ejecución de consultas SELECT COUNT () incluye la tabla unida a la izquierda?

9

En SQL Server 2012, tengo una función con valores de tabla y unirme a otra tabla. Necesito contar el número de filas para esta 'función con valores de tabla'. Cuando inspecciono el plan de ejecución, puedo ver la tabla de unión izquierda. ¿Por qué? ¿Cómo puede la tabla unida izquierda influir en el número de filas devueltas? Esperaría que el motor de db no necesite evaluar la tabla conjunta izquierda en la consulta SELECT count (..).

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

El plan de ejecución:

ingrese la descripción de la imagen aquí

La función de tabla de valores:

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
)

ACTUALIZAR:

Agrego un índice único para seguir la idea de Rob Farley:

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

E indexado sugerido por DB Engine:

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

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

Por simplicidad elimino la condición

WHERE Person.ConfirmStatus = 1

de la función con valores de la tabla anterior.

Ahora el plan de ejecución es mucho más simple pero aún toca la tabla ExternFile:

ingrese la descripción de la imagen aquí

¿Quizás el servidor SQL no es lo suficientemente inteligente?

Tomás Kubes
fuente

Respuestas:

12

Si ForeignId, ForeignTable, IsMainno se sabe que * es único ExternFile, entonces el QO deberá incluir esa tabla para calcular el recuento. Cada vez que coinciden varias filas, el recuento se verá afectado.

Simplificación de unión en el
diseño de SQL Server para simplificación (grabación de SQLBits)


* El optimizador no reconoce actualmente los índices únicos filtrados como únicos

ACTUALIZACIÓN (por OP) : la solución es cambiar la línea en la consulta desde LEFT JOIN (que puede producir varias filas):

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

APLICAR EXTERIOR con TOP (que produce una fila y no afecta a COUNT)

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

La consulta ahora es más efectiva. No se pudo agregar un índice único, porque los valores no eran únicos, eran únicos solo para la combinación de la condición y esto no se considera único como se mencionó anteriormente.

Rob Farley
fuente