Struktur organisasi Toko Maju Jaya kini bertingkat: Yusuf melapor ke Dewi, Dewi melapor ke Andi, Andi melapor ke Siti, dan Siti melapor ke Budi. Pertanyaan sederhana seperti “siapa saja seluruh bawahan Siti, berapa tingkat kepemimpinan di bawahnya?” tiba-tiba sulit dijawab query biasa. Chapter Self-Join bisa merangkai dua-tiga lapis join manual, tetapi jumlah lapisan harus diketahui lebih dulu — dan hierarki dunia nyata berubah tanpa memberi tahu query kita. SQL menutup celah ini dengan bentuk paling tidak terduga: sebuah CTE yang boleh membaca dirinya sendiri. WITH RECURSIVE membuat query yang hasilnya menjadi masukan bagi iterasi berikutnya — satu definisi, kedalaman berapa pun. Chapter ini membangunnya dari deret bilangan, memakainya untuk pohon karyawan, lalu menutup dengan pengaman agar rekursi tidak berubah menjadi putaran tanpa ujung.

Tabel Latihan: karyawan dan atasan_id #

Data latihan memakai satu tabel karyawan dengan kolom atasan_id yang menunjuk ke baris lain di tabel yang sama — pola self-referencing foreign key yang sudah diperkenalkan di chapter Self-Join . Nilai NULL pada atasan_id menandai puncak hierarki. Pohon yang terbentuk sengaja dibuat asimetris: cabang Andi turun lima tingkat sampai Yusuf, cabang Rina berhenti di tingkat ketiga:

sql buat-tabel.sql
-- Tabel latihan chapter Recursive CTE: struktur karyawan Toko Maju Jaya.
-- atasan_id menunjuk ke baris karyawan lain (self-referencing foreign key);
-- NULL berarti tidak punya atasan -- puncak hierarki.
CREATE TABLE karyawan (
    id        integer PRIMARY KEY,
    nama      text NOT NULL,
    jabatan   text NOT NULL,
    atasan_id integer REFERENCES karyawan (id)
);

-- Pohon yang dibentuk data ini:
--   Budi (Direktur)
--   +- Siti (Manajer)
--      +- Andi (Supervisor)
--      |  +- Dewi (Staf)
--      |  |  +- Yusuf (Magang)
--      |  +- Ratna (Staf)
--      +- Rina (Staf)
INSERT INTO karyawan (id, nama, jabatan, atasan_id) VALUES
    (1, 'Budi Santoso', 'Direktur',   NULL),
    (2, 'Siti Rahayu',  'Manajer',    1),
    (3, 'Andi Wijaya',  'Supervisor', 2),
    (4, 'Dewi Lestari', 'Staf',       3),
    (5, 'Ratna Kusuma', 'Staf',       3),
    (6, 'Yusuf Hakim',  'Magang',     4),
    (7, 'Rina Marlina', 'Staf',       2);

SELECT id, nama, jabatan, atasan_id FROM karyawan ORDER BY id;

Output yang Dihasilkan:

text
CREATE TABLE
INSERT 0 7
 id |     nama     |  jabatan   | atasan_id 
----+--------------+------------+-----------
  1 | Budi Santoso | Direktur   |          
  2 | Siti Rahayu  | Manajer    |         1
  3 | Andi Wijaya  | Supervisor |         2
  4 | Dewi Lestari | Staf       |         3
  5 | Ratna Kusuma | Staf       |         3
  6 | Yusuf Hakim  | Magang     |         4
  7 | Rina Marlina | Staf       |         2
(7 rows)

Struktur Dasar: Anchor Member dan Recursive Member #

Sebelum menyentuh pohon karyawan, mari lihat anatomi rekursi pada contoh paling murni: deret bilangan 1 sampai 10. WITH RECURSIVE menuntut dua bagian di dalam kurung yang dipisah UNION ALL — bagian awal (anchor member) memberikan baris pemula, bagian kedua (recursive member) memproduksi baris berikutnya dengan membaca hasil sejauh ini:

