以下查询在 SQL Server 2017 中遇到错误:
;with locations(RowNum, Latitude, Longitude) as (
select 1, 12.3456, 45.6789
),
locationsWithPrevious as (
select *,
PreviousLatitude = lag(l.Latitude) over(order by l.RowNum),
PreviousLongitude = lag(l.Longitude) over(order by l.RowNum)
from locations as l
),
locationsWithDistance as (
select *,
Distance = geography::Point(l.Latitude, l.Longitude, 4326).STDistance(geography::Point(l.PreviousLatitude, l.PreviousLongitude, 4326))
from locationsWithPrevious as l
where PreviousLatitude is not null
and PreviousLongitude is not null
)
select *
from locationsWithDistance as l
where Distance > 0
Msg 6569, Level 16, State 1, Line 1
'geography::Point' failed because parameter 1 is not allowed to be null.
原因:
谓词 Distance > 0
在将 PreviousLatitude/-Longitude
过滤为 IS NOT NULL
之前执行。
到目前为止一切顺利,因为 T-SQL 是声明性的,并且这里的操作顺序可以由 SQL Server 确定。
如果删除谓词 Distance > 0
,查询将正常运行,不会出现错误。
但现在我希望可以通过使用 ISNULL
函数来防止参数的 NULL
值,如下所示:
Distance = geography::Point(l.Latitude, l.Longitude, 4326).STDistance(geography::Point(isnull(l.PreviousLatitude, 0), isnull(l.PreviousLongitude, 0), 4326))
但是查询仍然返回相同的错误!
ISNULL
函数也没有在执行计划的过滤谓词中的任何位置列出!
SQL Server 的这种行为正确吗?
在我看来,由于 IS NOT NULL
过滤,SQL Server 错误地删除了 ISNULL
调用。
注释:
删除
IS NOT NULL
条件后,错误就会消失,因为现在按预期在过滤谓词中使用了ISNULL
函数(但查询已当然,语义上发生了变化):locationsWithDistance as ( select *, Distance = geography::Point(l.Latitude, l.Longitude, 4326).STDistance(geography::Point(isnull(l.PreviousLatitude, 0), isnull(l.PreviousLongitude, 0), 4326)) from locationsWithPrevious as l )
但是,如果您将
ISNULL
调用替换为CASE WHEN
操作,则查询可以正常工作:Distance = geography::Point(l.Latitude, l.Longitude, 4326).STDistance(geography::Point(case when l.PreviousLatitude is not null then l.PreviousLatitude else 0 end, case when l.PreviousLongitude is not null then l.PreviousLongitude else 0 end, 4326))
我还意识到,通过直接在基本查询中实例化
Point
,可以更好地制定查询,如下所示:;with locations(RowNum, GeoPosition) as ( select 1, geography::Point(12.3456, 45.6789, 4326) ), locationsWithPrevious as ( select *, PreviousGeoPosition = lag(l.GeoPosition) over(order by l.RowNum) from locations as l ), locationsWithDistance as ( select *, Distance = l.GeoPosition.STDistance(l.PreviousGeoPosition) from locationsWithPrevious as l where PreviousGeoPosition is not null ) select * from locationsWithDistance as l where Distance > 0
最佳答案
这确实是一个错误。编译器认为该值可证明不为空,并删除了 ISNULL
。但是,COALESCE
不会以同样的方式受到影响,它会编译为 CASE
并且编译器对其没有太多可见性。
编译器将中间计算放入计算标量
运算符中。但这些可以计算 at different points by the Expression Service ,因此 ISNULL
不应该被删除。
正如您所发现的,一种解决方法是删除 WHERE
。
另一种方法是使用 LAG
上的额外参数来添加默认值
;with locations(RowNum, Latitude, Longitude) as (
select 1, 12.3456, 45.6789
),
locationsWithPrevious as (
select *,
PreviousLatitude = lag(l.Latitude, 1, 0) over(order by l.RowNum),
PreviousLongitude = lag(l.Longitude, 1, 0) over(order by l.RowNum)
from locations as l
),
locationsWithDistance as (
select *,
Distance = geography::Point(l.Latitude, l.Longitude, 4326).STDistance(geography::Point(l.PreviousLatitude, l.PreviousLongitude, 4326))
from locationsWithPrevious as l
)
select *
from locationsWithDistance as l
where Distance > 0
关于SQL Server 优化器删除 ISNULL 调用,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/69941734/