Перекрестное соединение на таблице чисел, чтобы получить вершины линии, есть ли лучший способ?


8

Вопрос:

У меня есть пространственная таблица (дорожные линии), хранящаяся с использованием SDE.ST_GEOMETRYпользовательского типа данных ESRI в базе геоданных Oracle 12c . Я хочу перечислить вершины линий, чтобы в конечном итоге получить доступ и обновить их координаты. Если бы я использовал SDO_GEOMETRY / Oracle Locator, я бы использовал эту SDO_UTIL.GETVERTICESфункцию. Но я не использую SDO_GEOMETRY / Oracle Locator, и нет эквивалентной функции в SDE.ST_GEOMETRY. Единственные SDE.ST_GEOMETRY функции, которые я могу найти, которые относятся к вершинам, это ST_PointNи ST_NumPoints.

Я пришел с запросом, который успешно все это делает - получает вершины линий в виде строк (вдохновлено этой страницей ):

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 JOINSстроки в ROADSтаблице к NUMBERSтаблице (и ограничивает результаты количеством вершин в каждой строке).

Статистика: (обновлено)

  • Каждая строка имеет максимум 30 вершин (в среднем 4,38 вершин на линию)
  • ДОРОГ имеет 3,997 линий
  • ЧИСЛО имеет 30 строк (порядковые номера, начинающиеся с 1)
  • Результирующий набор содержит 17 536 строк

Тем не менее, производительность плохая (40 секунд), и я не могу не думать - есть ли более элегантный способ сделать это? Для меня использование таблицы чисел и перекрестного соединения кажется небрежным подходом. Есть ли способ лучше?

Условия Layman будут оценены; Я парень из общественных работ, а не администратор.


Обновление № 1:

Если я удаляю строки 3 и 4 (строки функций, связанных с X & Y) из запроса, он выполняется мгновенно. Но, конечно, я не могу просто удалить эти строки, мне нужны столбцы X & Y. Так что это наводит меня на мысль, что низкая производительность как-то связана с функциями X & Y.

Однако, если я экспортирую точки в статическую таблицу, а затем запусту на ней функции X & Y, это также будет выполнено мгновенно.

Итак, означает ли это, что низкая производительность вызвана функциями X & Y, кроме, ну, нет, это не так? Я смущен.


Обновление № 2:

Если я выведу X и Y из запроса, добавлю их во внешний запрос и добавлю ROWNUM к внутреннему запросу, то это будет намного быстрее (16 секунд - обновлено):

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

Джастин Кейв объясняет, почему ROWNUM помогает повысить производительность: почему добавление ROWNUM в запрос повышает производительность?

Хотя это улучшение производительности и хорошо, но пока недостаточно. И я не могу не думать, что до сих пор не до конца понимаю, как работает запрос или почему он такой медленный, как он есть.

Вопрос все еще стоит: есть ли лучший путь?


Комментарии не для расширенного обсуждения; этот разговор был перенесен в чат .
Пол Уайт 9

Ответы:


7

Я немного знаю о производительности Oracle и почти ничего не знаю о пользовательских типах данных, но постараюсь дать вам план по повышению производительности.

1) Убедитесь, что вы не можете получить план объяснения.

Можно получить планы объяснения, даже если у вас нет сложного программного обеспечения базы данных. Что произойдет, если вы выполните set autotrace on explain?

Вы также можете попробовать DBMS_XPLAN . Сначала сохраните план, добавив в ваш запрос несколько дополнительных ключевых слов:

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

Затем выполните это:

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

Возможно, что ни один из них не сработает, и вы действительно не сможете получить план объяснения. Я просто хотел проверить это, потому что с планом объяснения сообществу будет намного легче помочь вам.

2) Учитывайте требования.

