PostgreSQL
 sql >> Teknologi Basis Data >  >> RDS >> PostgreSQL

Bagaimana memilih menggunakan klausa WITH RECURSIVE

Pertama-tama, mari kita coba untuk menyederhanakan dan memperjelas deskripsi algoritma yang diberikan pada halaman manual. Untuk menyederhanakannya, pertimbangkan hanya union all di with recursive klausa untuk saat ini (dan union nanti):

WITH RECURSIVE pseudo-entity-name(column-names) AS (
    Initial-SELECT
UNION ALL
    Recursive-SELECT using pseudo-entity-name
)
Outer-SELECT using pseudo-entity-name

Untuk memperjelasnya mari kita jelaskan proses eksekusi query dalam kode semu:

working-recordset = result of Initial-SELECT

append working-recordset to empty outer-recordset

while( working-recordset is not empty ) begin

    new working-recordset = result of Recursive-SELECT 
        taking previous working-recordset as pseudo-entity-name

    append working-recordset to outer-recordset

end

overall-result = result of Outer-SELECT 
    taking outer-recordset as pseudo-entity-name

Atau bahkan lebih pendek - Mesin basis data mengeksekusi pemilihan awal, mengambil baris hasil sebagai set kerja. Kemudian berulang kali mengeksekusi pemilihan rekursif pada set kerja, setiap kali mengganti konten set kerja dengan hasil kueri yang diperoleh. Proses ini berakhir ketika set kosong dikembalikan oleh pemilihan rekursif. Dan semua baris hasil yang diberikan pertama oleh pemilihan awal dan kemudian oleh pemilihan rekursif dikumpulkan dan dimasukkan ke pemilihan luar, yang hasilnya menjadi hasil kueri keseluruhan.

Kueri ini menghitung faktorial dari 3:

WITH RECURSIVE factorial(F,n) AS (
    SELECT 1 F, 3 n
UNION ALL
    SELECT F*n F, n-1 n from factorial where n>1
)
SELECT F from factorial where n=1

Pilih awal SELECT 1 F, 3 n memberi kita nilai awal:3 untuk argumen dan 1 untuk nilai fungsi.
Pilih rekursif SELECT F*n F, n-1 n from factorial where n>1 menyatakan bahwa setiap kali kita perlu mengalikan nilai fungsi terakhir dengan nilai argumen terakhir dan mengurangi nilai argumen.
Mesin database mengeksekusinya seperti ini:

Pertama-tama ia mengeksekusi initail select, yang memberikan status awal dari recordset yang berfungsi:

F | n
--+--
1 | 3

Kemudian ia mengubah recordset yang berfungsi dengan kueri rekursif dan mendapatkan status keduanya:

F | n
--+--
3 | 2

Kemudian status ketiga:

F | n
--+--
6 | 1

Pada state ketiga tidak ada baris yang mengikuti n>1 kondisi dalam pemilihan rekursif, set kerja seterusnya adalah loop keluar.

Recordset luar sekarang menampung semua baris, dikembalikan oleh pemilihan awal dan rekursif:

F | n
--+--
1 | 3
3 | 2
6 | 1

Pilihan luar menyaring semua hasil antara dari kumpulan catatan luar, hanya menampilkan nilai faktorial akhir yang menjadi hasil kueri keseluruhan:

F 
--
6

Dan sekarang mari kita perhatikan tabel forest(id,parent_id,name) :

id | parent_id | name
---+-----------+-----------------
1  |           | item 1
2  | 1         | subitem 1.1
3  | 1         | subitem 1.2
4  | 1         | subitem 1.3
5  | 3         | subsubitem 1.2.1
6  |           | item 2
7  | 6         | subitem 2.1
8  |           | item 3

