Junção cruzada em uma tabela de números para obter vértices de linha, existe uma maneira melhor?

8

A questão:

Eu tenho uma tabela espacial (road lines), armazenada usando o SDE.ST_GEOMETRYtipo de dados definido pelo usuário da ESRI em um geodatabase Oracle 12c . Quero listar os vértices da linha para que eu possa acessar e atualizar suas coordenadas. Se eu estivesse usando SDO_GEOMETRY / Oracle Locator, usaria a SDO_UTIL.GETVERTICESfunção Mas não estou usando SDO_GEOMETRY / Oracle Locator e não há função equivalente no SDE.ST_GEOMETRY. As únicas SDE.ST_GEOMETRY funções que posso encontrar que pertencem aos vértices são ST_PointNe ST_NumPoints.

Eu vim com uma consulta que faz tudo isso com êxito - obtém os vértices da linha como linhas (inspiradas nesta 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""))"

Ele CROSS JOINSalinha as linhas ROADSde uma NUMBERStabela (e limita os resultados ao número de vértices em cada linha).

Estatísticas: (atualizado)

  • Cada linha tem no máximo 30 vértices (média de 4,38 vértices por linha)
  • ROADS possui 3.997 linhas
  • NUMBERS possui 30 linhas (números sequenciais iniciando em 1)
  • O conjunto de resultados possui 17.536 linhas

No entanto, o desempenho é ruim (40 segundos) e não consigo deixar de pensar - existe uma maneira mais elegante de fazer isso? Para mim, usar uma tabela de números e uma junção cruzada parece uma abordagem desleixada. Existe uma maneira melhor?

Os termos do leigo seriam apreciados; Eu sou um cara de obras públicas, não um DBA.


Atualização # 1:

Se eu remover as linhas 3 e 4 (sequência de funções relacionadas a X e Y) da consulta, ela será executada instantaneamente. Mas é claro, não posso simplesmente remover essas linhas, preciso das colunas X e Y. Portanto, isso me leva a acreditar que o desempenho lento tem algo a ver com as funções X e Y.

No entanto, se eu exportar os pontos para uma tabela estática e executar as funções X e Y nela, isso também será executado instantaneamente.

Então, isso significa que o desempenho lento é causado pelas funções X e Y, exceto, bem, não, não é? Estou confuso.


Atualização # 2:

Se eu retirar os X e Y da consulta, colocá-los em uma consulta externa e adicionar ROWNUM à consulta interna, será muito mais rápido (16 segundos - atualizado):

    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 que o ROWNUM ajuda o desempenho aqui: Por que adicionar o ROWNUM a uma consulta melhora o desempenho?

Embora essa melhoria de desempenho seja boa, ainda não é suficiente. E não posso deixar de pensar que ainda não entendo completamente como a consulta funciona ou por que é tão lenta quanto é.

A questão ainda permanece: existe uma maneira melhor?

Wilson
fonte
Comentários não são para discussão prolongada; esta conversa foi movida para o bate-papo .
Paul White 9

Respostas:

7

Sei um pouco sobre o desempenho do Oracle e praticamente nada sobre tipos de dados personalizados, mas tentarei dar um plano para melhorar o desempenho.

1) Verifique se você não pode obter um plano de explicação.

É possível obter planos de explicação mesmo se você não tiver um sofisticado software de banco de dados. O que acontece se você executarset autotrace on explain ?

Você também pode tentar DBMS_XPLAN . Primeiro salve o plano envolvendo sua consulta com algumas palavras-chave extras:

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

Em seguida, execute o seguinte:

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

É possível que nenhum deles funcione e você realmente não pode obter um plano de explicação. Eu só queria verificar isso porque, com um plano de explicação, será muito mais fácil para a comunidade ajudá-lo.

2) Considere os requisitos.

Você disse que 20 segundos não é bom o suficiente. Você ou alguém definiu exatamente o que é bom o suficiente? Existe algum espaço para negociação? Sua consulta precisa ser exatamente uma consulta SELECT? Você poderia preencher uma tabela temporária global em uma etapa e selecionar os resultados desejados na próxima? Você poderia criar um procedimento armazenado que retorne um conjunto de resultados e chame isso?