-- Recursive CTE paling sederhana: deret bilangan 1 sampai 10.
-- Struktur wajibnya: anchor member + UNION ALL + recursive member.

WITH RECURSIVE deret (n) AS (
    SELECT 1                          -- anchor member: titik awal
    UNION ALL
    SELECT n + 1                      -- recursive member: langkah berikutnya
    FROM deret
    WHERE n < 10                      -- syarat berhenti
)
SELECT n FROM deret ORDER BY n;

-- Untuk deret angka yang sesederhana ini, generate_series lebih ringkas
-- dan tidak perlu struktur WITH RECURSIVE sama sekali.
SELECT n FROM generate_series(1, 10) AS n ORDER BY n;

-- Rentang tanggal pun pekerjaan generate_series:
-- tujuh hari berturut-turut tanpa satu pun baris ditulis manual.
SELECT d::date AS tanggal
FROM generate_series(DATE '2026-01-05', DATE '2026-01-11', interval '1 day') AS d
ORDER BY d;

Output yang Dihasilkan:

text
 n  
----
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
(10 rows)

 n  
----
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
(10 rows)

  tanggal   
------------
 2026-01-05
 2026-01-06
 2026-01-07
 2026-01-08
 2026-01-09
 2026-01-10
 2026-01-11
(7 rows)

Ketiga query menghasilkan deret yang bisa diprediksi penuh — 1 sampai 10, lalu tujuh tanggal dari 5 sampai 11 Januari 2026. Mari kita bedah bagian pentingnya:

  1. WITH RECURSIVE deret (n) AS (...) — kata RECURSIVE melonggarkan satu larangan: tanpa kata itu, CTE tidak boleh merujuk namanya sendiri; dengan kata itu, rujukan FROM deret di dalam definisi deret menjadi legal.
  2. SELECT 1 (anchor member) — dievaluasi tepat sekali di awal; hasilnya (satu baris berisi 1) adalah bibit seluruh perhitungan.
  3. SELECT n + 1 FROM deret WHERE n < 10 (recursive member) — membaca hasil sementara deret, menaikkan n, dan menyumbangkan barisnya kembali. WHERE n < 10 adalah syarat berhenti: begitu n mencapai 10, bagian ini tidak menghasilkan baris lagi dan perulangan berakhir.
  4. UNION ALL — operator yang menyatukan hasil anchor dan seluruh hasil iterasi. UNION ALL mempertahankan baris ganda; peran UNION biasa akan kita bahas di bagian pengaman.

Dua query terakhir mengajarkan kejujuran pemilihan alat: generate_series menghasilkan deret 1–10 dan rentang tanggal dengan satu pemanggilan, tanpa struktur rekursif apa pun. Gunakan recursive CTE ketika baris berikutnya benar-benar bergantung pada baris sebelumnya — seperti mencari anak dari orang yang baru saja ditemukan; untuk rentang beraturan (angka, tanggal), generate_series lebih pendek dan lebih murah.

Cara Kerja: Working Table yang Menyusut #

Kata “rekursif” memanggil bayangan query yang memanggil dirinya. PostgreSQL melakukan hal yang setara dengan cara iteratif, dan model mentalnya penting untuk dipahami sebelum kita menulis pohon karyawan. Server menjaga satu tabel kerja (working table): anchor member mengisinya pertama kali, recursive membacanya, baris hasilnya masuk ke hasil akhir sekaligus menjadi isi working table yang baru, dan putaran berhenti saat working table kosong:

Diagram di atas memuat seluruh mesinnya; sisa hanya memilih anchor dan recursive yang tepat. Untuk struktur organisasi, keduanya punya bentuk baku: anchor mengambil akar pohon (atasan_id IS NULL), recursive mencari anak dari baris yang sudah terkumpul:

-- Seluruh struktur organisasi dari puncak ke bawah,
-- lengkap dengan kolom level (kedalaman dari Direktur).

