我有一个查询,需要一个json字符串作为参数。json是纬度,经度对的数组。输入示例如下。
declare @json nvarchar(max)= N'[[40.7592024,-73.9771259],[40.7126492,-74.0120867]
,[41.8662374,-87.6908788],[37.784873,-122.4056546]]';
它调用一个TVF,该TVF可以计算在1、3、5、10英里距离处某个地理位置周围的POI数量。
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 Server版本= 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
。(这是伪代码,请适当调整。)