Je veux interroger des données spatiales pour le voisin le plus proche. J'utilise cet article et la requête suivante fonctionne parfaitement:
SELECT TOP 1 [Location].STDistance(@Location)
FROM [DS1]
WHERE [Location].STDistance(@Location) IS NOT NULL
ORDER BY [Location].STDistance(@Location);
Le problème est que j'ai besoin de calculer cette distance pour chaque MSPID
. J'ai essayé plusieurs choses (en utilisant Cross Appliquer, créer une fonction distincte, etc.) mais rien n'a fonctionné. Ainsi, j'ai décidé de créer une boucle qui calculera la distance pour chaque MSPID
.
SELECT TOP 1 [Location].STDistance(@Location)
FROM [DS1]
WHERE [Location].STDistance(@Location) IS NOT NULL
AND @CurrentMSPID = [MSPID]
ORDER BY [Location].STDistance(@Location);
Le problème est que la déclaration ci-dessus n'utilise pas l'index. Si je remplace le @CurrentMSPID
Avec un numéro utilisé, l'index est utilisé, mais lorsqu'une variable est utilisée, l'index en cluster est utilisé à la place de l'espace spatial.
J'ai essayé beaucoup d'options et le seul qui fonctionne est:
OPTION (OPTIMIZE FOR ( @CurrentMSPID = 1001 ))
ou lorsque la valeur est codée dur. Je ne peux pas le laisser de cette façon parce que, à l'avenir, ce id
ne pouvait même pas exister et il est certain que le nombre de lignes qu'elle correspond sera modifiée.
Le [DS1]
table a une clé primaire sur [MSPID]
Index colonne et spatial sur le [Location]
colonne.
Comment puis-je aider le moteur à générer un meilleur plan d'exécution et à utiliser l'index spatial sans utiliser le OPTIMIZE FOR
option?
Il semble que d'autres données sont extraites de la table (nous filtrons par MSPID
maintenant) le moteur n'est pas en mesure d'utiliser l'index spatial et effectue plutôt une recherche d'index en cluster.
Je vais sortir un peu de membre ici et devinez ce que vous essayez d'atteindre.
Je soupçonne que vous souhaitez trouver le voisin le plus proche de la table DS1 pour toutes les lignes de DS1.
Pour un jeu de test, j'ai créé la table remplie de manière aléatoire suivante.
-- 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));
I Une version de votre requête initiale pour un test rapide, mais malheureusement sur mon bureau, l'optimiseur décidait que le parallèle était meilleur et ignoré l'indice spatial résultant d'une requête de 3 secondes. Le restreindre à un seul noyau l'a causé à utiliser l'index et renvoyé en millisecondes.
-- 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);
Pour atteindre ce que vous voulez pour une seule ligne de DS1 et en supposant que vous souhaitiez la rangée la plus proche de DS1 à celle que vous avez choisie, les éléments suivants utilisent l'index pour moi et fonctionnent à nouveau en millisecondes.
-- 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);
Cela peut être fait pour traverser toute la table, mais il prendra du temps pour compléter car il doit trouver le voisin le plus proche pour chacun. Il a fallu environ 1 minute pour 10 000, donc pour le million complet qui prendrait près de 2 heures. J'ai constaté que restreindre cette requête à un seul noyau n'est pas nécessaire et permettant au parallisme améliorera les performances. Pour moi, il a deux fois de moitié le temps. Je recommanderais également que vous ayez un coup d'œil à cela question et c'est Réponse pour d'autres considérations de performance.
/* 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);
Spécification de l'index spatial à l'aide de la fonction WITH (INDEX = [])
option fonctionne:
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);
Une portée
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);
Avez-vous vraiment besoin de la jointure?
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);