Cruce unir en una tabla de números para obtener vértices de línea, ¿hay una mejor manera?


8

La pregunta:

Tengo una tabla espacial (líneas de ruta), almacenada usando el SDE.ST_GEOMETRYtipo de datos definido por el usuario de ESRI en una geodatabase Oracle 12c . Quiero enumerar los vértices de las líneas para poder acceder y actualizar sus coordenadas. Si estuviera usando SDO_GEOMETRY / Oracle Locator, entonces usaría la SDO_UTIL.GETVERTICESfunción. Pero no estoy usando SDO_GEOMETRY / Oracle Locator, y no hay una función equivalente en SDE.ST_GEOMETRY. Las únicas SDE.ST_GEOMETRY funciones que puedo encontrar que pertenecen a los vértices son ST_PointNy ST_NumPoints.

Se me ocurrió una consulta que hace todo esto con éxito: obtiene los vértices de la línea como filas (inspirados en esta página ):

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

Es CROSS JOINSlas líneas en la ROADStabla a una NUMBERStabla (y limita los resultados al número de vértices en cada línea).

Estadísticas: (actualizado)

  • Cada línea tiene un máximo de 30 vértices (promedio de 4.38 vértices por línea)
  • CARRETERAS tiene 3,997 líneas
  • NÚMEROS tiene 30 filas (números secuenciales que comienzan en 1)
  • El conjunto de resultados tiene 17.536 filas.

Sin embargo, el rendimiento es pobre (40 segundos), y no puedo evitar pensar: ¿hay una manera más elegante de hacer esto? Para mí, usar una tabla de números y una unión cruzada parece un enfoque descuidado. ¿Hay una mejor manera?

Los términos del laico serían apreciados; Soy un tipo de Obras Públicas, no un DBA.


Actualización n. ° 1:

Si elimino las líneas 3 y 4 (cadena de funciones relacionadas con X e Y) de la consulta, se ejecuta instantáneamente. Pero, por supuesto, no puedo simplemente eliminar estas líneas, necesito las columnas X e Y. Entonces esto me lleva a creer que el rendimiento lento tiene algo que ver con las funciones X e Y.

Sin embargo, si exporto los puntos a una tabla estática y luego ejecuto las funciones X e Y, también se ejecuta instantáneamente.

Entonces, ¿esto significa que el rendimiento lento es causado por las funciones X e Y, excepto, bueno, no, no lo es? Estoy confundido.


Actualización n. ° 2:

Si saco las X e Y de la consulta, las pongo en una consulta externa y agrego ROWNUM a la consulta interna, entonces es mucho más rápido (16 segundos - actualizado):

    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 explica por qué ROWNUM ayuda al rendimiento aquí: ¿por qué agregar ROWNUM a una consulta mejora el rendimiento?

Si bien esta mejora del rendimiento es buena, aún no es lo suficientemente buena. Y no puedo evitar pensar que todavía no entiendo completamente cómo funciona la consulta o por qué es tan lenta como es.

La pregunta sigue en pie: ¿hay una mejor manera?


Los comentarios no son para discusión extendida; Esta conversación se ha movido al chat .
Paul White 9

Respuestas:


7

Sé un poco sobre el rendimiento de Oracle y casi nada sobre los tipos de datos personalizados, pero intentaré darle un plan para mejorar el rendimiento.

1) Verifique que no pueda obtener un plan de explicación.

Es posible obtener planes explicativos incluso si no tiene un software de base de datos sofisticado. ¿Qué pasa si ejecutas set autotrace on explain?

También puedes probar DBMS_XPLAN . Primero guarde el plan envolviendo su consulta con algunas palabras clave adicionales:

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

Entonces ejecute esto:

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

Es posible que ninguno de los dos funcione y usted realmente no puede obtener un plan explicativo. Solo quería verificar eso porque con un plan de explicación será mucho más fácil para la comunidad ayudarlo.

2) Considerar los requisitos.

