我可以在2012年的Microsoft SQL Server中获得更好的性能吗?

时间:2014-12-11 15:33:32

标签: sql-server performance loops while-loop geo

我正在编写一个循环,从一个表中获取一个点(美国州也是已知的),并查看该点与哪个县相交(#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万次迭代时)所以我希望以这种或那种方式加速这个查询。

非常感谢任何建议/意见。感谢。

5 个答案:

答案 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所涵盖的最大经度和纬度值。

enter image description here

要填充这些字段,我们需要让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查看该位置是否真的在该县内。

Devon

在我的内部应用程序中,检查330,000个POLYGON中的一个点是否存在,添加这四个额外字段会将速度从45秒增加到不到1秒。

相关问题