Вы сказали, что 20 секунд недостаточно. Вы или кто-то еще определил, что именно достаточно хорошо? Есть ли место для переговоров? Ваш запрос должен быть точно одним запросом SELECT? Не могли бы вы заполнить глобальную временную таблицу за один шаг и выбрать результаты, которые вы хотели в следующем? Не могли бы вы создать хранимую процедуру, которая возвращает набор результатов и вызвать его?

3) Установите нижнюю границу времени, необходимого для завершения запроса.

Я предлагаю выполнить простой запрос, который «обманывает», чтобы выяснить, как будет выглядеть хорошо оптимизированный запрос. Например, сколько времени занимает этот запрос, который получает только первые вершины?

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;

Я подозреваю, что даст вам 4000 строк. Если вы умножите время ответа этого запроса на 17,5 / 4, это может дать вам хорошую нижнюю границу для общего времени выполнения.

Если ваша нижняя граница для общего времени выполнения длиннее, чем вы установили на шаге 2, вам нужно либо проявить творческий подход к своей модели данных, заранее рассчитав результаты и сохранив их в таблицах, либо пересмотреть необходимое время отклика.

4) Тест, чтобы выяснить, какие функции вносят больше всего в ваше время выполнения.

С обновлением № 1 вы были на правильном пути, но вам нужно попытаться контролировать объем выполняемой работы. Например, можно ли написать группу относительно простых запросов, которые выполняют каждую функцию ровно 10000 раз? Как сравниваются времена отклика?

5) Иди на работу.

В зависимости от требований, установленных на шаге 2, и того, что вы нашли на шаге 4, попробуйте любой прием, который вы можете придумать, чтобы сократить время выполнения запроса. Вы можете предварительно рассчитать результаты и сохранить их? Если проблема связана с тем, сколько раз выполнялись функции, то недокументированная подсказка материализации может оказаться полезной. Это заставляет Oracle создавать скрытые временные таблицы за кулисами для хранения результатов. Я не знаю, совместимо ли это со специальными типами данных, которые вы используете.

Например, может быть, что-то подобное работает лучше? Извинения, если он не компилируется, но у меня нет возможности проверить.

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;

Если вы все еще застряли после всего этого, я подозреваю, что это по крайней мере даст вам дополнительную информацию, которую вы можете отредактировать в вопросе. Удачи!


2

Я попытался использовать CONNECT BY (и DUAL), чтобы увидеть, будет ли это быстрее, но это не так (примерно так же).

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

Я получил идею из этого поста: Как рассчитать диапазоны в Oracle?


2

Результаты и ответ на ответ Джо Оббиша :

Примечание. С этого момента я буду ссылаться на запрос в Обновлении № 2 как на «запрос»; Я не буду ссылаться на запрос в оригинальном вопросе.

1) Убедитесь, что вы не можете получить план объяснения.

Я не могу выполнить set autotrace on explain. Я получаю эту ошибку:ORA-00922: missing or invalid option (#922)

Но я могу выполнить DBMS_XPLAN. Я предполагал, что я не смогу сделать это. К счастью, я был не прав. Я сейчас бегу объяснять планы.

2) Учитывайте требования.

Ваш запрос должен быть точно одним запросом SELECT?

Я думаю , что вопрос действительно должен быть точно один запрос. Программное обеспечение, которое я использую, очень ограничено и не позволяет использовать несколько операторов выбора.

Вы точно определили свои требования?

  • Запрос будет использоваться для обновления координат вершин после внесения изменений в геометрию линии. Обычно это происходит либо с одной строкой за раз, либо, возможно, с десятками строк, но вряд ли с тысячами строк. В этом случае текущая производительность запроса будет адекватной.
  • Запрос также будет использоваться для построения новой геометрии линии для всех 3805 строк (это связано с темой динамической сегментации / линейных ссылок ). Это произойдет на лету, поэтому производительность абсолютно необходима. Скорее всего, запрос потребуется выполнить менее чем за 5 секунд.

3) Установите нижнюю границу времени, необходимого для завершения запроса.

