web-dev-qa-db-ja.com

STDistanceの実行を最適化するには?

ストアドプロシージャの実行中に、次の構造で一時テーブルを作成しています。

_[ID] BIGINT
[Point] GEOGRAPHY
_

IDは一意ではありません-IDごとに約_200_レコードがあります。

IDsからPointまでの距離が定数値よりも大きい少なくとも1つのPointを持つリストを見つける必要があります(例:_200_メートル)。

だから、私はこのようなものを使用しています:

_SELECT DISTINCT DS1.[ID]
FROM DataSource DS1
INNER JOIN DataSource DS2
    ON DS1.[ID] = DS2.[ID]
WHERE DS1.Point.STDistance(DS2.Point) > 200
_

23 000ポイントの場合、クエリは_4-5_秒間実行されます。もっと価値があると期待しているので、もっと良い解決策を見つける必要があります。

もっと速い方法があれば、いつでもマテリアライズドテーブルを作成し、これをIDベースで計算する追加のロジックを実装できると思います。

空間インデックスを作成しましたが、クエリオプティマイザーはそれを使用していません。このようにhintを使用すると、次のエラーが発生しますWITH (INDEX(SPATIAL_idx_test))

メッセージ8635、レベル16、状態4、行78
クエリプロセッサは、空間インデックスヒントを使用したクエリのクエリプランを作成できませんでした。理由:空間インデックスは、述語で提供されるコンパレーターをサポートしていません。インデックスヒントを削除するか、_SET FORCEPLAN_を削除してください。 `

8
gotqn

他にどのような改善を加えても、トレースフラグ6532、6553、および6534(起動時のみ)を有効にした場合の空間実行時間への影響を必ずテストしてください。これらは、ネイティブコード空間実装をオンにします。 SQL Server 2012 Service Pack 3またはSQL Server 2014 Service Pack 2が必要です( Microsoftサポート記事 )。ネイティブコンパイルは、SQL Server 2016の デフォルトではオン です。

STDistanceの場合、重要なトレースフラグは6533です。簡単なテストでは、ラップトップのSQL Server 2012インスタンスで空間インデックスを使用せずに、この改善された実行時間が2100msから150msに改善されました。

SQL 2016 – It Just Runs Faster:Native Spatial Implementation(s) by Bob Ward):

テストデータ

CREATE TABLE dbo.SpatialTest
(
    ID      integer NOT NULL IDENTITY(1,1) PRIMARY KEY CLUSTERED,
    Points  geography NOT NULL
);
GO
-- Insert random sample points
SET NOCOUNT ON;
GO
DECLARE @Point float = 1.1;

INSERT dbo.SpatialTest (Points) 
VALUES ('POINT(' + CAST(@Point AS varchar(20)) + ' ' + CAST(@Point AS varchar(20)) + ')' );

WHILE(SCOPE_IDENTITY() < 100000)
BEGIN
    SET @Point = @Point + Rand(SCOPE_IDENTITY());
    IF (@Point > 90.0)
    BEGIN
        SET @Point = -89.0 + Rand(SCOPE_IDENTITY());
    END;

    INSERT dbo.SpatialTest (Points) 
    VALUES ( 'POINT(' + CAST(@Point AS varchar(20)) + ' ' + CAST(@Point AS varchar(20)) + ')' );
END;

テストクエリ

DBCC TRACEON (6533);
DBCC TRACESTATUS;
GO
DECLARE 
    @s datetime2 = SYSUTCDATETIME(),
    @g geography = 'POINT(1.0 80.5)';

SELECT [Matches] = COUNT_BIG(*) 
FROM dbo.SpatialTest AS ST
WHERE ST.Points.STDistance(@g) > 10000000.5
OPTION (MAXDOP 1, RECOMPILE);

SELECT [Elapsed STDistance Query (ms)] = DATEDIFF(MILLISECOND, @s, SYSUTCDATETIME());

平均実行時間:2100ms(トレースフラグオフ); 150ms(トレースフラグがオン)。

6
Paul White 9

問題のクエリは、200メートル以上離れている点のペアを検索する必要があります。このようなクエリは、SQL Serverの空間インデックスではサポートされていません。

Microsoft docs 空間インデックスの概要 言う:

空間インデックスでサポートされる地理的メソッド

特定の条件下で、空間インデックスは次のセット指向の地理メソッドをサポートします:STIntersects()STEquals()およびSTDistance()。これらのメソッドは、空間インデックスでサポートされるように、クエリのWHERE句内で使用する必要があり、次の一般的な形式の述語内で発生する必要があります。

_geography1.method_name(geography2) comparison_operator valid_number
_

Null以外の結果を返すには、_geography1_と_geography2_に同じ空間参照識別子(SRID)が必要です。それ以外の場合、メソッドはNULLを返します。

空間インデックスは、次の述語形式をサポートしています。

_geography1.STIntersects(geography2) = 1

geography1.STEquals(geography2) = 1

geography1.STDistance(geography2) < number

geography1.STDistance(geography2) <= number
_

ご覧のとおり、質問のクエリにはサポートされている形式がありません。

そのため、インデックスを強制的に使用しようとすると、「理由:空間インデックスは述語で指定されたコンパレータをサポートしていません」というエラーメッセージが表示されます。

クエリを書き直して、ポイントのすべての組み合わせを含むデカルト積を作成し、そこから200メートル以内のポイントのセットを「減算」することもできますが、インデックスを使用したとしても、効率的ではないでしょうか。


私のデータベースには、地理的な場所を含む約3000行のテーブルがあり、その質問からクエリの簡易バージョンを試しました。 AND DS1.Point.STDistance(DS2.Point) IS NOT NULLを追加する必要はなく、この句なしでインデックスが使用されました。

インデックスは、比較が_< 200_の場合にのみ使用されました。

比較が_> 200_の場合は使用されませんでした。

_SELECT
    DS1.[Building ID]
FROM 
    [dbo].[tblPhysicalBuildings] AS DS1
    CROSS JOIN [dbo].[tblPhysicalBuildings] AS DS2
WHERE 
    DS1.LocationPoint.STDistance(DS2.LocationPoint) < 200
OPTION(RECOMPILE)
;
_

less

_SELECT
    DS1.[Building ID]
FROM 
    [dbo].[tblPhysicalBuildings] AS DS1
    CROSS JOIN [dbo].[tblPhysicalBuildings] AS DS2
WHERE 
    DS1.LocationPoint.STDistance(DS2.LocationPoint) > 200
OPTION(RECOMPILE)
;
_

more

3

23 000ポイントの場合、クエリは4〜5秒間実行されます。もっと価値があると期待しているので、もっと良い解決策を見つける必要があります。 [...]空間インデックスを作成しましたが、クエリオプティマイザーはそれを使用していません。このようなヒントを使用するとWITH (INDEX(SPATIAL_idx_test))次のエラーが発生します

ここでの唯一の答えは、インデックスについて言及していません。 PostGIS に移行すると、PostgreSQLの空間拡張であり、実質的にはGISのリファレンス実装であり、はるかにフル機能を備えているため、 _ST_DWithin_ 。これは空間インデックスを利用します

_CREATE INDEX ON datasource USING Gist( point );

SELECT DISTINCT ds1.id
FROM datasource AS ds1
WHERE EXISTS (
  SELECT 1
  FROM datasource ds2
  WHERE ds1.id = ds2.id
  AND NOT ST_DWithin( ds1.point, ds2.point, 200)
);
_

インデックスを使用して_ST_DWithin_を解決し、そのセットを反転します。あなたはもっとうまくできるかもしれません:

_SELECT DISTINCT ds1.id
FROM datasource AS ds1
CROSS JOIN LATERAL (
  SELECT *
  FROM datasource AS ds2
  WHERE ds1.id = ds2=id
  ORDER BY ds1.point <=> ds.point DESC
  LIMIT 1;
) AS t2
WHERE ds1.id = t2.id
AND NOT ST_DWithin( ds1.point, t2.point, 200);
_

これは距離比較を1回だけ実行し、KNNを使用してインデックスでソートします。

_btree_Gist_ を使用して、1つのインデックスルックアップに減らすこともできます。

_CREATE EXTENSION btree_Gist;
CREATE INDEX ON datasource USING Gist( id, point );
_

PostgreSQL および PostGIS は無料のオープンソースソフトウェアです。

私はこのような質問に答えました Januraryに戻る ですが、許容範囲のあるバッファーの方がおそらく良いと思います。より正確さが必要な場合は.buffer()を使用してください

2
Evan Carroll

より速くなる別の方法は、 STIntersects および BufferWithTolerance メソッドを使用して、あるポイントが別のポイントから特定の距離内にあるかどうかを確認することです。

SELECT DISTINCT DS1.[ID]
FROM DataSource DS1
INNER JOIN DataSource DS2
    ON DS1.[ID] = DS2.[ID]
WHERE DS2.STIntersects(DS1.BufferWithTolerance(200, 0.9, 0)) = 1

ただし、BufferWithToleranceには、基本的に速度と精度のトレードオフ値である許容値(この例では0.9)があることに注意してください。正確な結果が必要な場合、これはおそらくあなたのための方法ではありません。 STIntersectsは不正確な方法であることを思い出しているようですが、現時点ではそれをバックアップするための参照が見つからないため、多分それについて誤解しています。

0
paulH