Ho una query che accetta una stringa JSON come parametro. Il json è una matrice di coppie di latitudine e longitudine. Un input di esempio potrebbe essere il seguente.
declare @json nvarchar(max)= N'[[40.7592024,-73.9771259],[40.7126492,-74.0120867]
,[41.8662374,-87.6908788],[37.784873,-122.4056546]]';
Chiama un TVF che calcola il numero di PDI attorno a un punto geografico, a distanze di 1,3,5,10 miglia.
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
L'intento della query json è chiamare in blocco questa funzione. Se lo chiamo così, le prestazioni sono molto scarse e impiegano quasi 10 secondi per soli 4 punti:
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
Tuttavia, spostando la costruzione della geografia all'interno di una tabella derivata, le prestazioni migliorano notevolmente, completando la query in circa 1 secondo.
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
I piani sembrano praticamente identici. Nessuno dei due usa il parallelismo ed entrambi usano l'indice spaziale. C'è un rocchetto pigro aggiuntivo sul piano lento che posso eliminare con il suggerimento option(no_performance_spool)
. Ma le prestazioni della query non cambiano. Rimane ancora molto più lento.
L'esecuzione di entrambi con il suggerimento aggiunto in un batch peserà equamente entrambe le query.
Versione server SQL = Microsoft SQL Server 2016 (SP1-CU7-GDR) (KB4057119) - 13.0.4466.4 (X64)
Quindi la mia domanda è: perché è importante? Come posso sapere quando dovrei calcolare i valori all'interno di una tabella derivata o no?
point_of_interest
tabella, entrambi scansionano l'indice 4602 volte ed entrambi generano un worktable e un file di lavoro. Lo stimatore ritiene che questi piani siano identici ma le prestazioni dicono diversamente.
|LatLong.Lat - @geo.Lat| + |LatLong.Long - @geo.Long| < n
prima di fare il più complicato sqrt((LatLong.Lat - @geo.Lat)^2 + (LatLong.Long - @geo.Long)^2)
. E ancora meglio, calcolare prima i limiti superiore e inferiore, quindi LatLong.Lat > @geoLatLowerBound && LatLong.Lat < @geoLatUpperBound && LatLong.Long > @geoLongLowerBound && LatLong.Long < @geoLongUpperBound
. (Questo è pseudocodice, adattarsi in modo appropriato.)