json文字列をパラメーターとして受け取るクエリがあります。jsonは、緯度と経度のペアの配列です。入力例は次のとおりです。
declare @json nvarchar(max)= N'[[40.7592024,-73.9771259],[40.7126492,-74.0120867]
,[41.8662374,-87.6908788],[37.784873,-122.4056546]]';
それは、1,3,5,10マイルの距離で、地理的なポイントの周りのPOIの数を計算するTVFを呼び出します。
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クエリの目的は、この関数を一括呼び出しすることです。このように呼び出すと、パフォーマンスは非常に低く、わずか4ポイントで10秒近くかかります。
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))
計画= 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)
計画= 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
テーブルで358306論理読み取りを行い、両方とも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
ます。(これは擬似コードであり、適切に適応します。)