Jointure croisée sur une table de nombres pour obtenir des sommets de ligne, existe-t-il une meilleure façon?

8

La question:

J'ai une table spatiale (lignes de route), stockée à l'aide du SDE.ST_GEOMETRYtype 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.GETVERTICESfonction. 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_PointNet 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 JOINSles lignes du ROADStableau à un NUMBERStableau (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?

Wilson
la source
Les commentaires ne sont pas pour une discussion approfondie; cette conversation a été déplacée vers le chat .
Paul White 9

Réponses:

7

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:

explain plan for (SELECT... your query goes here); 

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?

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
          ,1 VERTEX_INDEX
          ,SDE.ST_PointN(a.SHAPE, 1) AS ST_POINT
    FROM  ENG.ROAD a
)
ORDER BY ROAD_ID, VERTEX_INDEX;

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.

WITH ROAD_CTE (ROAD_ID, VERTEX_INDEX, SHAPE) AS
(
    SELECT /*+ materalize */
      a.ROAD_ID
    , b.NUMBERS VERTEX_INDEX
    , a.SHAPE
    FROM ENG.ROAD a
    CROSS JOIN ENG.NUMBERS b
    WHERE b.NUMBERS <= SDE.ST_NUMPOINTS(a.SHAPE)
)
, CTE_WITH_ST_POINT (ROAD_ID, VERTEX_INDEX, ST_POINT) AS
(
    SELECT /*+ materalize */
      rcte.ROAD_ID
    , rcte.VERTEX_INDEX
    , SDE.ST_PointN(rcte.SHAPE, rcte.VERTEX_INDEX) ST_POINT
    FROM ROAD_CTE rcte
)
SELECT 
      ROAD_ID
    , VERTEX_INDEX
    , SDE.ST_X(ST_POINT) AS X
    , SDE.ST_Y(ST_POINT) AS Y
FROM CTE_WITH_ST_POINT
ORDER BY ROAD_ID, VERTEX_INDEX;

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!

Joe Obbish
la source
2

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).

SELECT  ROAD_ID
        ,T.VERTEX_INDEX
        ,SDE.ST_X(SDE.ST_PointN(SHAPE, T.VERTEX_INDEX)) AS X
        ,SDE.ST_Y(SDE.ST_PointN(SHAPE, T.VERTEX_INDEX)) AS Y
FROM    ENG.ROADS 
        CROSS JOIN
            (
            SELECT LEVEL AS VERTEX_INDEX 
            FROM DUAL CONNECT BY LEVEL <= 
                (
                SELECT MAX(SDE.ST_NUMPOINTS(SHAPE)) 
                FROM ENG.ROADS 
                )
            ) T
WHERE    T.VERTEX_INDEX <= SDE.ST_NUMPOINTS(SHAPE)
--removed to do explain plan: ORDER BY ROAD_ID, VERTEX_INDEX

-------------------------------------------------------------------------------------------------------
| Id  | Operation                      | Name                 | Rows  | Bytes | Cost (%CPU)| Time     |
-------------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT               |                      |   200 | 54800 |    36   (0)| 00:00:01 |
|   1 |  NESTED LOOPS                  |                      |   200 | 54800 |    36   (0)| 00:00:01 |
|   2 |   VIEW                         |                      |     1 |    13 |     2   (0)| 00:00:01 |
|*  3 |    CONNECT BY WITHOUT FILTERING|                      |       |       |            |          |
|   4 |     FAST DUAL                  |                      |     1 |       |     2   (0)| 00:00:01 |
|   5 |     SORT AGGREGATE             |                      |     1 |   261 |            |          |
|   6 |      TABLE ACCESS FULL         | ROAD                 |  3997 |  1018K|    34   (0)| 00:00:01 |
|*  7 |   TABLE ACCESS FULL            | ROAD                 |   200 | 52200 |    34   (0)| 00:00:01 |
-------------------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------
"   3 - filter(LEVEL<= (SELECT MAX(""SDE"".""ST_NUMPOINTS""(""SHAPE"")) FROM "
"              ""ENG"".""ROAD"" ""ROAD""))"
"   7 - filter(""T"".""VERTEX_INDEX""<=""SDE"".""ST_NUMPOINTS""(""ROAD"".""SHAPE""))"

J'ai eu l'idée de ce post: Comment calculer les plages dans Oracle?

