Incrocio incrociato su una tabella numerica per ottenere vertici di linea, esiste un modo migliore?


8

La domanda:

Ho una tabella spaziale (linee stradali), memorizzata usando il SDE.ST_GEOMETRYtipo di dati definito dall'utente di ESRI in un geodatabase Oracle 12c . Voglio elencare i vertici di linea in modo da poter infine accedere e aggiornare le loro coordinate. Se stessi usando SDO_GEOMETRY / Oracle Locator, allora userei la SDO_UTIL.GETVERTICESfunzione. Ma non sto usando SDO_GEOMETRY / Oracle Locator e non esiste una funzione equivalente in SDE.ST_GEOMETRY. Le uniche SDE.ST_GEOMETRY funzioni che posso trovare che riguardano i vertici sono ST_PointNe ST_NumPoints.

Ho elaborato una query che esegue correttamente tutto ciò: ottiene i vertici di riga come righe (ispirati a questa pagina ):

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

È CROSS JOINSle linee nella ROADStabella a una NUMBERStabella (e limita i risultati al numero di vertici in ogni riga).

Statistiche: (aggiornato)

  • Ogni riga ha un massimo di 30 vertici (media di 4,38 vertici per riga)
  • ROADS ha 3.997 linee
  • NUMBERS ha 30 righe (numeri sequenziali che iniziano da 1)
  • Il set di risultati ha 17.536 righe

Tuttavia, le prestazioni sono scadenti (40 secondi) e non posso fare a meno di pensare: esiste un modo più elegante per farlo? Per me, usare una tabella dei numeri e un cross join sembra un approccio sciatto. Esiste un modo migliore?

I termini di Layman sarebbero apprezzati; Sono un ragazzo di lavori pubblici, non un DBA.


Aggiornamento n. 1:

Se rimuovo le righe 3 e 4 (stringa di funzioni correlate a X e Y) dalla query, viene eseguito immediatamente. Ma ovviamente non posso semplicemente rimuovere queste righe, ho bisogno delle colonne X e Y. Quindi questo mi porta a credere che le prestazioni lente abbiano qualcosa a che fare con le funzioni X e Y.

Tuttavia, se esporto i punti in una tabella statica e quindi eseguo le funzioni X e Y su di essa, anche questo viene eseguito all'istante.

Quindi, questo significa che le prestazioni lente sono causate dalle funzioni X e Y, tranne, beh, no? Non ho capito bene.


Aggiornamento n. 2:

Se tiro fuori X e Y dalla query, li inserisco in una query esterna e aggiungo ROWNUM alla query interna, allora è molto più veloce (16 secondi - aggiornato):

    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 spiega perché ROWNUM aiuta le prestazioni qui: Perché l'aggiunta di ROWNUM a una query migliora le prestazioni?

Mentre questo miglioramento delle prestazioni è buono, non è ancora abbastanza buono. E non posso fare a meno di pensare che non riesco ancora a capire come funziona la query o perché è così lenta.

La domanda è ancora valida: esiste un modo migliore?


I commenti non sono per una discussione estesa; questa conversazione è stata spostata in chat .
Paul White 9

Risposte:


7

Conosco un po 'le prestazioni di Oracle e praticamente nulla sui tipi di dati personalizzati, ma proverò a darti un piano per migliorare le prestazioni.

1) Verifica di non poter ottenere un piano esplicativo.

È possibile ottenere piani esplicativi anche se non si dispone di un software di database sofisticato. Cosa succede se esegui set autotrace on explain?

Puoi anche provare DBMS_XPLAN . Innanzitutto salva il piano avvolgendo la query con alcune parole chiave aggiuntive:

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

Quindi eseguire questo:

SELECT PLAN_TABLE_OUTPUT FROM TABLE(DBMS_XPLAN.DISPLAY());

È possibile che nessuno di questi funzionerà e davvero non è possibile ottenere un piano esplicativo. Volevo solo verificarlo perché con un piano esplicativo sarà molto più facile per la comunità aiutarti.

2) Considerare i requisiti.

Hai detto che 20 secondi non sono abbastanza buoni. Tu o qualcun altro avete definito esattamente ciò che è abbastanza buono? C'è spazio per la negoziazione? La tua query deve essere esattamente una query SELECT? Potresti popolare una tabella temporanea globale in un passaggio e selezionare i risultati desiderati nel successivo? Potresti creare una procedura memorizzata che restituisce un set di risultati e chiamarlo?

