Tengo una consulta que toma una cadena json como parámetro. El json es un conjunto de pares de latitud y longitud. Un ejemplo de entrada podría ser el siguiente.
declare @json nvarchar(max)= N'[[40.7592024,-73.9771259],[40.7126492,-74.0120867]
,[41.8662374,-87.6908788],[37.784873,-122.4056546]]';
Llama a un TVF que calcula la cantidad de PDI alrededor de un punto geográfico, a distancias de 1,3,5,10 millas.
create or alter function [dbo].[fn_poi_in_dist](@geo geography)
returns table
with schemabinding as
return
select count_1 = sum(iif(LatLong.STDistance(@geo) <= 1609.344e * 1,1,0e))
,count_3 = sum(iif(LatLong.STDistance(@geo) <= 1609.344e * 3,1,0e))
,count_5 = sum(iif(LatLong.STDistance(@geo) <= 1609.344e * 5,1,0e))
,count_10 = count(*)
from dbo.point_of_interest
where LatLong.STDistance(@geo) <= 1609.344e * 10
La intención de la consulta json es llamar en masa a esta función. Si lo llamo así, el rendimiento es muy pobre, tomando casi 10 segundos por solo 4 puntos:
select row=[key]
,count_1
,count_3
,count_5
,count_10
from openjson(@json)
cross apply dbo.fn_poi_in_dist(
geography::Point(
convert(float,json_value(value,'$[0]'))
,convert(float,json_value(value,'$[1]'))
,4326))
plan = https://www.brentozar.com/pastetheplan/?id=HJDCYd_o4
Sin embargo, mover la construcción de la geografía dentro de una tabla derivada hace que el rendimiento mejore dramáticamente, completando la consulta en aproximadamente 1 segundo.
select row=[key]
,count_1
,count_3
,count_5
,count_10
from (
select [key]
,geo = geography::Point(
convert(float,json_value(value,'$[0]'))
,convert(float,json_value(value,'$[1]'))
,4326)
from openjson(@json)
) a
cross apply dbo.fn_poi_in_dist(geo)
plan = https://www.brentozar.com/pastetheplan/?id=HkSS5_OoE
Los planes se ven prácticamente idénticos. Ninguno usa paralelismo y ambos usan el índice espacial. Hay un carrete perezoso adicional en el plan lento que puedo eliminar con la pista option(no_performance_spool)
. Pero el rendimiento de la consulta no cambia. Sigue siendo mucho más lento.
Ejecutar ambos con la sugerencia agregada en un lote pesará ambas consultas por igual.
Versión del servidor SQL = Microsoft SQL Server 2016 (SP1-CU7-GDR) (KB4057119) - 13.0.4466.4 (X64)
Entonces mi pregunta es ¿por qué esto importa? ¿Cómo puedo saber cuándo debo calcular valores dentro de una tabla derivada o no?
point_of_interest
tabla, ambos escanean el índice 4602 veces y ambos generan una tabla de trabajo y un archivo de trabajo. El estimador cree que estos planes son idénticos pero el rendimiento dice lo contrario.
|LatLong.Lat - @geo.Lat| + |LatLong.Long - @geo.Long| < n
antes de hacerlo sea más complicado sqrt((LatLong.Lat - @geo.Lat)^2 + (LatLong.Long - @geo.Long)^2)
. Y aún mejor, primero calcule los límites superior e inferior LatLong.Lat > @geoLatLowerBound && LatLong.Lat < @geoLatUpperBound && LatLong.Long > @geoLongLowerBound && LatLong.Long < @geoLongUpperBound
. (Esto es pseudocódigo, adaptarse adecuadamente.)