У меня есть запрос, который принимает строку JSON в качестве параметра. JSON - это массив пар широты и долготы. Пример ввода может быть следующим.
declare @json nvarchar(max)= N'[[40.7592024,-73.9771259],[40.7126492,-74.0120867]
,[41.8662374,-87.6908788],[37.784873,-122.4056546]]';
Он вызывает TVF, который вычисляет количество POI вокруг географической точки на расстоянии 1,3,5,10 мили.
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
Целью запроса json является массовый вызов этой функции. Если я назову это так, производительность будет очень плохой и займет около 10 секунд всего за 4 балла:
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
Однако перемещение построения географии внутри производной таблицы приводит к значительному улучшению производительности, завершая запрос примерно за 1 секунду.
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
Планы выглядят практически идентичными. Ни один из них не использует параллелизм, и оба используют пространственный индекс. На медленном плане есть дополнительная ленивая шпуля, которую я могу устранить с помощью подсказки option(no_performance_spool)
. Но производительность запроса не меняется. Это все еще остается намного медленнее.
Запуск обоих с добавленной подсказкой в пакете будет взвешивать оба запроса одинаково.
Версия Sql-сервера = Microsoft SQL Server 2016 (SP1-CU7-GDR) (KB4057119) - 13.0.4466.4 (X64)
Итак, мой вопрос, почему это имеет значение? Как я могу узнать, когда мне следует вычислять значения внутри производной таблицы или нет?
point_of_interest
таблицы, оба сканируют индекс 4602 раза, и оба генерируют рабочий стол и рабочий файл. Оценщик полагает, что эти планы идентичны, но показатели говорят об обратном.
|LatLong.Lat - @geo.Lat| + |LatLong.Long - @geo.Long| < n
перед вами сложнее sqrt((LatLong.Lat - @geo.Lat)^2 + (LatLong.Long - @geo.Long)^2)
. А еще лучше, сначала рассчитайте верхнюю и нижнюю границы LatLong.Lat > @geoLatLowerBound && LatLong.Lat < @geoLatUpperBound && LatLong.Long > @geoLongLowerBound && LatLong.Long < @geoLongUpperBound
. (Это псевдокод, адаптируйтесь соответствующим образом.)