Dijiste que 20 segundos no son lo suficientemente buenos. ¿Usted o alguien más ha definido exactamente qué es lo suficientemente bueno? ¿Hay espacio para la negociación? ¿Su consulta debe ser exactamente una consulta SELECT? ¿Podría completar una tabla temporal global en un paso y seleccionar los resultados que desea en el siguiente? ¿Podría crear un procedimiento almacenado que devuelva un conjunto de resultados y llamarlo?

3) Establezca un límite inferior para el tiempo requerido para completar la consulta.

Sugiero ejecutar una consulta simple que "engañe" para descubrir cómo sería una consulta bien optimizada. Por ejemplo, ¿cuánto dura esta consulta que solo obtiene los primeros vértices?

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;

Sospecho que eso te dará 4000 filas. Si multiplica el tiempo de respuesta de esa consulta por 17.5 / 4, podría obtener un límite inferior bueno para el tiempo total de ejecución.

Si su límite inferior para el tiempo total de ejecución es más largo que el establecido en el paso 2, entonces necesita ser creativo con su modelo de datos calculando los resultados con anticipación y almacenándolos en tablas o necesita renegociar el tiempo de respuesta requerido.

4) Punto de referencia para descubrir qué funciones están contribuyendo más a su tiempo de ejecución.

Estaba en el camino correcto con la Actualización n. ° 1, pero debe intentar controlar la cantidad de trabajo que se realiza. Por ejemplo, ¿es posible escribir un grupo de consultas relativamente simples que ejecuten cada función exactamente 10000 veces? ¿Cómo se comparan los tiempos de respuesta?

5) Ve a trabajar.

Dependiendo de los requisitos establecidos en el paso 2 y de lo que encontró en el paso 4, intente cualquier truco que se le ocurra para reducir el tiempo de ejecución de la consulta. ¿Eres capaz de calcular previamente los resultados y guardarlos? Si el problema se relaciona con la cantidad de veces que se ejecutan las funciones, entonces la sugerencia de materialización no documentada puede ser útil. Eso obliga a Oracle a crear una tabla temporal oculta detrás de escena para almacenar los resultados. No sé si es compatible con los tipos de datos especiales que está utilizando.

Por ejemplo, ¿tal vez algo como esto funciona mejor? Disculpas si no se compila pero no tengo forma de probar.

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 todavía está atrapado después de todo esto, sospecho que al menos le dará información adicional que puede editar en la pregunta. ¡Buena suerte!


2

Intenté usar CONNECT BY (y DUAL) para ver si sería más rápido, pero no lo es (es casi lo mismo).

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

Se me ocurrió la idea de esta publicación: ¿Cómo calcular rangos en Oracle?


2

Resultados y respuesta a la respuesta de Joe Obbish :

Nota: De ahora en adelante, me referiré a la consulta en la Actualización # 2 como 'la consulta'; No me referiré a la consulta en la pregunta original.

1) Verifique que no pueda obtener un plan de explicación.

No puedo ejecutar set autotrace on explain. Me sale este error:ORA-00922: missing or invalid option (#922)

Pero soy capaz de ejecutar DBMS_XPLAN. Asumí que no podría hacer esto. Afortunadamente me equivoqué. Ahora estoy corriendo explicando planes.

2) Considerar los requisitos.

¿Su consulta debe ser exactamente una consulta SELECT?

Creo que la consulta no tiene por qué ser exactamente una consulta. El software que estoy usando es muy limitado y no permite múltiples sentencias select.

¿Ha definido exactamente cuáles son sus requisitos?

  • La consulta se utilizará para actualizar las coordenadas de vértice después de que se hayan realizado modificaciones en la geometría de línea. Esto normalmente le sucedería a una sola línea a la vez, o tal vez a decenas de líneas, pero no es probable que a miles de líneas. En este escenario, el rendimiento actual de la consulta será adecuado.
  • La consulta también se utilizará para construir una nueva geometría de línea para todas las 3.805 líneas (esto está relacionado con el tema de segmentación dinámica / referencia lineal ). Esto sucederá sobre la marcha en una vista, por lo que el rendimiento es absolutamente crucial. La consulta probablemente necesitará ejecutarse en menos de 5 segundos.

3) Establezca un límite inferior para el tiempo requerido para completar la consulta.

