Mam zapytanie, które bierze ciąg json jako parametr. Json to tablica par szerokości i długości geograficznej. Przykładowe dane wejściowe mogą być następujące.
declare @json nvarchar(max)= N'[[40.7592024,-73.9771259],[40.7126492,-74.0120867]
,[41.8662374,-87.6908788],[37.784873,-122.4056546]]';
Wzywa TVF, który oblicza liczbę punktów POI w pobliżu punktu geograficznego, w odległości 1,3,5,10 mil.
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
Celem zapytania json jest zbiorcze wywołanie tej funkcji. Jeśli nazywam to w ten sposób, wydajność jest bardzo niska i zajmuje prawie 10 sekund za zaledwie 4 punkty:
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
Jednak przesunięcie konstrukcji geograficznej wewnątrz tabeli pochodnej powoduje znaczną poprawę wydajności, wypełniając zapytanie w około 1 sekundę.
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
Plany wyglądają praktycznie identycznie. Żadne z nich nie używa równoległości i oba wykorzystują indeks przestrzenny. Na wolnym planie jest dodatkowa leniwa szpula, którą mogę wyeliminować za pomocą podpowiedzi option(no_performance_spool)
. Ale wydajność zapytania nie zmienia się. Nadal pozostaje znacznie wolniejszy.
Uruchomienie obu z dodaną wskazówką w partii spowoduje równe ważenie obu zapytań.
Wersja serwera SQL = Microsoft SQL Server 2016 (SP1-CU7-GDR) (KB4057119) - 13.0.4466.4 (X64)
Więc moje pytanie brzmi: dlaczego to ma znaczenie? Skąd mam wiedzieć, kiedy powinienem obliczyć wartości w tabeli pochodnej, czy nie?
point_of_interest
tabeli, oba skanują indeks 4602 razy i oba generują stół roboczy i plik roboczy. Estymator uważa, że te plany są identyczne, ale wyniki mówią inaczej.
|LatLong.Lat - @geo.Lat| + |LatLong.Long - @geo.Long| < n
przed wykonaniem jest bardziej skomplikowana sqrt((LatLong.Lat - @geo.Lat)^2 + (LatLong.Long - @geo.Long)^2)
. A jeszcze lepiej, najpierw oblicz górną i dolną granicę LatLong.Lat > @geoLatLowerBound && LatLong.Lat < @geoLatUpperBound && LatLong.Long > @geoLongLowerBound && LatLong.Long < @geoLongUpperBound
. (To pseudokod, odpowiednio się dostosuj).