我正在编写一个循环,从一个表中获取一个点(美国州也是已知的),并查看该点与哪个县相交(#INPUT
具有所有的点地理位置和{{ 1}}拥有所有县的地理位置。)
以下是查询:
[dbo].[counties]
对于表DECLARE @RN_BEGIN INT
DECLARE @RN_END INT
SET @RN_BEGIN = 1
SELECT @RN_END = MAX([RN]) FROM #INPUT
DECLARE @STATE CHAR(2)
DECLARE @GEO GEOGRAPHY
WHILE @RN_BEGIN <= @RN_END
BEGIN
SELECT @ID_NUMBER = [ID_NUMBER] FROM #INPUT WHERE [RN] = @RN_BEGIN
SELECT @STATE = [STATE] FROM #INPUT WHERE [RN] = @RN_BEGIN
SELECT @GEO = [Geo] FROM #INPUT WHERE [RN] = @RN_BEGIN
INSERT INTO #OUTPUT
SELECT
@ID_NUMBER,
CONCAT([statefp], [countyfp]) AS [FIPSTCNTY]
FROM [dbo].[counties]
WHERE @GEO.STIntersects([Geo]) = 1 AND [statefp] = @STATE
SET @RN_BEGIN = @RN_BEGIN + 1
END
(约100万行):我在#INPUT
上有一个聚集索引,[ID_NUMBER]
上有非聚集索引(不包括任何列) ,以及[RN]
上的空间索引(这是点列)。
对于表[Geo]
(完成时应该是~100万行):我在&#39; [ID_NUMBER]`
对于表#OUTPUT
(~3000行):我在ID字段上有一个聚簇索引(导入时它是如何引入的),{{1}上的非聚集索引(包括[dbo].[counties]
和[statefp]
),以及[geo]
上的空间索引(这是县列)。
所以我向你们所有人提出的问题是,我是否缺少任何明显的索引,或者是解决这个问题的新方法(除了循环之外)?我知道循环很慢(特别是当它必须循环大约100万次迭代时)所以我希望以这种或那种方式加速这个查询。
非常感谢任何建议/意见。感谢。
答案 0 :(得分:2)
非常肯定只需使用JOIN就可以在没有循环的情况下实现这一点:
INSERT INTO #OUTPUT (ID_NUMBER, FIPSTCNTY)
SELECT i.ID_NUMBER,
FIPSTCNTY = CONCAT(c.statefp, c.countyfp)
FROM dbo.counties AS c
INNER JOIN #INPUT AS i
ON i.GEO.STIntersects(c.Geo) = 1
AND i.State = c.State;
关于索引,如果没有看到执行计划很难说,但是如果你在启用了“包含实际执行计划”的SSMS中运行此查询,那么它将建议缺少索引,这不是一个精确的科学,但它是一个非常好的起点。
以下这一点应该被认为是一个注意 - 我不会提倡使用可以避免的游标
我不能强调上述内容,我不喜欢下一个人使用游标,但是,因为人们只是使用默认选项
他们得到的声誉甚至比他们应得的更糟糕,并且带有临时表的复杂WHILE
循环通常比正确声明的表现更差
光标。正是在这一点上我觉得有必要解决,因为它似乎是一种常见的误解。
在你的情况下你没有修改数据,只是向前阅读,所以我会将光标声明为LOCAL STATIC READ_ONLY FORWARD_ONLY
确保它仅使用我需要的功能进行初始化:
DECLARE @STATE CHAR(2),
@GEO GEOGRAPHY,
@ID_NUMBER INT;
DECLARE InputCursor CURSOR LOCAL STATIC READ_ONLY FORWARD_ONLY
FOR
SELECT State, Geo, ID_NUmber
FROM #Input;
OPEN InputCursor;
FETCH NEXT FROM InputCursor INTO @State, @Geo, @ID_NUMBER;
WHILE @@FETCH_STATUS = 0
BEGIN
INSERT INTO #OUTPUT
SELECT @ID_NUMBER, CONCAT([statefp], [countyfp]) AS [FIPSTCNTY]
FROM [dbo].[counties]
WHERE @GEO.STIntersects([Geo]) = 1
AND [statefp] = @STATE;
FETCH NEXT FROM InputCursor INTO @State, @Geo, @ID_NUMBER;
END
CLOSE InputCursor;
DEALLOCATE InputCursor;
答案 1 :(得分:0)
INSTEAD OF
SELECT @ID_NUMBER = [ID_NUMBER] FROM #INPUT WHERE [RN] = @RN_BEGIN
SELECT @STATE = [STATE] FROM #INPUT WHERE [RN] = @RN_BEGIN
SELECT @GEO = [Geo] FROM #INPUT WHERE [RN] = @RN_BEGIN
TRY
SELECT @ID_NUMBER = [ID_NUMBER],
@STATE = [STATE],
@GEO = [Geo]
FROM #INPUT
WHERE [RN] = @RN_BEGIN
答案 2 :(得分:0)
我认为你可以通过以下方式消除循环:
INSERT INTO #OUTPUT
SELECT i.[ID_Number], CONCAT([statefp], [countyft]) as [FIPSTCNTY]
FROM [dbo].[counties] c INNER JOIN #INPUT i ON i.[state] = c.[state]
WHERE i.[GEO].STIntersects(c.[GEO]) = 1
我认为另一件事是成本很高的是STIntersects条款。我不知道SQL是否正在使用这两个索引。你可以像这个人在这里一样解决这个问题:
slow spatial predicates (STContains, STIntersects, STWithin, ...)
答案 3 :(得分:0)
你应该能够完全消除循环。我认为您的整个查询块看起来可以替换为:
INSERT INTO #OUTPUT
SELECT DISTINCT i.[ID_NUMBER],
CONCAT(c.[statefp], c.[countyfp]) AS [FIPSTCNTY]
FROM [dbo].[counties] c
INNER JOIN #INPUT i
ON i.[Geo].STIntersects(c.[Geo]) = 1
AND i.[STATE] = c.[statefp]
显然,运行这样的东西以确保你得到你想要的东西:
SELECT DISTINCT TOP 1000 i.[ID_NUMBER],
CONCAT(c.[statefp], c.[countyfp]) AS [FIPSTCNTY]
FROM [dbo].[counties] c
INNER JOIN #INPUT i
ON i.[Geo].STIntersects(c.[Geo]) = 1
AND i.[STATE] = c.[statefp]
根据您的数据以及[counties]
和#INPUT
之间的关系,您可能不需要DISTINCT
限定符。如果您知道自己不需要它,或者(显然)如果您知道需要重复项,那么将其删除会更快。最好为INSERT INTO #OUTPUT ([ID_NUMBER], [FIPSTCNTY])
指定插入表的字段名称,但我无法知道这些列的名称是什么。但是,如果其中任何一个是多对一的,您可能需要它来避免密钥违规。
确保几何列上有空间索引。如果生成执行计划,您应该在SSMS中找到缺少索引的合理建议。您也可能需要使用联接。我不记得索引之间的相互影响程度如何;我几年来一直没有使用地理空间或几何学。
答案 4 :(得分:0)
在我的STIntersects
字段中使用SQL Server geography
函数时,我遇到了类似的性能问题,其中包含(有时很复杂的)POLYGON
形状。
添加空间索引没有什么区别,但这是我提出的解决方案。
在我的表格中,我添加了四个新的数据库字段,用于存储最小值和最小值。记录POLYGON所涵盖的最大经度和纬度值。
要填充这些字段,我们需要让SQL Server检查POLYGON形状。支撑自己,这不是很漂亮......
UPDATE [County]
SET
County_Min_Longitude = cast(geometry::STGeomFromWKB([County_Polygon].STAsBinary(), [County_Polygon].STSrid).MakeValid().STEnvelope().STPointN(1).STX as numeric(12, 5)) ,
County_Min_Latitude = cast(geometry::STGeomFromWKB([County_Polygon].STAsBinary(), [County_Polygon].STSrid).MakeValid().STEnvelope().STPointN(1).STY as numeric(12, 5)),
County_Max_Longitude = cast(geometry::STGeomFromWKB([County_Polygon].STAsBinary(), [County_Polygon].STSrid).MakeValid().STEnvelope().STPointN(3).STX as numeric(12, 5)),
County_Max_Latitude = cast(geometry::STGeomFromWKB([County_Polygon].STAsBinary(), [County_Polygon].STSrid).MakeValid().STEnvelope().STPointN(3).STY as numeric(12, 5))
一旦我们运行了这个查询,我们的郡记录将包含原始的POLYGON,加上它的边界。但这对我们有什么帮助?
那么,现在,当我们有一个点,并想知道它是否与我们的每个多边形相交时,我们可以放入一些额外的搜索条件。
这是(大致)您的代码将被更改为:
DECLARE
@longitude NUMERIC(15, 6) = 23.1238,
@latitude NUMERIC(15, 6) = -5.3473
INSERT INTO #OUTPUT
SELECT @ID_NUMBER,
CONCAT([statefp], [countyfp]) AS [FIPSTCNTY]
FROM [County]
WHERE @longitude > [County_Longitude_Min]
AND @longitude < [County_Longitude_Max]
AND @latitude > [County_Latitude_Min]
AND @latitude < [County_Latitude_Max]
AND @GEO.STIntersects([County_Polygon]) = 1 AND [statefp] = @STATE
使用此代码,SQL Server可以非常快速消除我们的观点绝对不存在的任何[County]
条记录,然后它只会使用STIntersects
我们的经度和纬度值 do 位于我们POLYGON所在的区域内。
例如,如果我想知道某位置是否位于英格兰德文郡,那么快速检查该点是否位于此矩形内是更有效的,如果是,那么然后使用STIntersects
查看该位置是否真的在该县内。
在我的内部应用程序中,检查330,000个POLYGON中的一个点是否存在,添加这四个额外字段会将速度从45秒增加到不到1秒。