WITH RECURSIVE hierarki AS (
    -- Anchor member: baris tanpa atasan -- akar pohon.
    SELECT id, nama, jabatan, atasan_id, 1 AS level
    FROM karyawan
    WHERE atasan_id IS NULL

    UNION ALL

    -- Recursive member: bawahan langsung dari baris yang sudah terkumpul.
    SELECT k.id, k.nama, k.jabatan, k.atasan_id, h.level + 1
    FROM karyawan k
    INNER JOIN hierarki h ON k.atasan_id = h.id
)
SELECT level, id, nama, jabatan, atasan_id
FROM hierarki
ORDER BY level, id;

Output yang Dihasilkan:

text
 level | id |     nama     |  jabatan   | atasan_id 
-------+----+--------------+------------+-----------
     1 |  1 | Budi Santoso | Direktur   |          
     2 |  2 | Siti Rahayu  | Manajer    |         1
     3 |  3 | Andi Wijaya  | Supervisor |         2
     3 |  7 | Rina Marlina | Staf       |         2
     4 |  4 | Dewi Lestari | Staf       |         3
     4 |  5 | Ratna Kusuma | Staf       |         3
     5 |  6 | Yusuf Hakim  | Magang     |         4
(7 rows)

Mari kita ikuti jalannya working table baris demi baris:

  1. Iterasi 0 (anchor) — WHERE atasan_id IS NULL menangkap satu baris: Budi Santoso, level 1. Working table berisi Budi.
  2. Iterasi 1 — recursive member mencari karyawan dengan atasan_id = 1: Siti Rahayu, level 2. Working table kini berisi Siti.
  3. Iterasi 2 — anak-anak Siti ditemukan: Andi Wijaya dan Rina Marlina, keduanya level 3. Working table berisi keduanya.
  4. Iterasi 3 dan 4 — anak Andi (Dewi Lestari, Ratna Kusuma, level 4) menyusul, lalu anak Dewi (Yusuf Hakim, level 5). Rina tidak punya anak, jadi cabangnya berhenti diam-diam.
  5. Iterasi 5 — tidak ada lagi karyawan yang belum terkumpul; recursive member menghasilkan nol baris, working table kosong, perulangan berakhir.

Hasil akhirnya tujuh baris — seluruh perusahaan — dengan level yang bertambah satu setiap turun satu lapis. Perhatikan pula aturan penulisan yang dipegang query ini: kolom hasil anchor dan recursive harus sejajar jumlah dan urutannya (kolom level dihitung sebagai 1 di anchor dan h.level + 1 di recursive), dan ORDER BY level, id memastikan output terurut penuh secara deterministik.

Urutan Pohon dengan Kolom path #

ORDER BY level menampilkan lapisan demi lapisan — rapi untuk laporan jumlah per tingkat, tetapi sulit melihat siapa anak siapa. Untuk itu kita simpan jalur lengkap dari akar ke tiap baris dalam satu kolom path, lalu urutkan berdasarkan kolom itu:

-- Kolom path menyimpan rantai nama dari akar sampai baris tersebut.
-- ORDER BY path menghasilkan urutan pohon (depth-first):
-- induk selalu tepat sebelum seluruh anak-anaknya.

WITH RECURSIVE pohon AS (
    SELECT id, nama, jabatan, 1 AS level, nama AS path
    FROM karyawan
    WHERE atasan_id IS NULL

    UNION ALL

    SELECT k.id, k.nama, k.jabatan, p.level + 1, p.path || ' > ' || k.nama
    FROM karyawan k
    INNER JOIN pohon p ON k.atasan_id = p.id
)
SELECT repeat('    ', level - 1) || nama AS struktur,
       level,
       path
FROM pohon
ORDER BY path;

Output yang Dihasilkan:

text
          struktur           | level |                                 path                                  
-----------------------------+-------+-----------------------------------------------------------------------
 Budi Santoso                |     1 | Budi Santoso
     Siti Rahayu             |     2 | Budi Santoso > Siti Rahayu
         Andi Wijaya         |     3 | Budi Santoso > Siti Rahayu > Andi Wijaya
             Dewi Lestari    |     4 | Budi Santoso > Siti Rahayu > Andi Wijaya > Dewi Lestari
                 Yusuf Hakim |     5 | Budi Santoso > Siti Rahayu > Andi Wijaya > Dewi Lestari > Yusuf Hakim
             Ratna Kusuma    |     4 | Budi Santoso > Siti Rahayu > Andi Wijaya > Ratna Kusuma
         Rina Marlina        |     3 | Budi Santoso > Siti Rahayu > Rina Marlina