3) Estabeleça um limite inferior para o tempo necessário para concluir a consulta.

Sugiro executar uma consulta simples que "engana" para descobrir como seria uma consulta otimizada. Por exemplo, quanto tempo essa consulta que obtém apenas os primeiros vértices leva?

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;

Eu suspeito que isso lhe dará 4000 linhas. Se você multiplicar o tempo de resposta dessa consulta por 17,5 / 4, isso poderá fornecer um limite inferior bom para o tempo total de execução.

Se o limite inferior para o tempo total de execução for maior do que o estabelecido na etapa 2, você precisará ser criativo com seu modelo de dados calculando os resultados com antecedência e armazenando-os em tabelas ou renegociar o tempo de resposta necessário.

4) Benchmark para descobrir quais funções estão contribuindo mais para o seu tempo de execução.

Você estava no caminho certo com a atualização nº 1, mas precisa tentar controlar a quantidade de trabalho que está sendo realizado. Por exemplo, é possível escrever um grupo de consultas relativamente simples que executam cada função exatamente 10000 vezes? Como os tempos de resposta se comparam?

5) Vá trabalhar.

Dependendo dos requisitos estabelecidos na etapa 2 e do que você encontrou na etapa 4, tente qualquer truque para reduzir o tempo de execução da consulta. Você é capaz de pré-calcular resultados e salvá-los? Se o problema estiver relacionado ao número de vezes que as funções são executadas, a dica de materialização não documentada pode ser útil. Isso força a Oracle a criar uma tabela temporária oculta nos bastidores para armazenar os resultados. Não sei se é compatível com os tipos de dados especiais que você está usando.

Por exemplo, talvez algo assim tenha um desempenho melhor? Desculpas se não compilar, mas não tenho como testar.

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 você ainda estiver parado depois de tudo isso, suspeito que ao menos lhe fornecerá informações adicionais que você pode editar na pergunta. Boa sorte!

Joe Obbish
fonte
2

Tentei usar CONNECT BY (e DUAL) para ver se seria mais rápido, mas não é (é quase o mesmo).

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

Eu tive a ideia deste post: Como calcular intervalos no Oracle?

Wilson
fonte
2

Resultados e resposta à resposta de Joe Obbish :

Nota: Daqui em diante, vou me referir à consulta na Atualização 2 como 'a consulta'; Não vou me referir à consulta na pergunta original.

1) Verifique se você não pode obter um plano de explicação.

Não consigo executar set autotrace on explain . Eu recebo este erro:ORA-00922: missing or invalid option (#922)

Mas eu sou capaz de executarDBMS_XPLAN . Eu tinha assumido que seria incapaz de fazer isso. Felizmente, eu estava errado. Agora estou executando planos de explicação.

2) Considere os requisitos.

Sua consulta precisa ser exatamente uma consulta SELECT?

Eu acho que a consulta faz precisa ser exatamente uma consulta. O software que estou usando é muito limitado e não permite várias instruções de seleção.

Você definiu exatamente quais são seus requisitos?

  • A consulta será usada para atualizar coordenadas de vértice após a edição da geometria da linha. Isso normalmente aconteceria com uma única linha de cada vez, ou talvez dezenas de linhas, mas provavelmente não com milhares de linhas. Nesse cenário, o desempenho atual da consulta será adequado.
  • A consulta também será usada para construir uma nova geometria de linha para todas as 3.805 linhas (isso está relacionado ao assunto de segmentação dinâmica / referência linear ). Isso acontecerá rapidamente, portanto, o desempenho é absolutamente crucial. A consulta provavelmente precisaria ser executada em menos de 5 segundos.

3) Estabeleça um limite inferior para o tempo necessário para concluir a consulta.

A consulta do primeiro vértice é executada em 3,75 segundos (retorna 3805 linhas, conforme o esperado).

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