Wilson
la source
2

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?

  • La requête sera utilisée pour mettre à jour les coordonnées des sommets après que des modifications ont été apportées à la géométrie de ligne. Cela se produit généralement sur une seule ligne à la fois, ou peut-être sur des dizaines de lignes, mais probablement pas sur des milliers de lignes. Dans ce scénario, les performances actuelles de la requête seront adéquates.
  • La requête sera également utilisée pour construire une nouvelle géométrie de ligne pour les 3 805 lignes (ceci est lié au sujet de la segmentation dynamique / référencement linéaire ). Cela se produira à la volée dans une vue, donc les performances sont absolument cruciales. La requête devrait probablement s'exécuter en moins de 5 secondes.

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_BMet ROADS_STARTPOINT_BM. J'ai exécuté des requêtes simples sur les tables en utilisant chacune des fonctions impliquées. Voici les résultats:

               +-----------+------------------+---------------------------------------------------------------------------+
               | TIME(sec) | RETURN TYPE      | QUERY                                                                     |
+--------------+-----------+------------------+---------------------------------------------------------------------------+
| ST_X         | < 0.5     | Double precision | SELECT ROAD_ID FROM (                                                     |
|              |           | (Number)         | SELECT ROAD_ID, SDE.ST_X(SHAPE) AS X FROM ENG.ROADS_STARTPOINT_BM         |
|              |           |                  | ) WHERE X IS NOT NULL ORDER BY ROAD_ID                                    |
+--------------+-----------+------------------+---------------------------------------------------------------------------+
| ST_Y         | < 0.5     | Double precision | SELECT ROAD_ID FROM (                                                     |
|              |           | (Number)         | SELECT ROAD_ID, SDE.ST_Y(SHAPE) AS Y FROM ENG.ROADS_STARTPOINT_BM         |
|              |           |                  | ) WHERE Y IS NOT NULL ORDER BY ROAD_ID                                    |
+--------------+-----------+------------------+---------------------------------------------------------------------------+
| ST_NumPoints | < 0.5     | Integer          | SELECT ROAD_ID FROM (                                                     |
|              |           |                  | SELECT ROAD_ID, SDE.ST_NumPoints(SHAPE) AS NUM_POINTS FROM ENG.ROADS_BM   |
|              |           |                  | ) WHERE NUM_POINTS IS NOT NULL ORDER BY ROAD_ID                           |
+--------------+-----------+------------------+---------------------------------------------------------------------------+
| ST_PointN*   | **9.5**   | ST_POINT         | SELECT ROAD_ID FROM (                                                     |
|              |           | (ST_GEOMETRY     | SELECT ROAD_ID, SDE.ST_PointN(SHAPE,1) AS ST_POINT FROM ENG.ROADS_BM      |
|              |           | subclass)        | ) WHERE ST_POINT IS NOT NULL ORDER BY ROAD_ID                             |
+--------------+-----------+------------------+---------------------------------------------------------------------------+

Documentation fonction: ST_X , ST_Y , ST_NumPoints , ST_PointN

Le résultat? ST_PointNc'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_PointNrenvoie un ST_POINTtype de données de géométrie, qui doit être assez complexe par rapport aux autres fonctions qui renvoient un nombre simple.

Remarque: ST_PointNest délicat. Ce genre de retour est ST_POINTque mon logiciel ne sait pas comment gérer dans un jeu de résultats: ORA-24359: OCIDefineObject not invoked for a Object type or Reference.

Pour contourner cela, je l'ai mis dans une requête en ligne pour empêcher la colonne d'être renvoyée dans l'ensemble de résultats. Mais quand je le fais, la requête ne traite pas réellement la colonne, ce qui va à l'encontre du but du test. Donc , je vérifie si elle est nulle dans la requête externe: WHERE ST_POINT IS NOT NULL ORDER BY RDSEC. Ce faisant, je m'assure que la ST_PointNfonction est réellement utilisée, sans la retourner dans le jeu de résultats.

Et bien sûr, je veux faire un test de pommes à pommes, donc je fais aussi le même genre de requête en ligne pour les autres fonctions (même si ce n'est pas techniquement nécessaire).

5) Allez travailler.

Sur la base des étapes 2, 3 et 4, voici mes conclusions:

  • Le problème est la ST_PointNfonction. 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.
  • Afin d'atteindre les performances dont j'ai besoin, je devrai pré-calculer la requête dans une table ou une vue matérialisée.
  • En ce qui concerne les «..trucs auxquels vous pouvez penser pour réduire le temps d'exécution des requêtes», je pourrais peut-être éliminer certains des sommets des lignes plus longues. Cela me permettrait de supprimer quelques lignes de la table NUMBERS (qui compte actuellement 30 lignes). Cela accélérerait la jointure (bien que tout gain de performances soit minime). Je devrais également examiner tous les index de table, malgré le fait que mes problèmes de performances ne sont pas liés aux index / jointures.
  • Sur la base des tests, je ne pense pas que le problème "... se rapporte au nombre de fois où les fonctions sont exécutées".
  • La requête CTE fournie dans le n ° 5 a été très bien compilée (je suis impressionné que Joe ait réussi à le faire). Étonnamment cependant, le temps d'exécution était de 30 secondes, ce qui n'est pas une amélioration. Je suppose que ST_PointNc'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.

Wilson
la source