(7 rows)

Kunci ada di satu ekspresi: p.path || ' > ' || k.nama — anak mewarisi path ayahnya lalu menempelkan namanya sendiri, sehingga path Yusuf Hakim menjadi Budi Santoso > Siti Rahayu > Andi Wijaya > Dewi Lestari > Yusuf Hakim: bukti tertulis bahwa rantai lima tingkat itu benar-benar dilalui baris demi baris. Karena tiap path diawali path induknya, ORDER BY path mengelompokkan seluruh sub-pohon tepat setelah induknya — urutan depth-first. Kolom struktur memanfaatkannya untuk visual: repeat(' ', level - 1) || nama menambah indentasi empat spasi per tingkat, dan bagan organisasi yang terbaca seperti pohon muncul di kolom pertama. Pola path semacam ini juga bahan utama kueri bill of materials — melacak komponen suku cadang bersarang sampai kedalaman berapa pun.

Menelusuri ke Dua Arah #

Akar tidak harus Direktur, dan arah tidak harus ke bawah. Kedua kebebasan itu dikendalikan dari dua titik: anchor menentukan titik awal, sedangkan kondisi join di recursive member menentukan arah — join ke anak berarti turun, join ke atasan berarti naik:

-- Arah traversal ditentukan oleh bagaimana recursive member join:
-- turun (anak dari baris terkumpul) atau naik (atasan dari baris terkumpul).

-- TURUN: seluruh tim di bawah Siti Rahayu -- sub-pohon berakar di id 2.
WITH RECURSIVE tim AS (
    SELECT id, nama, jabatan, 1 AS level
    FROM karyawan
    WHERE id = 2

    UNION ALL

    SELECT k.id, k.nama, k.jabatan, t.level + 1
    FROM karyawan k
    INNER JOIN tim t ON k.atasan_id = t.id
)
SELECT level, id, nama, jabatan FROM tim ORDER BY level, id;

-- NAIK: rantai atasan Yusuf Hakim, dari Yusuf sampai Direktur.
-- Join-nya dibalik: baris terkumpul mencari karyawan yang menjadi atasannya.
WITH RECURSIVE atasan AS (
    SELECT id, nama, jabatan, atasan_id, 1 AS level
    FROM karyawan
    WHERE id = 6

    UNION ALL

    SELECT k.id, k.nama, k.jabatan, k.atasan_id, a.level + 1
    FROM karyawan k
    INNER JOIN atasan a ON k.id = a.atasan_id
)
SELECT level, id, nama, jabatan FROM atasan ORDER BY level;

Output yang Dihasilkan:

text
 level | id |     nama     |  jabatan   
-------+----+--------------+------------
     1 |  2 | Siti Rahayu  | Manajer
     2 |  3 | Andi Wijaya  | Supervisor
     2 |  7 | Rina Marlina | Staf
     3 |  4 | Dewi Lestari | Staf
     3 |  5 | Ratna Kusuma | Staf
     4 |  6 | Yusuf Hakim  | Magang
(6 rows)

 level | id |     nama     |  jabatan   
-------+----+--------------+------------
     1 |  6 | Yusuf Hakim  | Magang
     2 |  4 | Dewi Lestari | Staf
     3 |  3 | Andi Wijaya  | Supervisor
     4 |  2 | Siti Rahayu  | Manajer
     5 |  1 | Budi Santoso | Direktur
(5 rows)

