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


8

質問:

ESRIのユーザー定義データタイプを使用してOracle 12c ジオデータベースに格納された空間テーブル(道路線)SDE.ST_GEOMETRYがあります。最終的にそれらの座標にアクセスして更新できるように、線の頂点をリストします。SDO_GEOMETRY / Oracle Locatorを使用している場合は、関数を使用し ます。しかし、私はSDO_GEOMETRY / Oracle Locatorを使用しておらず、に同等の関数はありません。唯一の機能私は頂点に関係することを見つけることができますがありますと。SDO_UTIL.GETVERTICESSDE.ST_GEOMETRYSDE.ST_GEOMETRY ST_PointNST_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個の頂点があります(1行あたりの平均は4.38個の頂点)
  • ROADSには3,997行あります
  • NUMBERSには30行あります(1から始まる連番)
  • 結果セットには17,536行あります

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

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


アップデート#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""))"

Justin Caveがここで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秒では十分ではないと言っていました。あなたまたは他の誰かが十分に良いものを正確に定義しましたか?交渉の余地はありますか?クエリは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;

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


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

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

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

1)説明プランを取得できないことを確認します。

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

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

2)要件を検討します。

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

クエリ正確に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)実行時間に最も貢献している関数を特定するためのベンチマーク。

私は(10,000行が含まれて両方の)2つのテーブルを作成しました:ROADS_BMROADS_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ことで、結果セットに返さずに、関数が実際に使用されていることを確認し ます。

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

5)仕事に行きます。

手順2、3、4に基づいて、ここに私の発見があります:

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

6)結論。

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

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.