如何强制 SQL Server 通过视图使用我的空间索引?

Mic*_*l B 6 sql-server spatial sql-server-2016

我有一些表,其中包含存储为 lat long 对的属性的事务。(然后在我的示例架构中有更多的列和数据点)。

一个常见的请求是查找特定点 X 英里范围内发生的交易,并且只检索附近每个房产发生的 5 个最近的交易。

为了完成这项工作,我决定添加一个视图来封装最新的逻辑:

create or alter view dbo.v_example
with schemabinding as
select example_id
      ,transaction_dt
      ,latitude
      ,longitude
      ,latlong
      ,most_recent= iif(row_number() over (partition by latitude,longitude order by transaction_dt desc) < 5,1,null)
from dbo.example;
Run Code Online (Sandbox Code Playgroud)

所以一个查询可能看起来像这样:

create or alter view dbo.v_example
with schemabinding as
select example_id
      ,transaction_dt
      ,latitude
      ,longitude
      ,latlong
      ,most_recent= iif(row_number() over (partition by latitude,longitude order by transaction_dt desc) < 5,1,null)
from dbo.example;
Run Code Online (Sandbox Code Playgroud)

不幸的是,当我通过视图查询时,SQL Server 不想使用空间索引。如果我删除schemabinding并尝试在视图上添加提示,我会发现查询处理器无法创建计划。

如何封装逻辑并仍然让它使用我的空间索引?

这是一个带有示例数据和计划形状的 db<>fiddle

该表要大得多,扫描它然后进行聚集索引查找然后逐点查找要慢得多。

Ran*_*gen 5

窗口函数和视图

最近我回答了一个关于视图和窗口函数的不同问题,但并非所有给出的答案都适用于这里。

两个不同之处在于此示例geography在窗口函数上使用数据类型和过滤器。这里正的部分是,你是不是绑定到窗口功能,可以使用像CROSS APPLY得到最接近的结果一个latitudelongitude组合。

上一个问题+答案

简而言之,窗口函数是在应用过滤之前计算的。

例如

where latlong.STDistance(geography::Point(40,-74,4326)) <=1609.344e1
and most_recent = 1
Run Code Online (Sandbox Code Playgroud)

更多信息可以在Paul White 2013 年关于窗口函数和视图的博客文章中找到。

测试

作为第一个测试,ROW_NUMBER()从视图中省略时结果很清楚

create or alter view dbo.v_example2
with schemabinding as
select example_id
      ,transaction_dt
      ,latitude
      ,longitude
      ,latlong
from dbo.example;

set statistics xml on;
select *
from dbo.v_example2
where latlong.STDistance(geography::Point(40,-74,4326)) <=1609.344e1
Run Code Online (Sandbox Code Playgroud)

产生了预期的高性能查询计划。

数据库<>小提琴


但你显然仍然想要额外的过滤

保持视图+窗口功能

您可以选择在之后添加窗口函数,如下所示:

create or alter view dbo.v_example2
with schemabinding as
select example_id
      ,transaction_dt
      ,latitude
      ,longitude
      ,latlong
from dbo.example;
Run Code Online (Sandbox Code Playgroud)

&

set statistics xml on;
;WITH CTE AS
(
select most_recent= iif(row_number() over (partition by latitude,longitude order by transaction_dt desc) < 5,1,null)
,*
from dbo.v_example2
where latlong.STDistance(geography::Point(40,-74,4326)) <=1609.344e1
)
SELECT
* 
FROM CTE
WHERE most_recent is not null;
Run Code Online (Sandbox Code Playgroud)

再次产生预期的执行计划。

数据库<>小提琴


使用内联表值函数+窗口函数

CREATE FUNCTION dbo.F_Example
(
    @P1 INT

)  
RETURNS TABLE
WITH SCHEMABINDING AS
RETURN
    (
SELECT example_id
      ,transaction_dt
      ,latitude
      ,longitude
      ,latlong
      ,most_recent FROM
(
select example_id
      ,transaction_dt
      ,latitude
      ,longitude
      ,latlong
      ,most_recent= iif(row_number() over (partition by latitude,longitude order by transaction_dt desc) < 5,1,null)
from dbo.example
WHERE latlong.STDistance(geography::Point(40,-74,4326)) <= 1609.344e1
) AS A
WHERE most_recent= @P1
);

EXEC SP_EXECUTESQL
N'SELECT * FROM  dbo.F_Example(@P1)',N'@P1 INT',@P1 = 1
Run Code Online (Sandbox Code Playgroud)

产生您预期的查询计划。

数据库<>小提琴

使用 CTE + 窗口函数

;WITH CTE AS
(
select example_id
      ,transaction_dt
      ,latitude
      ,longitude
      ,latlong
      ,most_recent= row_number() over (partition by latitude,longitude order by transaction_dt desc)
FROM dbo.example
WHERE latlong.STDistance(geography::Point(40,-74,4326)) <=1609.344e1
)

SELECT example_id
      ,transaction_dt
      ,latitude
      ,longitude
      ,latlong
      ,most_recent
FROM CTE2
WHERE most_recent < 5;
Run Code Online (Sandbox Code Playgroud)

使用CROSS APPLYTOP(4) 不使用窗口函数

SELECT example_id
      ,a.transaction_dt
      ,latitude
      ,longitude
      ,latlong
from dbo.example e1
CROSS APPLY(
SELECT TOP(4) transaction_dt
FROM dbo.example e2
WHERE e1.latitude = e2.latitude  and e1.longitude = e2.longitude
GROUP BY latitude,longitude,transaction_dt
ORDER BY transaction_dt desc
) as a
WHERE latlong.STDistance(geography::Point(40,-74,4326)) <=1609.344e1
AND e1.transaction_dt = a.transaction_dt
ORDER BY transaction_dt desc;
Run Code Online (Sandbox Code Playgroud)

我使用,top(4)因为结果集是基于< 5<= 5

使用该CROSS APPLY方法时,还需要添加一个索引,以删除索引假脱机和排序:

在此处输入图片说明

CREATE INDEX IX_latitude_longitude_transaction_dt
ON dbo.example(latitude,longitude,transaction_dt);
Run Code Online (Sandbox Code Playgroud)

在此处输入图片说明

不用说,您可以将CROSS APPLY解决方案添加到视图中,并以与以前相同的方式对其进行查询,这里是一个示例。一个 DB<>Fiddle here 中的所有上述示例。


MichaelB 的结束解决方案评论

感谢 Randi,我能够使用交叉应用的想法将我的逻辑重写为选择子查询。作为一个额外的好处,如果我不在我选择的列中提及该字段,我不会受到性能影响。

数据库<>小提琴

create or alter view dbo.v_example2
with schemabinding as
select example_id
      ,transaction_dt
      ,latitude
      ,longitude
      ,latlong
      ,most_recent= (
          select a.transaction_dt
          intersect
          select top 5 b.transaction_dt
          from dbo.example b
          where a.latitude=b.latitude
                and a.longitude=b.longitude
          order by transaction_dt desc
      )
from dbo.example a;
Run Code Online (Sandbox Code Playgroud)

&

select example_id
      ,transaction_dt
      ,latitude
      ,longitude
      ,latlong
from dbo.v_example2
where latlong.STDistance(geography::Point(40,-74,4326)) <=1609.344e1
      and most_recent is not null
Run Code Online (Sandbox Code Playgroud)