Je crée une table temporaire lors de l'exécution de la procédure stockée avec la structure suivante:
[ID] BIGINT
[Point] GEOGRAPHY
le ID
n'est pas unique - il y a environ 200
enregistrements pour chaque ID
.
J'ai besoin de trouver une liste avec une liste distincte IDs
pour laquelle il existe au moins un Point
à Point
distance plus grande que la valeur constante (par exemple 200
Mètres) .
Donc, j'utilise quelque chose comme ça:
SELECT DISTINCT DS1.[ID]
FROM DataSource DS1
INNER JOIN DataSource DS2
ON DS1.[ID] = DS2.[ID]
WHERE DS1.Point.STDistance(DS2.Point) > 200
Pour 23 000 points, la requête est exécutée pour 4-5
Secondes. Comme je m'attends à avoir plus de valeurs, j'ai besoin de trouver une meilleure solution.
Je suppose que s'il y a un moyen plus rapide, je peux toujours créer une table matérialisée et mettre en œuvre une logique supplémentaire qui le calculera sur ID
base.
J'ai créé un indice spatial, mais l'optimiseur de requêtes ne l'utilise pas. Si j'utilise un hint
comme ceci WITH (INDEX(SPATIAL_idx_test))
je reçois l'erreur suivante:
MSG 8635, niveau 16, état 4, ligne 78
[.____] Le processeur de requête n'a pas pu produire un plan de requête pour une requête avec un indice d'index spatial. Raison: les index spatiaux ne prennent pas en charge le comparateur fourni dans le prédicat. Essayez de supprimer les indices d'index ou de supprimerSET FORCEPLAN
. `
Indépendamment de toute autre amélioration que vous apportez, veillez à tester l'impact sur les délais d'exécution spatiale d'activation des drapeaux de trace 6532, 6533 et 6534 (start-up uniquement). Ceux-ci activés Mises en œuvre spatiale du code natif. Service SQL Server 2012 Service Pack 3 ou SQL Server 2014 2 requis ( Article de support Microsoft ). La compilation native est ON par défaut de SQL Server 2016.
Pour STDistance
L'indicateur de trace important est 6533 . Dans un simple test, ce temps d'exécution amélioré de 2100 ms à 150 ms sans utiliser d'index spatial sur l'instance SQL Server 2012 de l'ordinateur portable.
Exemple adapté de SQL 2016 - il fonctionne simplement plus vite: mise en œuvre (s) spatiale (s) native par 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());
Temps d'exécution moyens: 2100ms (drapeau de trace off); 150ms (drapeau de trace sur).
La requête dans la question veut trouver des paires de points plus à moins de 200 mètres. Un tel type de requête n'est pas pris en charge par les index spatiaux de SQL Server.
Microsoft Docs Vue d'ensemble des index spatiaux Dites:
Méthodes de géographie supportées par des index spatiaux
Dans certaines conditions, les index spatiaux prennent en charge les méthodes de géographie optionnelles suivantes:
STIntersects()
,STEquals()
etSTDistance()
. Pour être soutenu par un indice spatial, ces méthodes doivent être utilisées dans la clauseWHERE
d'une requête, et elles doivent se produire dans un prédicat de la forme générale suivante:geography1.method_name(geography2) comparison_operator valid_number
Pour renvoyer un résultat non nul,
geography1
Etgeography2
Doit avoir le même identifiant de référence spatial (srid). Sinon, la méthode renvoieNULL
.Les index spatiaux prennent en charge les formulaires de prédicats suivants:
geography1.STIntersects(geography2) = 1 geography1.STEquals(geography2) = 1 geography1.STDistance(geography2) < number geography1.STDistance(geography2) <= number
Comme vous pouvez le constater, la requête en question n'a pas l'une des formes prises en charge.
C'est pourquoi vous obtenez ce message d'erreur "Raison: les index spatiaux ne prennent pas en charge le comparateur fourni dans le prédicat" lorsque vous essayez de forcer l'utilisation de l'index.
Vous pouvez essayer de réécrire la requête pour créer un produit cartésien avec toutes les combinaisons de points et "soustraire" d'un ensemble de points à moins de 200 mètres, mais je doute qu'il soit plus efficace, même s'il utilisait l'index.
J'ai une table avec ~ 3000 rangées avec des emplacements géographiques de ma base de données et j'ai essayé la version simplifiée de la requête de la question à ce sujet. Ajout AND DS1.Point.STDistance(DS2.Point) IS NOT NULL
n'était pas nécessaire, l'index a été utilisé sans cette clause.
L'indice n'a été utilisé que lorsque la comparaison était < 200
.
Il n'a pas été utilisé lorsque la comparaison était > 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)
;
Pour 23 000 points, la requête est exécutée pendant 4-5 secondes. Comme je m'attends à avoir plus de valeurs, j'ai besoin de trouver une meilleure solution. [...] J'ai créé un indice spatial, mais l'optimiseur de requêtes ne l'utilise pas. Si j'utilise un indice comme ceci
WITH (INDEX(SPATIAL_idx_test))
je reçois l'erreur suivante
La seule réponse ici ne fait aucune mention d'un index. Je voulais juste dire si vous migrez à PostGis , l'extension spatiale pour PostgreSQL qui est pratiquement la mise en œuvre de référence pour SIG et beaucoup plus complète en vedette, vous aurez ST_DWithin
. Ceci sera utiliser un indice spatial.
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)
);
Cela utilisera l'index pour résoudre ST_DWithin
, Puis invert cet ensemble. Vous pourrez peut-être faire encore mieux:
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);
Cela ne sera exécuté que la distance de comparaison une fois, et cela utilisera Knn pour trier l'index.
Vous pouvez même le réduire à une recherche d'index avec btree_Gist
.
CREATE EXTENSION btree_Gist;
CREATE INDEX ON datasource USING Gist( id, point );
PostgreSQL et PostGis sont un logiciel libre et open source.
J'ai répondu à une question comme celle-ci retour dans Janturary , mais je pense que le tampon de tolérance est probablement mieux. Si vous avez besoin de plus de précision, utilisez .buffer()
Une autre manière qui peut être plus rapide consiste à utiliser les méthodes - STITURSECTS et tamponwithtolérance pour vérifier si un point est dans une certaine distance d'une autre.
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
Notez que ce tamponwithtolérance a une valeur de tolérance (dans mon exemple de 0,9) essentiellement une valeur de compromis entre la vitesse et la précision. Si vous voulez des résultats exacts, ce n'est probablement pas la méthode pour vous. Je semble aussi rappeler que STITRESSECTS est une méthode imprécise, mais je ne trouve aucune référence à ce moment-là pour le moment, alors peut-être que je me trompe à ce sujet.