web-dev-qa-db-ja.com

数値テーブルでクロス結合してライン頂点を取得しますが、より良い方法はありますか?

質問:

空間テーブル (道路線)があり、ESRIの SDE.ST_GEOMETRY Oracle 12cのユーザー定義データ型 geodatabase を使用して保存されています。最終的にそれらの座標にアクセスして更新できるように、線の頂点をリストしたいと思います。 SDO_GEOMETRY/Oracle Locatorを使用している場合は、 SDO_UTIL.GETVERTICES 関数を使用します。しかし、私はSDO_GEOMETRY/Oracle Locatorを使用しておらず、SDE.ST_GEOMETRYに同等の関数はありません。唯一のSDE.ST_GEOMETRYfunctions 頂点に関連するのは 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""))"

これは、ROADSテーブルの行をNUMBERSテーブルにCROSS JOINSします(結果を各行の頂点の数に制限します)。

統計:(更新済み)

  • 各線には最大30個の頂点があります(1行あたりの平均は4.38個の頂点)
  • ROADSには3,997行あります
  • NUMBERSには30行あります(1から始まる連番)
  • 結果セットには17,536行あります

ただし、パフォーマンスが悪い(40秒)ので、どうしても考えなければなりません。これを行うためのよりエレガントな方法はありますか?私にとって、数値テーブルとクロス結合を使用することは、ずさんなアプローチのように思えます。もっと良い方法はありますか?

レイマンの用語をいただければ幸いです。私はDBAではなく、Public Worksの人です。


更新#1:

クエリから行3&4(X&Y関連関数の文字列)を削除すると、即座に実行されます。しかし、もちろん、私はこれらの行を単に削除することはできません。need 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""))"

Justin Caveが、ここでROWNUMがパフォーマンスに役立つ理由を説明します。 ROWNUMをクエリに追加するとパフォーマンスが向上する理由

このパフォーマンスの改善は良好ですが、まだ十分ではありません。そして、私は仕方がありませんが、クエリがどのように機能するか、なぜクエリが遅いのか、まだ完全には理解していません。

問題はまだ残っています:より良い方法はありますか?

8
Wilson

Oracleのパフォーマンスについては少し知っており、カスタムデータ型についてはほとんど何も知りませんが、パフォーマンスを向上させるための計画を提示します。

1)Explain Planを取得できないことを確認します。

洗練されたデータベースソフトウェアを持っていない場合でも、説明計画を取得することは可能です。 _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秒では十分ではないと言っていました。あなたや他の誰かが十分に良いものを正確に定義しましたか?交渉の余地はありますか?クエリは厳密に1つのSELECTクエリである必要がありますか? グローバル一時テーブル を1つのステップで入力し、次のステップで必要な結果を選択できますか? 結果セットを返すストアドプロシージャ を作成してそれを呼び出すことができますか?

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;
_

これらすべてを試しても問題が解決しない場合は、質問に編集できる追加情報が少なくとも表示されると思います。幸運を!

7
Joe Obbish

Joe Obbishの回答に対する結果と応答

注:ここからは、Update#2のクエリを「クエリ」と呼びます。元の質問のクエリについては触れません。

1)Explain Planを取得できないことを確認します。

_set autotrace on explain_を実行できません。私はこのエラーを受け取ります:ORA-00922: missing or invalid option (#922)

しかし、私はamが_DBMS_XPLAN_を実行できます。私はこれができないだろうと思っていました。幸いにも、私は間違っていました。現在、説明プランを実行しています。

2)要件を検討します。

クエリは厳密に1つのSELECTクエリである必要がありますか?

クエリdoesは正確に1つのクエリである必要があると思います。私が使用しているソフトウェアは非常に制限されており、複数のselectステートメントを使用できません。