Query pertama mengganti anchor menjadi WHERE id = 2: Siti Rahayu jadi akar, dan enam baris timnya terkumpul — Andi, Rina, lalu Dewi, Ratna, sampai Yusuf di level 4. Budi tidak pernah muncul karena traversal hanya menjauh dari akar. Query kedua membalik join-nya: k.id = a.atasan_id berarti “cari baris karyawan yang menjadi atasan baris terkumpul”, sehingga rantai memanjat — Yusuf → Dewi → Andi → Siti → Budi — dan berhenti di Budi karena atasan_id NULL tidak pernah cocok dengan k.id mana pun. Perhatikan baik-baik bahwa hanya satu baris lahir per iterasi saat menanjak: pohon ke atas selalu berupa rantai tunggal, karena satu karyawan hanya punya satu atasan.

Pengaman: Batas Kedalaman dan UNION #

Rekursi berhenti ketika recursive member berhenti menghasilkan baris. Data pohon kita berhenti dengan sendirinya — finite dan tanpa siklus — tetapi data nyata tidak selalu sebaik itu: atasan_id yang saling menunjuk (A melapor ke B, B melapor ke A) membuat working table tidak pernah kosong dan query berputar selamanya. Dua pengaman pertama yang perlu Anda hafal:

-- Dua pengaman untuk rekursi yang tidak kunjung berhenti.

-- Pengaman kedalaman: WHERE di recursive member membatasi sampai level 3.
-- Baris level 3 tetap tampil, tetapi tidak lagi dikembangkan.
WITH RECURSIVE tim AS (
    SELECT id, nama, jabatan, 1 AS level
    FROM karyawan
    WHERE atasan_id IS NULL

    UNION ALL

    SELECT k.id, k.nama, k.jabatan, t.level + 1
    FROM karyawan k
    INNER JOIN tim t ON k.atasan_id = t.id
    WHERE t.level < 3
)
SELECT level, id, nama, jabatan FROM tim ORDER BY level, id;

-- UNION (tanpa ALL) membuang duplikat: baris yang sudah pernah muncul
-- tidak masuk lagi ke working table, sehingga perulangan berhenti lebih cepat.
-- Anchor sengaja memuat dua nilai (1 dan 2) supaya duplikatnya terlihat.

-- UNION ALL: hasil 7 baris -- angka 2, 3, 4 muncul dua kali.
WITH RECURSIVE deret (n) AS (
    (SELECT 1 UNION ALL SELECT 2)
    UNION ALL
    SELECT n + 1 FROM deret WHERE n < 4
)
SELECT n FROM deret ORDER BY n;

-- UNION: hasil 4 baris -- duplikat dibuang, tiap angka tampil sekali.
WITH RECURSIVE deret (n) AS (
    (SELECT 1 UNION ALL SELECT 2)
    UNION
    SELECT n + 1 FROM deret WHERE n < 4
)
SELECT n FROM deret ORDER BY n;

Output yang Dihasilkan:

text
 level | id |     nama     |  jabatan   
-------+----+--------------+------------
     1 |  1 | Budi Santoso | Direktur
     2 |  2 | Siti Rahayu  | Manajer
     3 |  3 | Andi Wijaya  | Supervisor
     3 |  7 | Rina Marlina | Staf
(4 rows)

 n 
---
 1
 2
 2
 3
 3
 4
 4
(7 rows)

 n 
---
 1
 2
 3
 4
(4 rows)

Pengaman pertama adalah WHERE t.level < 3 di recursive member: baris level 3 (Andi, Rina) tetap tampil di hasil, tetapi tidak lagi dicarikan anak, sehingga traversal berhenti di kedalaman tiga — empat baris, bukan tujuh. Teknik ini bekerja pada data apa pun, termasuk yang bersiklus, dan pas untuk kebutuhan seperti “tiga tingkat ke bawah sang manajer”. Pengaman kedua adalah mengganti UNION ALL dengan UNION: baris duplikat dibuang sebelum masuk working table, sehingga baris yang sudah pernah dihasilkan tidak pernah diproses dua kali. Query deret menunjukkan efeknya — anchor dua nilai (1 dan 2) membuat jalur 1→2→3→4 dan 2→3→4 bertabrakan: UNION ALL melaporkan 7 baris dengan angka 2, 3, 4 kembar, UNION memangkasnya menjadi 4 baris bersih dan menghentikan iterasi begitu tidak ada baris baru. Harganya nyata: setiap iterasi harus menyaring seluruh hasil sebelumnya untuk mencari kebaruan — biaya yang tidak Anda bayar pada UNION ALL.

