web-dev-qa-db-ja.com

各グループの最近傍の空間データのクエリ

最近傍の空間データを照会したい。私は この記事 を使用しており、次のクエリは完全に機能します。

SELECT TOP 1 [Location].STDistance(@Location)
FROM [DS1]
WHERE [Location].STDistance(@Location) IS NOT NULL          
ORDER BY [Location].STDistance(@Location);

問題は、MSPIDごとにこの距離を計算する必要があることです。私はいくつかのことを試してみましたが(クロス適用、別の関数の作成など)、何もうまくいきませんでした。そこで、各MSPIDの距離を計算するループを作成することにしました。

SELECT TOP 1 [Location].STDistance(@Location)
FROM [DS1]
WHERE [Location].STDistance(@Location) IS NOT NULL 
    AND @CurrentMSPID = [MSPID]         
ORDER BY [Location].STDistance(@Location);

問題は、上記のステートメントがインデックスを使用しないことです。 @CurrentMSPIDを数値に置き換えた場合、インデックスが使用されますが、変数が使用されている場合は、空間インデックスの代わりにクラスター化インデックスが使用されます。

私は多くのオプションを試してみましたが、うまくいく唯一のものは次のとおりです:

OPTION (OPTIMIZE FOR ( @CurrentMSPID  = 1001 ))

または、値がハードコードされている場合。将来このidが存在しなくなる可能性があり、一致する行数が確実に変更されるため、そのままにすることはできません。

[DS1]テーブルには、[MSPID]列に主キーがあり、[Location]列に空間インデックスがあります。

OPTIMIZE FORオプションを使用せずに、エンジンがより適切な実行プランを生成し、空間インデックスを使用できるようにするにはどうすればよいですか?


テーブルから他のデータが抽出されると(MSPIDでフィルターしているため)、エンジンは空間インデックスを使用できず、代わりにクラスター化インデックスシークを実行するようです。

5
gotqn

ここで少し手足を外に出て、あなたが何を達成しようとしているのかを推測します。

DS1のすべての行について、DS1テーブル内から最近傍を検索する必要があると思います。

テストセット用に、次のランダムに入力されたテーブルを作成しました。

-- Test table
CREATE TABLE DS1 ( 
  MSPID INTEGER IDENTITY(1,1) NOT NULL PRIMARY KEY,
  Location Geometry NOT NULL
);

-- 1 million random points
INSERT INTO DS1 (Location)
SELECT TOP 1000000 
  Geometry::Point(
    Rand(CAST(NEWID() AS VARBINARY))*100000,
    Rand(CAST(NEWID() AS VARBINARY))*100000,
    0) Location
FROM TALLY;

CREATE SPATIAL INDEX DS1_SIDX ON DS1 (Location) 
USING GEOMETRY_AUTO_GRID 
WITH (BOUNDING_BOX =(-15000, -15000, 2015000, 2015000));

私は簡単なテストのための最初のクエリのバージョンですが、残念ながら私のデスクトップでは、オプティマイザは並列の方が優れていると判断し、空間インデックスを無視して3秒のクエリを生成しました。これを単一コアに制限すると、インデックスが使用され、ミリ秒単位で返されました。

-- Nearest neighbour test
DECLARE @Location Geometry = Geometry::Point(50000,50000,0);

/* Ignored index
 SQL Server Execution Times:
   CPU time = 21781 ms,  elapsed time = 3805 ms.
*/
SELECT TOP 1  
  MSPID, 
  Location.STDistance(@location) Distance
FROM DS1
WHERE Location.STDistance(@location) IS NOT NULL
ORDER BY Location.STDistance(@location)

/* Used index
 SQL Server Execution Times:
   CPU time = 0 ms,  elapsed time = 10 ms.
*/
SELECT TOP 1  
  MSPID, 
  Location.STDistance(@location) Distance
FROM DS1
WHERE Location.STDistance(@location) IS NOT NULL
ORDER BY Location.STDistance(@location)
OPTION (MAXDOP 1);

DS1からの単一の行に必要なものを実現し、DS1から選択した行に最も近い行が必要であると想定すると、次の例では、インデックスを使用してミリ秒単位で機能します。

-- For a specific row using cross apply
DECLARE @MSPID INTEGER = 500000;

SELECT 
  a.MSPID FromMSPID,
  b.MSPID ToMSPID,
  b.Distance
FROM DS1 a
  CROSS APPLY (
    SELECT TOP 1  
      MSPID, 
      c.Location.STDistance(a.Location) Distance
    FROM DS1 c
    WHERE c.Location.STDistance(a.Location) IS NOT NULL AND
      c.MSPID <> a.MSPID
    ORDER BY c.Location.STDistance(a.Location)
    ) b
WHERE a.MSPID = @MSPID
OPTION (MAXDOP 1);

これはテーブル全体を通過するように作成できますが、それぞれの最近傍を見つける必要があるため、完了するまでにしばらく時間がかかります。 10,000の場合は約1分かかり、100万の場合は約2時間かかります。 このクエリを単一コアに制限する必要はなく、パラリズムを許可するとパフォーマンスが向上することを発見しました。私にとってはそれは時間を半分にした。 question をご覧になることをお勧めします。他のパフォーマンスに関する考慮事項は answer です。

/* For all (well 10,000) using cross apply
 SQL Server Execution Times:
   CPU time = 60641 ms,  elapsed time = 64545 ms.
*/
SELECT TOP 10000
  a.MSPID FromMSPID,
  b.MSPID ToMSPID,
  b.Distance
FROM DS1 a
  CROSS APPLY (
    SELECT TOP 1  
      MSPID, 
      c.Location.STDistance(a.Location) Distance
    FROM DS1 c
    WHERE c.Location.STDistance(a.Location) IS NOT NULL AND
      c.MSPID <> a.MSPID
    ORDER BY c.Location.STDistance(a.Location)
    ) b
OPTION (MAXDOP 1);
4
MickyT

WITH (INDEX = [])オプションを使用して空間インデックスを指定すると機能します。

SELECT TOP 1 [Location].STDistance(@Location)
FROM [DS1] WITH (INDEX = [...])
INNER JOIN ...
    ON ...
WHERE [Location].STDistance(@Location) IS NOT NULL 
    AND @CurrentMSPID = [MSPID]         
ORDER BY [Location].STDistance(@Location);
3
gotqn

リーチ

SELECT TOP 1 DS2.[Location].STDistance(@Location)
FROM [DS1] DS1
INNER JOIN [DS2] DS2
   ON DS1.[MSPID] = @CurrentMSPID 
  and DS2.[MSPID] = @CurrentMSPID 
WHERE DS2.[Location].STDistance(@Location) IS NOT NULL     
ORDER BY DS2.[Location].STDistance(@Location);

本当に参加が必要ですか?

SELECT TOP 1 DS2.[Location].STDistance(@Location)
FROM [DS2] DS2
where DS2.[MSPID] = @CurrentMSPID 
and DS2.[Location].STDistance(@Location) IS NOT NULL     
ORDER BY DS2.[Location].STDistance(@Location);
2
paparazzo