要件を正確に定義しましたか?

  • クエリは、ラインジオメトリが編集された後、 頂点座標の更新 に使用されます。これは通常、一度に1行または数十行発生しますが、数千行には発生しません。このシナリオでは、現在のクエリのパフォーマンスで十分です。
  • このクエリは、3,805行すべての新しいラインジオメトリを構築するためにも使用されます(これは動的セグメンテーションの主題に関連しています/ 線形参照 )。これはオンザフライでビューで発生するため、パフォーマンスは絶対に重要です。クエリは、おそらく5秒未満で実行する必要があります。

3)クエリの完了に必要な時間の下限を設定します。

最初の頂点クエリは3.75秒で実行されます(予想どおり3805行を返します)。

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

結果:合計実行時間の下限は、手順2で設定したもの(5秒)よりも長くなります。したがって、解決策は「...事前に結果を計算してテーブルに保存することにより、データモデルで創造的になる」ことです(必要な応答時間は交渉不可能です)。つまり、マテリアライズドビューを作成します。

さらに、16.25秒の下限は、更新#2のクエリの合計実行時間(16秒)と一致します。これは、操作する必要のある関数とデータを考えると、クエリが完全に最適化されていることを証明していると思います。

4)実行時間に最も貢献している関数を特定するためのベンチマーク

2つのテーブル(どちらも10,000行を含む)を作成しました:_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_XST_YST_NumPointsST_PointN

結果? _ST_PointN_が問題です。それは他の機能と比較して9.5秒の応答時間はひどいです。私はこれが少し意味を作ると思います。 _ST_PointN_は _ST_POINT_ ジオメトリデータタイプを返します。これは、単純な数値を返す他の関数と比べてかなり複雑である必要があります。

注:_ST_PointN_はトリッキーです。その戻り値の型は _ST_POINT_ であり、これは私のソフトウェアが結果セットでの処理方法を認識していません:_ORA-24359: OCIDefineObject not invoked for a Object type or Reference_。

これを回避するために、インラインクエリに配置して、列が結果セットに返されないようにします。しかし、その場合、クエリは実際には列を処理しないため、テストの目的に反します。したがって、外部クエリでそれがnullかどうかを確認します:_WHERE ST_POINT IS NOT NULL ORDER BY RDSEC_。これを行うことにより、_ST_PointN_関数が結果セットに返されることなく、実際に使用されていることを確認します。

そしてもちろん、私はりんご同士のテストをしたいので、他の関数に対しても同様のインラインクエリを実行します(技術的には必要ありませんが)。

5)仕事に行きます

手順2、3、4に基づいて、ここに私の調査結果があります。

  • 問題は_ST_PointN_関数です。 それは遅いです。しかし、これについて私ができることは多くないと思います。関数を完全に再プログラム/再作成する以外は、それを作成したスペシャリストよりも上手くできることを期待しています。 正確には実用的ではありません。
  • 必要なパフォーマンスを実現するために、テーブルまたはマテリアライズドビューでクエリを事前計算する必要があります。
  • 「クエリの実行時間を短縮するために考えられるトリック」については、長い行の一部の頂点を削除できる可能性があります。これにより、NUMBERSテーブル(現在は30行)から数行を削除できます。これにより、結合が高速化されます(ただし、パフォーマンスの向上は最小限になります)。また、パフォーマンスの問題はインデックス/結合とは関係がないという事実にもかかわらず、すべてのテーブルインデックスも確認する必要があります。
  • テストに基づいて、問題は「関数が実行される回数に関連する」とは思わない。
  • #5で提供されたCTEクエリは問題なくコンパイルされました(Joeがこれをうまく実行できたことに感銘を受けました)。驚いたことに、実行時間は30秒で、これは改善されていません。 _ST_PointN_もそのせいだと思います。 CTEクエリは無駄ではありませんでした。使うだけでたくさんのことを学びました。

6)結論

クエリを可能な限り最適化したことに満足しています。事前計算を設定し、次のステップに進みます。 Joe Obbishに感謝します。私は彼が提供したステップからトンを学びました。

2
Wilson

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
Wilson