Recursive CTE menuntaskan perangkat section ini: subquery klasik di tiga posisi, EXISTS untuk keberadaan, CTE bernama untuk keterbacaan, dan rekursi untuk hierarki. Satu pertanyaan penutup menunggu jawaban definitif: dari semua alat itu, kapan sebenarnya subquery unggul dibanding join — dan kapan keduanya cuma dua ejaan untuk satu rencana eksekusi? Chapter terakhir section ini mengadu keduanya: Subquery vs Join .

Referensi #

Berkas Sumber

Berkas pendukung halaman ini dapat diunduh dan dijalankan langsung sesuai penjelasan di atas.

arah-traversal.sql

Unduh berkas
sql
-- Arah traversal ditentukan oleh bagaimana recursive member join:
-- turun (anak dari baris terkumpul) atau naik (atasan dari baris terkumpul).

-- TURUN: seluruh tim di bawah Siti Rahayu -- sub-pohon berakar di id 2.
WITH RECURSIVE tim AS (
    SELECT id, nama, jabatan, 1 AS level
    FROM karyawan
    WHERE id = 2

    UNION ALL

    SELECT k.id, k.nama, k.jabatan, t.level + 1
    FROM karyawan k
    INNER JOIN tim t ON k.atasan_id = t.id
)
SELECT level, id, nama, jabatan FROM tim ORDER BY level, id;

-- NAIK: rantai atasan Yusuf Hakim, dari Yusuf sampai Direktur.
-- Join-nya dibalik: baris terkumpul mencari karyawan yang menjadi atasannya.
WITH RECURSIVE atasan AS (
    SELECT id, nama, jabatan, atasan_id, 1 AS level
    FROM karyawan
    WHERE id = 6

    UNION ALL

    SELECT k.id, k.nama, k.jabatan, k.atasan_id, a.level + 1
    FROM karyawan k
    INNER JOIN atasan a ON k.id = a.atasan_id
)
SELECT level, id, nama, jabatan FROM atasan ORDER BY level;

buat-tabel.sql

Unduh berkas
sql
-- Tabel latihan chapter Recursive CTE: struktur karyawan Toko Maju Jaya.
-- atasan_id menunjuk ke baris karyawan lain (self-referencing foreign key);
-- NULL berarti tidak punya atasan -- puncak hierarki.
CREATE TABLE karyawan (
    id        integer PRIMARY KEY,
    nama      text NOT NULL,
    jabatan   text NOT NULL,
    atasan_id integer REFERENCES karyawan (id)
);

-- Pohon yang dibentuk data ini:
--   Budi (Direktur)
--   +- Siti (Manajer)
--      +- Andi (Supervisor)
--      |  +- Dewi (Staf)
--      |  |  +- Yusuf (Magang)
--      |  +- Ratna (Staf)
--      +- Rina (Staf)
INSERT INTO karyawan (id, nama, jabatan, atasan_id) VALUES
    (1, 'Budi Santoso', 'Direktur',   NULL),
    (2, 'Siti Rahayu',  'Manajer',    1),
    (3, 'Andi Wijaya',  'Supervisor', 2),
    (4, 'Dewi Lestari', 'Staf',       3),
    (5, 'Ratna Kusuma', 'Staf',       3),
    (6, 'Yusuf Hakim',  'Magang',     4),
    (7, 'Rina Marlina', 'Staf',       2);

SELECT id, nama, jabatan, atasan_id FROM karyawan ORDER BY id;

deret-bilangan.sql

Unduh berkas
sql
-- Recursive CTE paling sederhana: deret bilangan 1 sampai 10.
-- Struktur wajibnya: anchor member + UNION ALL + recursive member.