'Memperluas pohon penuh ' di sini berarti menyortir item pohon dalam urutan pertama kedalaman yang dapat dibaca manusia sambil menghitung level dan (mungkin) jalurnya. Kedua tugas (dari penyortiran dan penghitungan level atau jalur yang benar) tidak dapat diselesaikan dalam satu (atau bahkan jumlah konstan) SELECT tanpa menggunakan klausa WITH RECURSIVE (atau klausa Oracle CONNECT BY, yang tidak didukung oleh PostgreSQL). Tetapi kueri rekursif ini berhasil (hampir, lihat catatan di bawah):

WITH RECURSIVE fulltree(id,parent_id,level,name,path) AS (
    SELECT id, parent_id, 1 as level, name, name||'' as path from forest where parent_id is null
UNION ALL
    SELECT t.id, t.parent_id, ft.level+1 as level, t.name, ft.path||' / '||t.name as path
    from forest t, fulltree ft where t.parent_id = ft.id
)
SELECT * from fulltree order by path

Mesin database menjalankannya seperti ini:

Pertama, ia mengeksekusi pemilihan initail, yang memberikan semua item level tertinggi (akar) dari forest tabel:

id | parent_id | level | name             | path
---+-----------+-------+------------------+----------------------------------------
1  |           | 1     | item 1           | item 1
8  |           | 1     | item 3           | item 3
6  |           | 1     | item 2           | item 2

Kemudian, ia mengeksekusi pemilihan rekursif, yang memberikan semua item level 2 dari forest tabel:

id | parent_id | level | name             | path
---+-----------+-------+------------------+----------------------------------------
2  | 1         | 2     | subitem 1.1      | item 1 / subitem 1.1
3  | 1         | 2     | subitem 1.2      | item 1 / subitem 1.2
4  | 1         | 2     | subitem 1.3      | item 1 / subitem 1.3
7  | 6         | 2     | subitem 2.1      | item 2 / subitem 2.1

Kemudian, itu mengeksekusi pemilihan rekursif lagi, mengambil item level 3d:

id | parent_id | level | name             | path
---+-----------+-------+------------------+----------------------------------------
5  | 3         | 3     | subsubitem 1.2.1 | item 1 / subitem 1.2 / subsubitem 1.2.1

Dan sekarang ia mengeksekusi pemilihan rekursif lagi, mencoba mengambil item level 4, tetapi tidak ada satupun, sehingga loop keluar.

SELECT luar menetapkan urutan baris yang benar yang dapat dibaca manusia, mengurutkan pada kolom jalur:

id | parent_id | level | name             | path
---+-----------+-------+------------------+----------------------------------------
1  |           | 1     | item 1           | item 1
2  | 1         | 2     | subitem 1.1      | item 1 / subitem 1.1
3  | 1         | 2     | subitem 1.2      | item 1 / subitem 1.2
5  | 3         | 3     | subsubitem 1.2.1 | item 1 / subitem 1.2 / subsubitem 1.2.1
4  | 1         | 2     | subitem 1.3      | item 1 / subitem 1.3
6  |           | 1     | item 2           | item 2
7  | 6         | 2     | subitem 2.1      | item 2 / subitem 2.1
8  |           | 1     | item 3           | item 3

CATATAN :Urutan baris yang dihasilkan akan tetap benar hanya jika tidak ada karakter tanda baca collation-preceeding / dalam nama barang. Jika kita mengganti nama Item 2 di Item 1 * , itu akan merusak urutan baris, berdiri di antara Item 1 dan turunannya.
Solusi yang lebih stabil adalah menggunakan karakter tab (E'\t' ) sebagai pemisah jalur dalam kueri (yang dapat diganti dengan pemisah jalur yang lebih mudah dibaca nanti:di pilih luar, sebelum ditampilkan ke manusia atau dll). Jalur yang dipisahkan tab akan mempertahankan urutan yang benar sampai ada tab atau karakter kontrol dalam nama item - yang dapat dengan mudah diperiksa dan dikesampingkan tanpa kehilangan kegunaan.

