ストアドプロシージャの実行中に、次の構造で一時テーブルを作成しています。
_[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
_を削除してください。 `
他にどのような改善を加えても、トレースフラグ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(トレースフラグがオン)。
問題のクエリは、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)
;
_
_SELECT
DS1.[Building ID]
FROM
[dbo].[tblPhysicalBuildings] AS DS1
CROSS JOIN [dbo].[tblPhysicalBuildings] AS DS2
WHERE
DS1.LocationPoint.STDistance(DS2.LocationPoint) > 200
OPTION(RECOMPILE)
;
_
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()
を使用してください
より速くなる別の方法は、 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は不正確な方法であることを思い出しているようですが、現時点ではそれをバックアップするための参照が見つからないため、多分それについて誤解しています。