La consulta del primer vértice se ejecuta en 3.75 segundos (devuelve 3805 filas, como se esperaba).

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

El resultado: el límite inferior para el tiempo total de ejecución es más largo que el establecido en el paso 2 (5 segundos). Por lo tanto, creo que la solución es '... ser creativo con mi modelo de datos calculando los resultados con anticipación y almacenándolos en una tabla' (el tiempo de respuesta requerido no es negociable). En otras palabras, crea una vista materializada.

Además, el límite inferior de 16.25 segundos coincide con el tiempo total de ejecución de la consulta en la Actualización n. ° 2 (16 segundos). Creo que esto prueba que mi consulta está totalmente optimizada, dadas las funciones y los datos con los que tengo que trabajar.

4) Punto de referencia para descubrir qué funciones están contribuyendo más a su tiempo de ejecución.

He creado dos tablas (ambas contienen 10.000 filas): ROADS_BMy ROADS_STARTPOINT_BM. He ejecutado consultas simples en las tablas usando cada una de las funciones involucradas. Aquí están los resultados:

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

Documentación de funciones: ST_X , ST_Y , ST_NumPoints , ST_PointN

¿El resultado? ST_PointNes el problema. Su tiempo de respuesta de 9.5 segundos es abismal en comparación con las otras funciones. Sin embargo, supongo que esto tiene un poco de sentido . ST_PointNdevuelve un ST_POINTtipo de datos de geometría, que tiene que ser bastante complejo en comparación con las otras funciones que devuelven un número simple.

Nota: ST_PointNes complicado. Es el tipo de retorno es ST_POINT, que mi software no sabe cómo manejar en un conjunto de resultados: ORA-24359: OCIDefineObject not invoked for a Object type or Reference.

Para evitar esto, lo puse en una consulta en línea para evitar que la columna regrese al conjunto de resultados. Pero cuando hago eso, la consulta en realidad no procesa la columna, lo que anula el propósito de la prueba. Así que puedo comprobar si es nulo en la consulta externa: WHERE ST_POINT IS NOT NULL ORDER BY RDSEC. Al hacer esto, me aseguro de que la ST_PointNfunción se esté utilizando realmente, sin devolverla al conjunto de resultados.

Y, por supuesto, quiero hacer una prueba de manzanas con manzanas, así que también hago el mismo tipo de consulta en línea para las otras funciones (aunque no es técnicamente necesario).

5) Ve a trabajar.

Basado en los pasos 2, 3 y 4, aquí están mis hallazgos:

  • El problema es la ST_PointNfunción. Es lento. Sin embargo, no creo que haya mucho que pueda hacer al respecto. Además de intentar reprogramar / recrear completamente la función con la esperanza de que pudiera hacerlo mejor que los especialistas que la hicieron. No es exactamente práctico.
  • Para lograr el rendimiento que requiero, tendré que calcular previamente la consulta en una tabla o vista materializada.
  • En cuanto a '... trucos que se te ocurran para reducir el tiempo de ejecución de la consulta', podría eliminar algunos de los vértices en las líneas más largas. Esto me permitiría eliminar algunas filas de la tabla NUMBERS (que actualmente tiene 30 filas). Esto aceleraría la unión (aunque cualquier ganancia en rendimiento sería mínima). También debería revisar todos los índices de la tabla, a pesar de que mis problemas de rendimiento no están relacionados con los índices / combinaciones.
  • Según las pruebas, no creo que el problema '... se relacione con la cantidad de veces que se ejecutan las funciones'.
  • La consulta CTE que se proporcionó en el n. ° 5 se compiló muy bien (estoy impresionado de que Joe haya podido lograr esto). Sin embargo, sorprendentemente, el tiempo de ejecución fue de 30 segundos, lo que no es una mejora. Supongo que también ST_PointNes culpa de eso. Sin embargo, la consulta CTE no fue un desperdicio; Aprendí mucho con solo usarlo.

6. Conclusión.

Estoy satisfecho de haber optimizado la consulta tanto como sea posible. Configuraré el cálculo previo y pasaré a lo siguiente. Muchas gracias a Joe Obbish; He aprendido un montón de los pasos que dio.

Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.