3) Stabilire un limite inferiore per il tempo richiesto per completare la query.

Suggerisco di eseguire una semplice query che "tradisce" per capire come sarebbe una query ben ottimizzata. Ad esempio, quanto tempo richiede questa query che ottiene solo i primi vertici?

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;

Sospetto che ti darà 4000 file. Se moltiplichi il tempo di risposta di quella query per 17,5 / 4, ciò potrebbe darti un limite inferiore per il tempo di esecuzione totale.

Se il limite inferiore per il tempo di esecuzione totale è più lungo di quello stabilito nel passaggio 2, è necessario essere creativi con il modello di dati calcolando i risultati in anticipo e archiviandoli in tabelle oppure è necessario rinegoziare il tempo di risposta richiesto.

4) Benchmark per capire quali funzioni stanno contribuendo maggiormente al tempo di esecuzione.

Eri sulla strada giusta con l'aggiornamento n. 1 ma devi provare a controllare la quantità di lavoro svolto. Ad esempio, è possibile scrivere un gruppo di query relativamente semplici che eseguono ogni funzione esattamente 10000 volte? Come si confrontano i tempi di risposta?

5) Vai a lavorare.

A seconda dei requisiti stabiliti nel passaggio 2 e di ciò che è stato riscontrato nel passaggio 4, provare qualsiasi trucco che si possa pensare per ridurre il tempo di esecuzione della query. Sei in grado di pre-calcolare i risultati e salvarli? Se il problema riguarda il numero di volte in cui le funzioni vengono eseguite , può essere utile il suggerimento materializzato non documentato . Ciò costringe Oracle a creare una tabella temporanea nascosta dietro le quinte per memorizzare i risultati. Non so se è compatibile con i tipi di dati speciali che stai utilizzando.

Ad esempio, forse qualcosa del genere funziona meglio? Mi scuso se non si compila ma non ho modo di testare.

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;

Se sei ancora bloccato dopo tutto questo, sospetto che ti darà almeno ulteriori informazioni che puoi modificare nella domanda. In bocca al lupo!


2

Ho provato a utilizzare CONNECT BY (e DUAL) per vedere se sarebbe stato più veloce, ma non lo è (è più o meno lo stesso).

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

Ho avuto l'idea da questo post: come calcolare gli intervalli in Oracle?


2

Risultati e risposta alla risposta di Joe Obbish :

Nota: da qui in avanti, mi riferirò alla query nell'aggiornamento n. 2 come "la query"; Non mi riferirò alla domanda nella domanda originale.

1) Verifica di non poter ottenere un piano esplicativo.

Non sono in grado di eseguire set autotrace on explain. Ottengo questo errore:ORA-00922: missing or invalid option (#922)

Ma sono in grado di eseguire DBMS_XPLAN. Avevo supposto che non sarei stato in grado di farlo. Fortunatamente, ho sbagliato. Ora sto correndo spiegare i piani.

2) Considerare i requisiti.

La tua query deve essere esattamente una query SELECT?

Credo che la domanda non ha bisogno di essere esattamente una query. Il software che sto usando è molto limitato e non consente dichiarazioni multiple selezionate.

Hai definito esattamente quali sono le tue esigenze?

  • La query verrà utilizzata per aggiornare le coordinate del vertice dopo aver apportato modifiche alla geometria della linea. Questo in genere accadrebbe a una singola riga alla volta, o forse a decine di righe, ma non è probabile che a migliaia di righe. In questo scenario, le prestazioni attuali della query saranno adeguate.
  • La query verrà inoltre utilizzata per costruire una nuova geometria di linea per tutte le 3.805 linee (questo è correlato all'argomento della segmentazione dinamica / riferimento lineare ). Ciò avverrà al volo in una vista, quindi le prestazioni sono assolutamente cruciali. La query dovrebbe probabilmente essere eseguita in meno di 5 secondi.

3) Stabilire un limite inferiore per il tempo richiesto per completare la query.

La query del primo vertice viene eseguita in 3,75 secondi (restituisce 3805 righe, come previsto).

3.75 sec * (16495 total / 3805 lines) = 16.25 sec