Sangat mudah untuk mengubah kueri terakhir untuk memperluas subpohon sembarang - Anda hanya perlu mengganti kondisi parent_id is null dengan perent_id=1 (Misalnya). Perhatikan bahwa varian kueri ini akan mengembalikan semua level dan jalur relatif terhadap Item 1 .

Dan sekarang tentang kesalahan umum . Kesalahan khas yang paling menonjol khusus untuk kueri rekursif adalah mendefinisikan kondisi berhenti sakit dalam pemilihan rekursif, yang menghasilkan perulangan tak terbatas.

Misalnya, jika kita menghilangkan where n>1 kondisi dalam contoh faktorial di atas, eksekusi pemilihan rekursif tidak akan pernah memberikan himpunan kosong (karena kita tidak memiliki kondisi untuk memfilter satu baris) dan perulangan akan terus berlanjut tanpa batas.

Itulah alasan yang paling mungkin mengapa beberapa pertanyaan Anda hang (alasan lain yang tidak spesifik tetapi masih memungkinkan adalah pemilihan yang sangat tidak efektif, yang dijalankan dalam waktu yang terbatas tetapi sangat lama).

Tidak banyak panduan kueri khusus RECURSIVE untuk menyebutkan, sejauh yang saya tahu. Tapi saya ingin menyarankan (agak jelas) langkah demi langkah prosedur pembuatan kueri rekursif.

  • Buat dan debug pilihan awal Anda secara terpisah.

  • Bungkus dengan scaffolding DENGAN konstruksi RECURSIVE
    dan mulailah membangun dan men-debug pilihan rekursif Anda.

Konstruksi scaffolding yang direkomendasikan adalah seperti ini:

WITH RECURSIVE rec( <Your column names> ) AS (
    <Your ready and working initial SELECT>
UNION ALL
    <Recursive SELECT that you are debugging now>
)
SELECT * from rec limit 1000

Pilihan luar yang paling sederhana ini akan menampilkan seluruh recordset luar, yang, seperti yang kita ketahui, berisi semua baris keluaran dari pemilihan awal dan setiap eksekusi pemilihan rekursif dalam satu lingkaran dalam urutan keluaran aslinya - seperti pada contoh di atas! limit 1000 bagian akan mencegah penggantungan, menggantinya dengan keluaran besar di mana Anda akan dapat melihat titik berhenti yang terlewat.

  • Setelah men-debug awal dan rekursif pilih build dan debug pilihan luar Anda.

Dan sekarang hal terakhir yang disebutkan - perbedaan dalam menggunakan union alih-alih union all di with recursive ayat. Ini memperkenalkan batasan keunikan baris yang menghasilkan dua baris tambahan dalam kodesemu eksekusi kami:

working-recordset = result of Initial-SELECT

discard duplicate rows from working-recordset /*union-specific*/

append working-recordset to empty outer-recordset

while( working-recordset is not empty ) begin

    new working-recordset = result of Recursive-SELECT 
        taking previous working-recordset as pseudo-entity-name

    discard duplicate rows and rows that have duplicates in outer-recordset 
        from working-recordset /*union-specific*/

    append working-recordset to outer-recordset

end

overall-result = result of Outer-SELECT 
    taking outer-recordset as pseudo-entity-name



  1. Database
  2.   
  3. Mysql
  4.   
  5. Oracle
  6.   
  7. Sqlserver
  8.   
  9. PostgreSQL
  10.   
  11. Access
  12.   
  13. SQLite
  14.   
  15. MariaDB
  1. INSERT a SELECT GROUP BY :lebih banyak kolom target daripada kesalahan ekspresi

  2. Menghilangkan tanda kutip ganda untuk melakukan kueri di PostgreSQL

  3. Apakah mungkin menggunakan variabel dan tidak menentukan tipe pengembalian di postgreSQL?

  4. Menuju cloud di CHAR(10)

  5. Bagaimana Atand() Bekerja di PostgreSQL