La question:
J'ai une table spatiale (lignes de route), stockée à l'aide du SDE.ST_GEOMETRY
type de données défini par l' utilisateur d'ESRI dans une géodatabase Oracle 12c . Je veux lister les sommets des lignes afin que je puisse finalement accéder et mettre à jour leurs coordonnées. Si j'utilisais SDO_GEOMETRY / Oracle Locator, j'utiliserais la
SDO_UTIL.GETVERTICES
fonction. Mais je n'utilise pas SDO_GEOMETRY / Oracle Locator, et il n'y a pas de fonction équivalente dans SDE.ST_GEOMETRY
. Les seules SDE.ST_GEOMETRY
fonctions que je peux trouver concernant les sommets sont ST_PointN
et ST_NumPoints
.
J'ai trouvé une requête qui réussit à faire tout cela - obtient les sommets des lignes sous forme de lignes (inspiré par cette page ):
1 SELECT a.ROAD_ID
2 ,b.NUMBERS VERTEX_INDEX
3 ,a.SDE.ST_X(SDE.ST_PointN(a.SHAPE, b.NUMBERS)) AS X
4 ,a.SDE.ST_Y(SDE.ST_PointN(a.SHAPE, b.NUMBERS)) AS Y
5 FROM ENG.ROADS a
6 CROSS JOIN ENG.NUMBERS b
7 WHERE b.NUMBERS <= SDE.ST_NumPoints(a.SHAPE)
8 --removed to do explain plan: ORDER BY ROAD_ID, b.NUMBERS
----------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes |TempSpc| Cost (%CPU)| Time |
----------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 5996 | 1545K| | 262 (1)| 00:00:01 |
| 1 | MERGE JOIN | | 5996 | 1545K| | 262 (1)| 00:00:01 |
| 2 | INDEX FULL SCAN | R23715_SDE_ROWID_UK | 30 | 90 | | 1 (0)| 00:00:01 |
|* 3 | SORT JOIN | | 3997 | 1018K| 2392K| 261 (1)| 00:00:01 |
| 4 | TABLE ACCESS FULL| ROAD | 3997 | 1018K| | 34 (0)| 00:00:01 |
----------------------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
" 3 - access(""B"".""NUMBERS""<=""SDE"".""ST_NumPoints""(""A"".""SHAPE""))"
" filter(""B"".""NUMBERS""<=""SDE"".""ST_NumPoints""(""A"".""SHAPE""))"
Il CROSS JOINS
les lignes du ROADS
tableau à un NUMBERS
tableau (et limite les résultats au nombre de sommets dans chaque ligne).
Statistiques: (mis à jour)
- Chaque ligne a un maximum de 30 sommets (moyenne de 4,38 sommets par ligne)
- ROADS compte 3 997 lignes
- NUMBERS a 30 lignes (numéros séquentiels commençant à 1)
- L'ensemble de résultats contient 17 536 lignes
Cependant, les performances sont médiocres (40 secondes), et je ne peux m'empêcher de penser - existe-t-il une façon plus élégante de le faire? Pour moi, l'utilisation d'une table de nombres et d'une jointure croisée semble être une approche bâclée. Y a-t-il une meilleure façon?
Les termes de Layman seraient appréciés; Je suis un gars des travaux publics, pas un DBA.
Mise à jour # 1:
Si je supprime les lignes 3 et 4 (chaîne de fonctions liées à X et Y) de la requête, elle s'exécute instantanément. Mais bien sûr, je ne peux pas simplement supprimer ces lignes, j'ai besoin des colonnes X & Y. Cela m'amène donc à croire que la lenteur des performances a quelque chose à voir avec les fonctions X & Y.
Cependant, si j'exporte les points dans une table statique, puis que j'exécute les fonctions X et Y dessus, cela s'exécute également instantanément.
Donc, cela signifie-t-il que la lenteur des performances est causée par les fonctions X et Y, sauf que non, ce n'est pas le cas? Je suis confus.
Mise à jour # 2:
Si j'apporte les X et Y de la requête, les place dans une requête externe et ajoute ROWNUM à la requête interne, alors c'est beaucoup plus rapide (16 secondes - mis à jour):
SELECT
ROWNUM
,ROAD_ID
,VERTEX_INDEX
,SDE.ST_X(ST_POINT) AS X
,SDE.ST_Y(ST_POINT) AS Y
FROM
(
SELECT
ROWNUM
,a.ROAD_ID
,b.NUMBERS VERTEX_INDEX
,SDE.ST_PointN(a.SHAPE, b.NUMBERS) AS ST_POINT
FROM ENG.ROAD a
CROSS JOIN ENG.NUMBERS b
WHERE b.NUMBERS <= SDE.ST_NumPoints(a.SHAPE)
)
--removed to do explain plan: ORDER BY ROAD_ID, VERTEX_INDEX
-------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes |TempSpc| Cost (%CPU)| Time |
-------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 5996 | 322K| | 262 (1)| 00:00:01 |
| 1 | COUNT | | | | | | |
| 2 | VIEW | | 5996 | 322K| | 262 (1)| 00:00:01 |
| 3 | COUNT | | | | | | |
| 4 | MERGE JOIN | | 5996 | 1545K| | 262 (1)| 00:00:01 |
| 5 | INDEX FULL SCAN | R23715_SDE_ROWID_UK | 30 | 90 | | 1 (0)| 00:00:01 |
|* 6 | SORT JOIN | | 3997 | 1018K| 2392K| 261 (1)| 00:00:01 |
| 7 | TABLE ACCESS FULL| ROAD | 3997 | 1018K| | 34 (0)| 00:00:01 |
-------------------------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
" 6 - access(""B"".""NUMBERS""<=""SDE"".""ST_NumPoints""(""A"".""SHAPE""))"
" filter(""B"".""NUMBERS""<=""SDE"".""ST_NumPoints""(""A"".""SHAPE""))"
Justin Cave explique pourquoi ROWNUM améliore les performances ici: Pourquoi l'ajout de ROWNUM à une requête améliore-t-il les performances?
Bien que cette amélioration des performances soit bonne, elle n'est pas encore suffisante. Et je ne peux pas m'empêcher de penser que je ne comprends toujours pas complètement comment fonctionne la requête ou pourquoi elle est aussi lente qu'elle l'est.
La question se pose toujours: existe-t-il une meilleure solution?
la source
Réponses:
Je connais un peu les performances d'Oracle et pratiquement rien sur les types de données personnalisés, mais je vais essayer de vous donner un plan pour améliorer les performances.
1) Vérifiez que vous ne pouvez pas obtenir de plan d'explication.
Il est possible d'obtenir des plans d'explication même si vous n'avez pas de logiciel de base de données sophistiqué. Que se passe-t-il si vous exécutez
set autotrace on explain
?Vous pouvez également essayer DBMS_XPLAN . Enregistrez d'abord le plan en enveloppant votre requête de quelques mots clés supplémentaires:
Exécutez ensuite ceci:
SELECT PLAN_TABLE_OUTPUT FROM TABLE(DBMS_XPLAN.DISPLAY());
Il est possible qu'aucun de ces éléments ne fonctionne et vous ne pouvez vraiment pas obtenir de plan d'explication. Je voulais juste vérifier cela, car avec un plan d'explication, il sera beaucoup plus facile pour la communauté de vous aider.
2) Tenez compte des exigences.
Vous avez dit que 20 secondes ne suffisaient pas. Avez-vous ou quelqu'un d'autre défini exactement ce qui est assez bon? Y a-t-il une marge de négociation? Votre requête doit-elle être exactement une requête SELECT? Pourriez-vous remplir une table temporaire globale en une seule étape et sélectionner les résultats souhaités dans la suivante? Pourriez-vous créer une procédure stockée qui renvoie un jeu de résultats et l'appeler?
3) Établissez une limite inférieure pour le temps requis pour terminer la requête.
Je suggère d'exécuter une requête simple qui "triche" pour comprendre à quoi ressemblerait une requête bien optimisée. Par exemple, combien de temps dure cette requête qui n'obtient que les premiers sommets?
Je soupçonne que cela vous donnera 4000 lignes. Si vous multipliez le temps de réponse de cette requête par 17,5 / 4, cela pourrait vous donner une bonne limite inférieure pour le temps d'exécution total.
Si votre limite inférieure pour le temps d'exécution total est plus longue que celle que vous avez établie à l'étape 2, vous devez soit faire preuve de créativité avec votre modèle de données en calculant les résultats à l'avance et en les stockant dans des tableaux, soit vous devez renégocier le temps de réponse requis.
4) Benchmark pour déterminer quelles fonctions contribuent le plus à votre temps d'exécution.
Vous étiez sur la bonne voie avec la mise à jour # 1, mais vous devez essayer de contrôler la quantité de travail en cours. Par exemple, est-il possible d'écrire un groupe de requêtes relativement simples qui exécutent chaque fonction exactement 10000 fois? Comment les temps de réponse se comparent-ils?
5) Allez travailler.
En fonction des exigences établies à l'étape 2 et de ce que vous avez trouvé à l'étape 4, essayez n'importe quelle astuce à laquelle vous pouvez penser pour réduire le temps d'exécution de la requête. Pouvez-vous pré-calculer les résultats et les économiser? Si le problème concerne le nombre d'exécutions des fonctions, le conseil de matérialisation non documenté peut être utile. Cela oblige Oracle à créer une table temporaire cachée dans les coulisses pour stocker les résultats. Je ne sais pas s'il est compatible avec les types de données spéciaux que vous utilisez.
Par exemple, peut-être que quelque chose comme ça fonctionne mieux? Toutes mes excuses s'il ne compile pas mais je n'ai aucun moyen de tester.
Si vous êtes toujours coincé après tout cela, je soupçonne que cela vous donnera au moins des informations supplémentaires que vous pouvez modifier dans la question. Bonne chance!
la source
J'ai essayé d'utiliser CONNECT BY (et DUAL) pour voir si ce serait plus rapide, mais ce n'est pas le cas (c'est à peu près la même chose).
J'ai eu l'idée de ce post: Comment calculer les plages dans Oracle?
la source
Résultats et réponse à la réponse de Joe Obbish :
Remarque: à partir de maintenant, je ferai référence à la requête dans la mise à jour # 2 comme 'la requête'; Je ne ferai pas référence à la requête dans la question d'origine.
1) Vérifiez que vous ne pouvez pas obtenir de plan d'explication.
Je suis incapable d'exécuter
set autotrace on explain
. Je reçois cette erreur:ORA-00922: missing or invalid option (#922)
Mais je suis capable d'exécuter
DBMS_XPLAN
. J'avais supposé que je serais incapable de le faire. Heureusement, j'avais tort. J'exécute maintenant des plans d'explication.2) Tenez compte des exigences.
Votre requête doit-elle être exactement une requête SELECT?
Je pense que la requête ne doit être exactement une requête. Le logiciel que j'utilise est très limité et ne permet pas de multiples déclarations de sélection.
Avez-vous défini exactement vos besoins?
3) Établissez une limite inférieure pour le temps requis pour terminer la requête.
La requête du premier sommet s'exécute en 3,75 secondes (renvoie 3805 lignes, comme prévu).
3.75 sec * (16495 total / 3805 lines) = 16.25 sec
Résultat: la limite inférieure du temps d'exécution total est plus longue que celle que j'ai établie à l'étape 2 (5 secondes). Par conséquent, je pense que la solution est de «... faire preuve de créativité avec mon modèle de données en calculant les résultats à l'avance et en les stockant dans un tableau» (le temps de réponse requis n'est pas négociable). En d'autres termes, faites une vue matérialisée.
De plus, la limite inférieure de 16,25 secondes correspond au temps d'exécution total de la requête dans la mise à jour # 2 (16 secondes). Je pense que cela prouve que ma requête est entièrement optimisée, compte tenu des fonctions et des données avec lesquelles je dois travailler.
4) Benchmark pour déterminer quelles fonctions contribuent le plus à votre temps d'exécution.
J'ai créé deux tables (chacune contenant 10 000 lignes):
ROADS_BM
etROADS_STARTPOINT_BM
. J'ai exécuté des requêtes simples sur les tables en utilisant chacune des fonctions impliquées. Voici les résultats:Documentation fonction: ST_X , ST_Y , ST_NumPoints , ST_PointN
Le résultat?
ST_PointN
c'est le problème. Son temps de réponse de 9,5 secondes est épouvantable par rapport aux autres fonctions. Je suppose que cela a un peu de sens cependant.ST_PointN
renvoie unST_POINT
type de données de géométrie, qui doit être assez complexe par rapport aux autres fonctions qui renvoient un nombre simple.5) Allez travailler.
Sur la base des étapes 2, 3 et 4, voici mes conclusions:
ST_PointN
fonction. C'est lent. Je ne pense pas que je puisse faire grand-chose à ce sujet. Autre que d'essayer de reprogrammer / recréer complètement la fonction dans l'espoir que je pourrais faire mieux que les spécialistes qui l'ont faite. Pas vraiment pratique.ST_PointN
c'est à blâmer pour cela aussi. La requête CTE n'était cependant pas un gaspillage; J'ai beaucoup appris rien qu'en l'utilisant.6. Conclusion.
Je suis convaincu d'avoir optimisé la requête autant que possible. Je vais configurer le pré-calcul et passer à la chose suivante. Un grand merci à Joe Obbish; J'ai appris une tonne des étapes qu'il a fournies.
la source