Gabung silang pada tabel angka untuk mendapatkan simpul garis, apakah ada cara yang lebih baik?

8

Pertanyaan:

Saya memiliki tabel spasial (garis jalan), disimpan menggunakan SDE.ST_GEOMETRYtipe data ESRI yang ditentukan pengguna dalam geodatabase Oracle 12c . Saya ingin membuat daftar garis simpul sehingga saya akhirnya dapat mengakses & memperbarui koordinat mereka. Jika saya menggunakan SDO_GEOMETRY / Oracle Locator, maka saya akan menggunakan SDO_UTIL.GETVERTICESfungsinya. Tapi saya tidak menggunakan SDO_GEOMETRY / Oracle Locator, dan tidak ada fungsi yang setara di SDE.ST_GEOMETRY. Satu-satunya SDE.ST_GEOMETRY fungsi yang saya temukan yang berhubungan dengan simpul adalah ST_PointNdan ST_NumPoints.

Saya telah datang dengan kueri yang berhasil melakukan semua ini - dapatkan simpul garis sebagai baris (terinspirasi oleh halaman ini ):

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

Ini CROSS JOINSgaris dalam ROADStabel ke NUMBERStabel (dan membatasi hasil ke jumlah simpul di setiap baris).

Statistik: (diperbarui)

  • Setiap baris memiliki maksimum 30 simpul (rata-rata 4,38 simpul per baris)
  • ROADS memiliki 3.997 jalur
  • NUMBERS memiliki 30 baris (angka berurutan mulai dari 1)
  • Set hasil memiliki 17.536 baris

Namun, kinerjanya buruk (40 detik), dan saya berpikir - apakah ada cara yang lebih elegan untuk melakukan ini? Bagi saya, menggunakan tabel angka dan gabungan silang sepertinya pendekatan yang ceroboh. Apakah ada cara yang lebih baik?

Ketentuan awam akan dihargai; Saya seorang pria Pekerjaan Umum, bukan DBA.


Perbarui # 1:

Jika saya menghapus baris 3 & 4 (serangkaian fungsi terkait X & Y) dari kueri, itu dieksekusi langsung. Tapi tentu saja, saya tidak bisa hanya menghapus garis-garis ini, saya perlu kolom X & Y. Jadi ini membuat saya percaya bahwa kinerja lambat ada hubungannya dengan fungsi X & Y.

Namun, jika saya mengekspor poin ke tabel statis, dan kemudian menjalankan fungsi X & Y di atasnya, ini langsung dieksekusi juga.

Jadi, apakah ini berarti bahwa kinerja yang lambat disebabkan oleh fungsi X & Y, kecuali, yah, bukan? Saya bingung.


Perbarui # 2:

Jika saya mengeluarkan X dan Y dari kueri, memasukkannya ke dalam kueri luar, dan menambahkan ROWNUM ke kueri dalam, maka itu jauh lebih cepat (16 detik - diperbarui):

    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 menjelaskan mengapa ROWNUM membantu kinerja di sini: Mengapa menambahkan ROWNUM ke permintaan meningkatkan kinerja?

Meskipun peningkatan kinerja ini baik, itu belum cukup baik. Dan saya tidak dapat membantu tetapi berpikir bahwa saya masih belum sepenuhnya memahami cara kerja kueri atau mengapa permintaannya lambat.

Pertanyaannya masih ada: apakah ada cara yang lebih baik?

Wilson
sumber
Komentar bukan untuk diskusi panjang; percakapan ini telah dipindahkan ke obrolan .
Paul White 9

Jawaban:

7

Saya tahu sedikit tentang kinerja Oracle dan tidak banyak tentang tipe data khusus, tetapi saya akan mencoba memberi Anda rencana untuk meningkatkan kinerja.

1) Pastikan Anda tidak dapat menjelaskan rencana.

Dimungkinkan untuk menjelaskan rencana walaupun Anda tidak memiliki perangkat lunak basis data yang canggih. Apa yang terjadi jika Anda mengeksekusi set autotrace on explain?

Anda juga dapat mencoba DBMS_XPLAN . Pertama-tama, hemat paket dengan membungkus kueri Anda dengan beberapa kata kunci tambahan:

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

Kemudian jalankan ini:

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

Ada kemungkinan bahwa keduanya tidak akan bekerja dan Anda benar-benar tidak bisa mendapatkan rencana yang jelas. Saya hanya ingin memverifikasi itu karena dengan menjelaskan rencana itu akan jauh lebih mudah bagi masyarakat untuk membantu Anda.

2) Pertimbangkan persyaratan.

Anda mengatakan bahwa 20 detik tidak cukup baik. Sudahkah Anda atau orang lain mendefinisikan apa yang cukup baik? Apakah ada ruang untuk negosiasi? Apakah permintaan Anda harus tepat satu permintaan SELECT? Bisakah Anda mengisi tabel sementara global dalam satu langkah dan memilih hasil yang Anda inginkan di langkah berikutnya? Bisakah Anda membuat prosedur tersimpan yang mengembalikan set hasil dan memanggilnya?