Il risultato: il limite inferiore per il tempo di esecuzione totale è più lungo di quello stabilito nel passaggio 2 (5 secondi). Pertanto, penso che la soluzione sia "... diventare creativi con il mio modello di dati calcolando i risultati in anticipo e memorizzandoli in una tabella" (il tempo di risposta richiesto non è negoziabile). In altre parole, crea una visione materializzata.

Inoltre, il limite inferiore di 16,25 secondi corrisponde al tempo di esecuzione totale della query nell'aggiornamento n. 2 (16 secondi). Penso che ciò dimostri che la mia query è completamente ottimizzata, date le funzioni e i dati con cui devo lavorare.

4) Benchmark per capire quali funzioni stanno contribuendo maggiormente al tempo di esecuzione.

Ho creato due tabelle (entrambe contengono 10.000 righe): ROADS_BMe ROADS_STARTPOINT_BM. Ho eseguito semplici query sui tavoli utilizzando ciascuna delle funzioni coinvolte. Ecco i risultati:

               +-----------+------------------+---------------------------------------------------------------------------+
               | 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                             |
+--------------+-----------+------------------+---------------------------------------------------------------------------+

Documentazione funzioni: ST_X , ST_Y , ST_NumPoints , ST_PointN

Il risultato? ST_PointNè il problema. Il tempo di risposta di 9,5 secondi è spaventoso rispetto alle altre funzioni. Suppongo che questo abbia un po 'di senso però. ST_PointNrestituisce un ST_POINTtipo di dati di geometria, che deve essere abbastanza complesso rispetto alle altre funzioni che restituiscono un numero semplice.

Nota: ST_PointNè complicato. E 'di tipo di ritorno è ST_POINT, che il mio software non sa come gestire in un set di risultati: ORA-24359: OCIDefineObject not invoked for a Object type or Reference.

Per ovviare a questo, l'ho inserito in una query incorporata per impedire che la colonna venga restituita al set di risultati. Ma quando lo faccio, la query non elabora effettivamente la colonna, il che vanifica lo scopo del test. Così ho verificare se è nullo nella query esterna: WHERE ST_POINT IS NOT NULL ORDER BY RDSEC. In questo modo, mi assicuro che la ST_PointNfunzione venga effettivamente utilizzata, senza restituirla al set di risultati.

E, naturalmente, voglio fare un test da mele a mele, quindi faccio lo stesso tipo di query inline anche per le altre funzioni (anche se non è tecnicamente necessario).

5) Vai a lavorare.

Sulla base dei passaggi 2, 3 e 4, ecco i miei risultati:

  • Il problema è la ST_PointNfunzione. È lento. Non credo ci sia molto da fare al riguardo però. Oltre a tentare di riprogrammare / ricreare completamente la funzione nella speranza di poter fare meglio degli specialisti che l'hanno creata. Non esattamente pratico.
  • Per ottenere le prestazioni di cui ho bisogno, dovrò pre-calcolare la query in una tabella o vista materializzata.
  • Per quanto riguarda i "trucchi che puoi pensare per ridurre il tempo di esecuzione della query", potrei essere in grado di eliminare alcuni vertici nelle righe più lunghe. Ciò mi consentirebbe di rimuovere alcune righe dalla tabella NUMERI (che attualmente ha 30 righe). Ciò accelererebbe il join (anche se qualsiasi guadagno in termini di prestazioni sarebbe minimo). Dovrei anche rivedere tutti gli indici delle tabelle, nonostante il fatto che i miei problemi di prestazioni non siano correlati agli indici / join.
  • Sulla base dei test, non credo che il problema "... sia correlato al numero di volte in cui le funzioni vengono eseguite".
  • La query CTE fornita nel numero 5 è stata compilata correttamente (sono impressionato dal fatto che Joe sia stato in grado di farlo). Sorprendentemente, il tempo di esecuzione è stato di 30 secondi, il che non è un miglioramento. Immagino ST_PointNsia anche colpa di questo. La query CTE non è stata comunque uno spreco; Ho imparato molto semplicemente usandolo.

6. Conclusione.

Sono soddisfatto di aver ottimizzato la query il più possibile. Preparerò il pre-calcolo e passerò alla cosa successiva. Un grande ringraziamento a Joe Obbish; Ho imparato un sacco dai passaggi che ha fornito.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.