WITH RECURSIVE deret (n) AS (
    SELECT 1                          -- anchor member: titik awal
    UNION ALL
    SELECT n + 1                      -- recursive member: langkah berikutnya
    FROM deret
    WHERE n < 10                      -- syarat berhenti
)
SELECT n FROM deret ORDER BY n;

-- Untuk deret angka yang sesederhana ini, generate_series lebih ringkas
-- dan tidak perlu struktur WITH RECURSIVE sama sekali.
SELECT n FROM generate_series(1, 10) AS n ORDER BY n;

-- Rentang tanggal pun pekerjaan generate_series:
-- tujuh hari berturut-turut tanpa satu pun baris ditulis manual.
SELECT d::date AS tanggal
FROM generate_series(DATE '2026-01-05', DATE '2026-01-11', interval '1 day') AS d
ORDER BY d;

hierarki-level.sql

Unduh berkas
sql
-- Seluruh struktur organisasi dari puncak ke bawah,
-- lengkap dengan kolom level (kedalaman dari Direktur).

WITH RECURSIVE hierarki AS (
    -- Anchor member: baris tanpa atasan -- akar pohon.
    SELECT id, nama, jabatan, atasan_id, 1 AS level
    FROM karyawan
    WHERE atasan_id IS NULL

    UNION ALL

    -- Recursive member: bawahan langsung dari baris yang sudah terkumpul.
    SELECT k.id, k.nama, k.jabatan, k.atasan_id, h.level + 1
    FROM karyawan k
    INNER JOIN hierarki h ON k.atasan_id = h.id
)
SELECT level, id, nama, jabatan, atasan_id
FROM hierarki
ORDER BY level, id;

hierarki-path.sql

Unduh berkas
sql
-- Kolom path menyimpan rantai nama dari akar sampai baris tersebut.
-- ORDER BY path menghasilkan urutan pohon (depth-first):
-- induk selalu tepat sebelum seluruh anak-anaknya.

WITH RECURSIVE pohon AS (
    SELECT id, nama, jabatan, 1 AS level, nama AS path
    FROM karyawan
    WHERE atasan_id IS NULL

    UNION ALL

    SELECT k.id, k.nama, k.jabatan, p.level + 1, p.path || ' > ' || k.nama
    FROM karyawan k
    INNER JOIN pohon p ON k.atasan_id = p.id
)
SELECT repeat('    ', level - 1) || nama AS struktur,
       level,
       path
FROM pohon
ORDER BY path;

pengaman-dedup.sql

Unduh berkas
sql
-- Dua pengaman untuk rekursi yang tidak kunjung berhenti.

-- Pengaman kedalaman: WHERE di recursive member membatasi sampai level 3.
-- Baris level 3 tetap tampil, tetapi tidak lagi dikembangkan.
WITH RECURSIVE tim AS (
    SELECT id, nama, jabatan, 1 AS level
    FROM karyawan
    WHERE atasan_id IS NULL

    UNION ALL

    SELECT k.id, k.nama, k.jabatan, t.level + 1
    FROM karyawan k
    INNER JOIN tim t ON k.atasan_id = t.id
    WHERE t.level < 3
)
SELECT level, id, nama, jabatan FROM tim ORDER BY level, id;

-- UNION (tanpa ALL) membuang duplikat: baris yang sudah pernah muncul
-- tidak masuk lagi ke working table, sehingga perulangan berhenti lebih cepat.
-- Anchor sengaja memuat dua nilai (1 dan 2) supaya duplikatnya terlihat.

-- UNION ALL: hasil 7 baris -- angka 2, 3, 4 muncul dua kali.
WITH RECURSIVE deret (n) AS (
    (SELECT 1 UNION ALL SELECT 2)
    UNION ALL
    SELECT n + 1 FROM deret WHERE n < 4
)
SELECT n FROM deret ORDER BY n;

-- UNION: hasil 4 baris -- duplikat dibuang, tiap angka tampil sekali.
WITH RECURSIVE deret (n) AS (
    (SELECT 1 UNION ALL SELECT 2)
    UNION
    SELECT n + 1 FROM deret WHERE n < 4
)
SELECT n FROM deret ORDER BY n;