3) Menetapkan batas bawah untuk waktu yang dibutuhkan untuk menyelesaikan kueri.

Saya sarankan menjalankan kueri sederhana yang "menipu" untuk mencari tahu seperti apa query yang dioptimalkan dengan baik. Misalnya, berapa lama kueri ini yang hanya memerlukan simpul pertama berlangsung?

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;

Saya menduga itu akan memberi Anda 4000 baris. Jika Anda melipatgandakan waktu respons kueri tersebut dengan 17,5 / 4, itu bisa memberi Anda batas bawah yang baik untuk total waktu eksekusi.

Jika batas bawah Anda untuk total waktu eksekusi lebih lama dari yang Anda buat pada langkah 2, maka Anda harus berkreasi dengan model data dengan menghitung hasil sebelumnya dan menyimpannya dalam tabel atau Anda perlu menegosiasikan ulang waktu respons yang diperlukan.

4) Tolok Ukur untuk mengetahui fungsi mana yang paling berkontribusi terhadap waktu eksekusi Anda.

Anda berada di jalur yang benar dengan Pembaruan # 1 tetapi Anda harus mencoba mengontrol jumlah pekerjaan yang dilakukan. Misalnya, apakah mungkin untuk menulis grup pertanyaan yang relatif sederhana yang menjalankan setiap fungsi tepat 10.000 kali? Bagaimana perbandingan waktu tanggapan?

5) Pergi bekerja.

Bergantung pada persyaratan yang ditetapkan pada langkah 2 dan apa yang Anda temukan di langkah 4 coba trik apa pun yang dapat Anda pikirkan untuk mengurangi runtime kueri. Apakah Anda dapat melakukan pra-perhitungan hasil dan menyimpannya? Jika masalah terkait dengan berapa kali fungsi dieksekusi maka petunjuk materialized tidak berdokumen dapat membantu. Itu memaksa Oracle untuk membuat tabel temp tersembunyi di balik layar untuk menyimpan hasilnya. Saya tidak tahu apakah itu kompatibel dengan tipe data khusus yang Anda gunakan.

Misalnya, mungkin sesuatu seperti ini berkinerja lebih baik? Maaf jika tidak dikompilasi tapi saya tidak punya cara untuk menguji.

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;

Jika Anda masih terjebak setelah semua ini, saya curiga setidaknya akan memberi Anda informasi tambahan yang dapat Anda edit ke dalam pertanyaan. Semoga berhasil!

Joe Obbish
sumber
2

Saya mencoba menggunakan CONNECT BY (dan DUAL) untuk melihat apakah itu akan lebih cepat, tetapi tidak (ini hampir sama).

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

Saya mendapat ide dari posting ini: Bagaimana cara menghitung rentang di Oracle?

Wilson
sumber
2

Hasil dan respons terhadap jawaban Joe Obbish :

Catatan: Mulai sekarang, saya akan merujuk ke kueri di Pembaruan # 2 sebagai 'kueri'; Saya tidak akan merujuk ke kueri di pertanyaan awal.

1) Pastikan Anda tidak dapat menjelaskan rencana.

Saya tidak bisa mengeksekusi set autotrace on explain. Saya mendapatkan kesalahan ini:ORA-00922: missing or invalid option (#922)

Tapi saya saya bisa mengeksekusi DBMS_XPLAN. Saya berasumsi bahwa saya tidak akan dapat melakukan ini. Untungnya saya salah. Saya sekarang menjalankan menjelaskan rencana.

2) Pertimbangkan persyaratan.

Apakah permintaan Anda harus tepat satu permintaan SELECT?

Saya pikir query tidak perlu menjadi tepat satu query. Perangkat lunak yang saya gunakan sangat terbatas, dan tidak memungkinkan untuk beberapa pernyataan pilih.

Sudahkah Anda mendefinisikan dengan tepat apa persyaratan Anda?

  • Query akan digunakan untuk memperbarui koordinat vertex setelah pengeditan dilakukan ke garis geometri. Ini biasanya akan terjadi pada satu baris pada satu waktu, atau mungkin puluhan baris, tetapi tidak mungkin untuk ribuan baris. Dalam skenario ini, kinerja permintaan saat ini akan memadai.
  • Kueri juga akan digunakan untuk membuat garis geometri baru untuk semua 3.805 baris (ini terkait dengan subjek segmentasi dinamis / referensi linear ). Ini akan terjadi saat dalam pandangan, sehingga kinerja sangat penting. Permintaan kemungkinan akan perlu dijalankan dalam waktu kurang dari 5 detik.

3) Menetapkan batas bawah untuk waktu yang dibutuhkan untuk menyelesaikan kueri.