O resultado: o limite inferior para o tempo total de execução é maior do que o estabelecido na etapa 2 (5 segundos). Portanto, acho que a solução é '... ser criativo com meu modelo de dados, calculando os resultados antecipadamente e armazenando-os em uma tabela' (o tempo de resposta necessário não é negociável). Em outras palavras, faça uma visualização materializada.

Além disso, o limite inferior de 16,25 segundos corresponde ao tempo total de execução da consulta na Atualização 2 (16 segundos). Acho que isso prova que minha consulta está totalmente otimizada, dadas as funções e os dados com os quais tenho que trabalhar.

4) Benchmark para descobrir quais funções estão contribuindo mais para o seu tempo de execução.

Eu criei duas tabelas (ambas contêm 10.000 linhas): ROADS_BMe ROADS_STARTPOINT_BM. Eu executei consultas simples nas tabelas usando cada uma das funções envolvidas. Aqui estão os 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                             |
+--------------+-----------+------------------+---------------------------------------------------------------------------+

Documentação de função: ST_X , ST_Y , ST_NumPoints , ST_PointN

O resultado? ST_PointNé o problema. Seu tempo de resposta de 9,5 segundos é péssimo em comparação com as outras funções. Suponho que isso faça algum sentido . ST_PointNretorna um ST_POINTtipo de dados de geometria, que deve ser bastante complexo em comparação com as outras funções que retornam um número simples.

Nota: ST_PointNé complicado. É do tipo de retorno é ST_POINT, que o meu software não sabe como lidar com um conjunto de resultados: ORA-24359: OCIDefineObject not invoked for a Object type or Reference.

Para contornar isso, coloquei-o em uma consulta embutida para impedir que a coluna retornasse ao conjunto de resultados. Mas quando faço isso, a consulta não processa a coluna, o que anula o objetivo do teste. Então eu verificar se ele é nulo na consulta externa: WHERE ST_POINT IS NOT NULL ORDER BY RDSEC. Ao fazer isso, garanto que a ST_PointNfunção esteja realmente sendo usada, sem retorná-la ao conjunto de resultados.

E, é claro, eu quero fazer um teste de maçãs para maçãs, então também faço o mesmo tipo de consulta embutida para as outras funções (mesmo que não seja tecnicamente necessário).

5) Vá trabalhar.

Com base nas etapas 2, 3 e 4, aqui estão minhas descobertas:

  • O problema é a ST_PointNfunção. Está lento. Acho que não há muito o que fazer sobre isso. Além de tentar reprogramar / recriar completamente a função, na esperança de que eu pudesse me sair melhor do que os especialistas que a criaram. Não é exatamente prático.
  • Para alcançar o desempenho exigido, precisarei pré-calcular a consulta em uma tabela ou exibição materializada.
  • No que diz respeito aos 'truques que você pode pensar para reduzir o tempo de execução da consulta', talvez eu consiga eliminar alguns dos vértices nas linhas mais longas. Isso me permitiria remover algumas linhas da tabela NUMBERS (que atualmente possui 30 linhas). Isso aceleraria a junção (embora qualquer ganho no desempenho fosse mínimo). Também devo revisar todos os índices de tabela, apesar de meus problemas de desempenho não estarem relacionados a índices / associações.
  • Baseado no teste, não acho que o problema "... esteja relacionado ao número de vezes que as funções são executadas".
  • A consulta CTE que foi fornecida no número 5 foi compilada (estou impressionada que Joe conseguiu fazer isso). Surpreendentemente, o tempo de execução foi de 30 segundos, o que não é uma melhoria. Eu acho que ST_PointNé o culpado por isso também. A consulta CTE não foi um desperdício; Eu aprendi muito apenas usando isso.

6. Conclusão.

Estou satisfeito por otimizar a consulta o máximo possível. Vou configurar o pré-cálculo e passar para a próxima. Um grande obrigado a Joe Obbish; Eu aprendi muito com as etapas que ele forneceu.

Wilson
fonte