Запрос первой вершины выполняется за 3,75 секунды (возвращает 3805 строк, как и ожидалось).

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

Результат: нижняя граница для общего времени выполнения длиннее, чем я установил на шаге 2 (5 секунд). Поэтому я думаю, что решение состоит в том, чтобы «... проявить творческий подход к моей модели данных, заранее рассчитав результаты и сохранив их в таблице» (требуемое время ответа не подлежит обсуждению). Другими словами, сделайте материализованное представление.

Кроме того, нижняя граница в 16,25 секунды соответствует общему времени выполнения запроса в Обновлении № 2 (16 секунд). Я думаю, это доказывает, что мой запрос полностью оптимизирован, учитывая функции и данные, с которыми мне приходится работать.

4) Тест, чтобы выяснить, какие функции вносят больше всего в ваше время выполнения.

Я создал две таблицы (обе содержат 10000 строк): ROADS_BMи ROADS_STARTPOINT_BM. Я выполнил простые запросы к таблицам, используя каждую из задействованных функций. Вот результаты:

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

Документация по функциям: ST_X , ST_Y , ST_NumPoints , ST_PointN

Результат? ST_PointNэто проблема. Время отклика составляет 9,5 секунд, это ужасно по сравнению с другими функциями. Я предполагаю, что это имеет смысл, хотя. ST_PointNвозвращает ST_POINTтип данных геометрии, который должен быть довольно сложным по сравнению с другими функциями, которые возвращают простое число.

Примечание: ST_PointNэто сложно. Это возвращаемый тип ST_POINT, который моя программа не знает , как обращаться в результирующем наборе: ORA-24359: OCIDefineObject not invoked for a Object type or Reference.

Чтобы обойти это, я поместил его во встроенный запрос, чтобы столбец не возвращался в набор результатов. Но когда я это делаю, запрос фактически не обрабатывает столбец, что противоречит цели теста. Так что проверить , если она равна нулю во внешнем запросе: WHERE ST_POINT IS NOT NULL ORDER BY RDSEC. Делая это, я гарантирую, что ST_PointNфункция фактически используется, не возвращая ее в набор результатов.

И, конечно же, я хочу провести тест «от яблока к яблоку», поэтому я выполняю тот же тип встроенного запроса и для других функций (хотя это не является технически необходимым).

5) Иди на работу.

Основываясь на шагах 2, 3 и 4, вот мои выводы:

  • Проблема в ST_PointNфункции. Это медленно. Я не думаю, что я могу с этим многое сделать. Кроме попыток полностью перепрограммировать / воссоздать функцию в надежде, что я смогу добиться большего успеха, чем специалисты, которые ее сделали. Не совсем практично.
  • Чтобы добиться требуемой производительности, мне нужно предварительно вычислить запрос в виде таблицы или материализованного представления.
  • Что касается «… уловок, которые вы можете придумать для сокращения времени выполнения запроса», я мог бы устранить некоторые вершины в более длинных строках. Это позволило бы мне удалить несколько строк из таблицы NUMBERS (которая в настоящее время имеет 30 строк). Это ускорит объединение (хотя любой выигрыш в производительности будет минимальным). Я также должен проверить все табличные индексы, несмотря на то, что мои проблемы с производительностью не связаны с индексами / объединениями.
  • Основываясь на тестировании, я не думаю, что проблема «... связана с тем, сколько раз выполнялись функции».
  • Запрос CTE, который был предоставлен в # 5, скомпилирован просто отлично (я впечатлен, что Джо смог осуществить это). Удивительно, но время выполнения составило 30 секунд, что не является улучшением. Я думаю ST_PointN, в этом тоже виноват. Запрос CTE не был пустой тратой; Я многому научился, просто используя его.

6. Заключение.

Я удовлетворен тем, что максимально оптимизировал запрос. Я настрою предварительный расчет и перейду к следующему. Большое спасибо Джо Оббишу; Я узнал тонну из шагов, которые он обеспечил.

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.