Query vertex pertama dijalankan dalam 3,75 detik (menghasilkan 3805 baris, seperti yang diharapkan).

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

Hasilnya: batas bawah untuk total waktu eksekusi lebih lama daripada yang saya buat pada langkah 2 (5 detik). Oleh karena itu, saya pikir solusinya adalah '... menjadi kreatif dengan model data saya dengan menghitung hasil sebelumnya dan menyimpannya dalam sebuah tabel' (waktu respons yang diperlukan tidak dapat dinegosiasikan). Dengan kata lain, buatlah pandangan yang terwujud.

Selain itu, batas bawah 16,25 detik cocok dengan total waktu pelaksanaan kueri dalam Pembaruan # 2 (16 detik). Saya pikir ini membuktikan bahwa permintaan saya sepenuhnya dioptimalkan, mengingat fungsi dan data yang harus saya kerjakan.

4) Tolok Ukur untuk mengetahui fungsi mana yang paling berkontribusi terhadap waktu eksekusi Anda.

Saya telah membuat dua tabel (keduanya berisi 10.000 baris): ROADS_BMdan ROADS_STARTPOINT_BM. Saya telah menjalankan kueri sederhana di atas meja menggunakan masing-masing fungsi yang terlibat. Inilah hasilnya:

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

Dokumentasi fungsi: ST_X , ST_Y , ST_NumPoints , ST_PointN

Hasil? ST_PointNadalah masalahnya. 9,5 detik waktu responnya sangat buruk dibandingkan dengan fungsi lainnya. Saya kira ini masuk akal . ST_PointNmengembalikan ST_POINTtipe data geometri, yang harus cukup kompleks dibandingkan fungsi lain yang mengembalikan angka sederhana.

Catatan: ST_PointNrumit. Itu jenis kembali ST_POINT, yang software saya tidak tahu bagaimana menangani dalam satu set hasil: ORA-24359: OCIDefineObject not invoked for a Object type or Reference.

Untuk menyiasatinya, saya memasukkannya ke dalam kueri sebaris untuk mencegah kolom dikembalikan ke set hasil. Tetapi ketika saya melakukan itu, permintaan tidak benar-benar memproses kolom, yang mengalahkan tujuan pengujian. Jadi saya memeriksa apakah itu adalah nol dalam permintaan luar: WHERE ST_POINT IS NOT NULL ORDER BY RDSEC. Dengan melakukan ini, saya memastikan bahwa ST_PointNfungsi tersebut benar-benar digunakan, tanpa mengembalikannya ke set hasil.

Dan tentu saja, saya ingin melakukan tes apples-to-apples, jadi saya melakukan jenis inline query yang sama untuk fungsi-fungsi lain juga (meskipun secara teknis tidak diperlukan).

5) Pergi bekerja.

Berdasarkan langkah 2, 3 & 4, berikut ini adalah temuan saya:

  • Masalahnya adalah ST_PointNfungsinya. Itu lambat. Saya pikir tidak banyak yang bisa saya lakukan tentang ini. Selain mencoba memprogram ulang sepenuhnya / membuat ulang fungsi dengan harapan saya bisa melakukan lebih baik daripada spesialis yang membuatnya. Tidak praktis.
  • Untuk mencapai kinerja yang saya butuhkan, saya harus melakukan pra-komputasi kueri dalam tabel atau tampilan terwujud.
  • Sejauh '..trik yang dapat Anda pikirkan untuk mengurangi runtime kueri' berjalan, saya mungkin bisa menghilangkan beberapa simpul di garis yang lebih panjang. Ini akan memungkinkan saya untuk menghapus beberapa baris dari tabel ANGKA (yang saat ini memiliki 30 baris). Ini akan mempercepat bergabung (meskipun setiap keuntungan dalam kinerja akan minimal). Saya juga harus meninjau semua indeks tabel, meskipun faktanya masalah kinerja saya tidak terkait dengan indeks / bergabung.
  • Berdasarkan pengujian, saya tidak berpikir masalah '... berkaitan dengan berapa kali fungsi dieksekusi'.
  • Kueri CTE yang disediakan di # 5 dikompilasi dengan baik (Saya terkesan bahwa Joe dapat melakukan ini). Anehnya, waktu eksekusi adalah 30 detik, yang bukan perbaikan. Saya kira ST_PointNyang harus disalahkan untuk itu juga. Namun, permintaan CTE tidak sia-sia; Saya belajar banyak hanya dengan menggunakannya.

6. Kesimpulan.

Saya puas bahwa saya telah mengoptimalkan kueri sebanyak mungkin. Saya akan menyiapkan pra-perhitungan, dan beralih ke hal berikutnya. Terima kasih banyak untuk Joe Obbish; Saya telah belajar banyak dari langkah-langkah yang diberikannya.

Wilson
sumber