Memecah tabel menjadi partisi hanya membantu jika query tidak membaca semuanya. Kabar baiknya: PostgreSQL melakukan pemangkasan ini secara otomatis. Partition pruning adalah kemampuan planner membuang partisi yang tidak mungkin memuat baris jawaban, berdasarkan perbandingan predikat query dengan batas partisi. Pada tabel log_penjualan tiga bulan Toko Maju Jaya, query laporan bulanan seharusnya hanya menyentuh satu dari tiga partisi — dan rencana eksekusinya bisa kita buktikan memang begitu. Chapter ini membahas pruning statis (predikat bernilai tetap), pruning saat eksekusi untuk prepared statement, interaksinya dengan partisi DEFAULT, pengaturan enable_partition_pruning, serta teknik lanjutan partition-wise join.

Menyiapkan Tabel Tiga Partisi #

Demo memakai tabel penjualan kuartal pertama 2026 dengan penyebaran data yang rata — 100 baris per tanggal — supaya biaya tiap partisi sebanding dan rencana eksekusi mudah dibandingkan:

sql buat-tabel.sql
-- Tabel penjualan satu kuartal dengan tiga partisi bulanan.
CREATE TABLE log_penjualan (
    id      bigint NOT NULL,
    tanggal date NOT NULL,
    kota    text NOT NULL,
    total   numeric(12,2) NOT NULL
) PARTITION BY RANGE (tanggal);

CREATE TABLE log_penjualan_2026_01 PARTITION OF log_penjualan
    FOR VALUES FROM ('2026-01-01') TO ('2026-02-01');
CREATE TABLE log_penjualan_2026_02 PARTITION OF log_penjualan
    FOR VALUES FROM ('2026-02-01') TO ('2026-03-01');
CREATE TABLE log_penjualan_2026_03 PARTITION OF log_penjualan
    FOR VALUES FROM ('2026-03-01') TO ('2026-04-01');

-- 9000 baris merata: 100 baris per tanggal, 1 Jan - 31 Mar 2026.
INSERT INTO log_penjualan
SELECT g,
       DATE '2026-01-01' + ((g - 1) % 90),
       (ARRAY['Bandung', 'Jakarta', 'Medan', 'Surabaya'])[1 + (g % 4)],
       ((g * 37) % 1000 + 1) * 1000.00
FROM generate_series(1, 9000) AS g;

SELECT tableoid::regclass AS partisi, count(*) AS baris
FROM log_penjualan
GROUP BY 1
ORDER BY 1;

-- Statistik diisi sekarang agar rencana eksekusi stabil dan dapat dibandingkan.
ANALYZE log_penjualan;

Output yang Dihasilkan:

text
CREATE TABLE
CREATE TABLE
CREATE TABLE
CREATE TABLE
INSERT 0 9000
        partisi        | baris 
-----------------------+-------
 log_penjualan_2026_01 |  3100
 log_penjualan_2026_02 |  2800
 log_penjualan_2026_03 |  3100
(3 rows)

ANALYZE

Tiga partisi bulanan menampung 3.100, 2.800, dan 3.100 baris sesuai jumlah hari kalender. ANALYZE di akhir file wajib: statistik per partisi menentukan estimasi baris dan biaya pada rencana eksekusi, sehingga output EXPLAIN pada contoh-contoh berikutnya konsisten dan dapat dibandingkan antar query.

Pruning Statis dengan Predikat Tetap #

Pruning statis terjadi saat planning: predikat yang bernilai tetap (literal seperti DATE '2026-02-15') dibandingkan langsung dengan batas tiap partisi, dan partisi yang tidak berpotongan dibuang dari rencana sebelum eksekusi dimulai. Tiga query berikut memperlihatkan spektrumnya:

-- Tanpa filter: semua partisi harus dibaca.
EXPLAIN SELECT count(*) FROM log_penjualan;

-- Filter kesamaan pada partition key: dua partisi hilang dari rencana.
EXPLAIN SELECT count(*) FROM log_penjualan
WHERE tanggal = DATE '2026-02-15';

-- Filter rentang yang melintasi Feb-Mar: partisi Januari hilang.
EXPLAIN SELECT count(*)
FROM log_penjualan
WHERE tanggal BETWEEN DATE '2026-02-20' AND DATE '2026-03-05';

Output yang Dihasilkan:

text
                                             QUERY PLAN                                              
-----------------------------------------------------------------------------------------------------
 Aggregate  (cost=224.50..224.51 rows=1 width=8)
   ->  Append  (cost=0.00..202.00 rows=9000 width=0)
         ->  Seq Scan on log_penjualan_2026_01 log_penjualan_1  (cost=0.00..54.00 rows=3100 width=0)
         ->  Seq Scan on log_penjualan_2026_02 log_penjualan_2  (cost=0.00..49.00 rows=2800 width=0)
         ->  Seq Scan on log_penjualan_2026_03 log_penjualan_3  (cost=0.00..54.00 rows=3100 width=0)
(5 rows)

                                         QUERY PLAN                                         
--------------------------------------------------------------------------------------------
 Aggregate  (cost=56.25..56.26 rows=1 width=8)
   ->  Seq Scan on log_penjualan_2026_02 log_penjualan  (cost=0.00..56.00 rows=100 width=0)
         Filter: (tanggal = '2026-02-15'::date)
(3 rows)

                                             QUERY PLAN                                             
----------------------------------------------------------------------------------------------------
 Aggregate  (cost=143.00..143.01 rows=1 width=8)
   ->  Append  (cost=0.00..139.50 rows=1400 width=0)
         ->  Seq Scan on log_penjualan_2026_02 log_penjualan_1  (cost=0.00..63.00 rows=900 width=0)
               Filter: ((tanggal >= '2026-02-20'::date) AND (tanggal <= '2026-03-05'::date))
         ->  Seq Scan on log_penjualan_2026_03 log_penjualan_2  (cost=0.00..69.50 rows=500 width=0)
               Filter: ((tanggal >= '2026-02-20'::date) AND (tanggal <= '2026-03-05'::date))
(6 rows)

Tiga rencana dalam satu file memperlihatkan tiga keadaan. Query pertama tanpa filter memuat node Append dengan ketiga partisi — wajar, karena jawabannya bisa ada di mana saja. Query kedua dengan tanggal = DATE '2026-02-15' hanya berisi Seq Scan on log_penjualan_2026_02: dua partisi lainnya tidak pernah disebut rencana, sehingga 6.200 baris Januari dan Maret tidak dibaca sama sekali. Query ketiga dengan rentang BETWEEN '2026-02-20' AND '2026-03-05' mempertahankan dua partisi — rentang itu melintasi Februari dan Maret, jadi hanya Januari yang dipangkas. Perhatikan juga alias log_penjualan_2026_02 log_penjualan: baris yang tersisa tetap tampak seolah berasal dari tabel induk.

Membandingkan: Pruning Dimatikan #

Sebelum percaya penuh, kita bandingkan dengan kondisi pruning dimatikan lewat pengaturan enable_partition_pruning:

-- Matikan pruning untuk membandingkan perilaku bawaan.
SET enable_partition_pruning = off;

-- Filter yang sama persis dengan contoh sebelumnya,
-- tetapi kini ketiga partisi ikut direncanakan.
EXPLAIN SELECT count(*) FROM log_penjualan
WHERE tanggal = DATE '2026-02-15';

-- Nyalakan kembali agar contoh-contoh berikutnya berperilaku bawaan.
SET enable_partition_pruning = on;

Output yang Dihasilkan:

text
SET
                                             QUERY PLAN                                             
----------------------------------------------------------------------------------------------------
 Aggregate  (cost=180.26..180.27 rows=1 width=8)
   ->  Append  (cost=0.00..180.01 rows=102 width=0)
         ->  Seq Scan on log_penjualan_2026_01 log_penjualan_1  (cost=0.00..61.75 rows=1 width=0)
               Filter: (tanggal = '2026-02-15'::date)
         ->  Seq Scan on log_penjualan_2026_02 log_penjualan_2  (cost=0.00..56.00 rows=100 width=0)
               Filter: (tanggal = '2026-02-15'::date)
         ->  Seq Scan on log_penjualan_2026_03 log_penjualan_3  (cost=0.00..61.75 rows=1 width=0)
               Filter: (tanggal = '2026-02-15'::date)
(8 rows)

SET

Filter yang sama persis dengan contoh sebelumnya kini menghasilkan Append berisi ketiga partisi, lengkap dengan baris estimasi 1 pada partisi Januari dan Maret — planner tahu partisi itu nyaris tidak memuat jawaban, tetapi tetap harus merencanakan pemindaiannya karena pruning dimatikan. Total biaya rencana naik dari 56 menjadi 180 satuan biaya untuk query yang jawabannya identik. Pengaturan ini jarang perlu dimatikan; kegunaannya terbatas untuk men-debug atau mengukur seberapa besar sumbangan pruning.

Pruning Saat Eksekusi untuk Prepared Statement #

Pruning statis butuh nilai yang diketahui saat planning. Prepared statement dengan parameter $1 justru menyembunyikan nilai itu — rencana yang di-cache harus bisa dipakai untuk parameter apa pun. PostgreSQL menjawabnya dengan runtime pruning: executor memangkas partisi pada saat eksekusi, tepat setelah nilai parameter diketahui. Karena output berikut memuat EXPLAIN ANALYZE dengan waktu eksekusi nyata, output-nya disalin dari eksekusi nyata dan ditampilkan manual — angka actual time, Planning Time, dan Execution Time akan berbeda di mesin Anda, tetapi bentuk rencana dan baris Subplans Removed stabil:

-- Paksa generic plan agar nilai parameter tidak diketahui saat planning.
SET plan_cache_mode = force_generic_plan;

-- Prepared statement dengan parameter tanggal.
PREPARE hitung_per_tanggal (date) AS
    SELECT count(*) FROM log_penjualan WHERE tanggal = $1;

-- Pruning kini terjadi saat EKSEKUSI, bukan saat planning:
-- planner menyusun rencana dengan tiga partisi, lalu executor
-- membuang dua subplan setelah nilai $1 diketahui.
EXPLAIN ANALYZE EXECUTE hitung_per_tanggal(DATE '2026-02-15');

Output yang Dihasilkan:

text
SET
PREPARE
                                                                   QUERY PLAN                                                                   
------------------------------------------------------------------------------------------------------------------------------------------------
 Aggregate  (cost=181.75..181.76 rows=1 width=8) (actual time=0.212..0.213 rows=1 loops=1)
   ->  Append  (cost=0.00..181.00 rows=300 width=0) (actual time=0.008..0.204 rows=100 loops=1)
         Subplans Removed: 2
         ->  Seq Scan on log_penjualan_2026_02 log_penjualan_1  (cost=0.00..56.00 rows=100 width=0) (actual time=0.008..0.198 rows=100 loops=1)
               Filter: (tanggal = $1)
               Rows Removed by Filter: 2700
 Planning Time: 0.378 ms
 Execution Time: 0.243 ms
(8 rows)

Dua tanda pengenal runtime pruning terlihat jelas. Pertama, Filter: (tanggal = $1) — rencana generik ini benar-benar tidak tahu nilainya, beda dengan Filter: (tanggal = '2026-02-15') pada pruning statis. Kedua, baris Subplans Removed: 2 di bawah node Append: dari tiga subplan yang disusun planner, executor membuang dua partisi saat nilai 2026-02-15 diketahui, dan hanya Seq Scan Februari yang benar-benar berjalan (rows=100). File memakai plan_cache_mode = force_generic_plan agar pergantian ke rencana generik terjadi sejak eksekusi pertama — pada perilaku bawaan, lima eksekusi pertama masih memakai rencana khusus per nilai parameter.

Pruning Bersama Partisi DEFAULT #

Kehadiran partisi DEFAULT sering ditakuti membuat pruning gagal. Kenyataannya planner tetap memangkas DEFAULT ketika predikat bertentangan dengan constraint implisitnya — DEFAULT hanya menampung nilai di luar batas partisi lain:

-- Tambahkan partisi DEFAULT pada tabel yang sama.
CREATE TABLE log_penjualan_default PARTITION OF log_penjualan DEFAULT;

-- Nilai di dalam rentang Februari: DEFAULT tetap bisa dipangkas
-- karena batasnya diketahui bertentangan dengan predikat.
EXPLAIN SELECT count(*) FROM log_penjualan
WHERE tanggal = DATE '2026-02-15';

-- Nilai di luar semua rentang: hanya DEFAULT yang tersisa di rencana.
EXPLAIN SELECT count(*) FROM log_penjualan
WHERE tanggal = DATE '2026-06-15';

Output yang Dihasilkan:

text
CREATE TABLE
                                         QUERY PLAN                                         
--------------------------------------------------------------------------------------------
 Aggregate  (cost=56.25..56.26 rows=1 width=8)
   ->  Seq Scan on log_penjualan_2026_02 log_penjualan  (cost=0.00..56.00 rows=100 width=0)
         Filter: (tanggal = '2026-02-15'::date)
(3 rows)

                                        QUERY PLAN                                        
------------------------------------------------------------------------------------------
 Aggregate  (cost=21.51..21.52 rows=1 width=8)
   ->  Seq Scan on log_penjualan_default log_penjualan  (cost=0.00..21.50 rows=5 width=0)
         Filter: (tanggal = '2026-06-15'::date)
(3 rows)

Untuk tanggal = DATE '2026-02-15' (di dalam rentang Februari), rencana tetap hanya berisi partisi Februari — DEFAULT dipangkas karena nilainya pasti di luar jangkauannya. Untuk tanggal = DATE '2026-06-15' (di luar semua rentang), satu-satunya kandidat justru DEFAULT. Jadi kehilangan performa bukan datang dari query, melainkan dari pemeliharaan: menciptakan partisi baru saat DEFAULT berisi baris yang rentangnya bertabrakan akan ditolak sampai baris itu dipindahkan — alasan kuat menjaga DEFAULT tetap ramping.

Partition-Wise Join #

Dua tabel terpartisi dengan partition key sama dapat di-join per pasangan partisi alih-alih digabung dulu seluruhnya. Teknik ini dikendalikan oleh enable_partitionwise_join dan berjalan baik dengan pruning — tiap pasangan partisi kecil di-join terpisah:

-- Dua tabel terpartisi LIST dengan partition key yang sama: kota.
CREATE TABLE pelanggan_kota (
    id    integer NOT NULL,
    nama  text NOT NULL,
    kota  text NOT NULL
) PARTITION BY LIST (kota);

CREATE TABLE pelanggan_jakarta PARTITION OF pelanggan_kota
    FOR VALUES IN ('Jakarta');
CREATE TABLE pelanggan_bandung PARTITION OF pelanggan_kota
    FOR VALUES IN ('Bandung');

CREATE TABLE pengiriman_kota (
    id    integer NOT NULL,
    kota  text NOT NULL,
    berat numeric(8,2) NOT NULL
) PARTITION BY LIST (kota);

CREATE TABLE pengiriman_jakarta PARTITION OF pengiriman_kota
    FOR VALUES IN ('Jakarta');
CREATE TABLE pengiriman_bandung PARTITION OF pengiriman_kota
    FOR VALUES IN ('Bandung');

INSERT INTO pelanggan_kota VALUES
    (1, 'Budi', 'Jakarta'),
    (2, 'Siti', 'Bandung');
INSERT INTO pengiriman_kota VALUES
    (1, 'Jakarta', 12.50),
    (2, 'Bandung',  3.25),
    (3, 'Jakarta',  8.00);

ANALYZE pelanggan_kota;
ANALYZE pengiriman_kota;

-- Join antar partitioned table: secara bawaan digabung dulu
-- menjadi dua Append besar, lalu di-join sekali.
SET enable_partitionwise_join = off;
EXPLAIN SELECT p.nama, count(*) AS jumlah_kiriman
FROM pelanggan_kota p
JOIN pengiriman_kota k ON p.kota = k.kota
GROUP BY p.nama;

-- Dengan partition-wise join aktif, join dipecah per pasangan partisi.
SET enable_partitionwise_join = on;
EXPLAIN SELECT p.nama, count(*) AS jumlah_kiriman
FROM pelanggan_kota p
JOIN pengiriman_kota k ON p.kota = k.kota
GROUP BY p.nama;

Output yang Dihasilkan:

text
CREATE TABLE
CREATE TABLE
CREATE TABLE
CREATE TABLE
CREATE TABLE
CREATE TABLE
INSERT 0 2
INSERT 0 3
ANALYZE
ANALYZE
SET
                                             QUERY PLAN                                             
----------------------------------------------------------------------------------------------------
 GroupAggregate  (cost=4.19..4.24 rows=2 width=13)
   Group Key: p.nama
   ->  Sort  (cost=4.19..4.20 rows=3 width=5)
         Sort Key: p.nama
         ->  Nested Loop  (cost=0.00..4.17 rows=3 width=5)
               Join Filter: (p.kota = k.kota)
               ->  Append  (cost=0.00..2.05 rows=3 width=8)
                     ->  Seq Scan on pengiriman_bandung k_1  (cost=0.00..1.01 rows=1 width=8)
                     ->  Seq Scan on pengiriman_jakarta k_2  (cost=0.00..1.02 rows=2 width=8)
               ->  Materialize  (cost=0.00..2.04 rows=2 width=13)
                     ->  Append  (cost=0.00..2.03 rows=2 width=13)
                           ->  Seq Scan on pelanggan_bandung p_1  (cost=0.00..1.01 rows=1 width=13)
                           ->  Seq Scan on pelanggan_jakarta p_2  (cost=0.00..1.01 rows=1 width=13)
(13 rows)

SET
                                          QUERY PLAN                                          
----------------------------------------------------------------------------------------------
 GroupAggregate  (cost=4.13..4.17 rows=2 width=13)
   Group Key: p.nama
   ->  Sort  (cost=4.13..4.13 rows=3 width=5)
         Sort Key: p.nama
         ->  Append  (cost=0.00..4.10 rows=3 width=5)
               ->  Nested Loop  (cost=0.00..2.03 rows=1 width=5)
                     Join Filter: (p_1.kota = k_1.kota)
                     ->  Seq Scan on pelanggan_bandung p_1  (cost=0.00..1.01 rows=1 width=13)
                     ->  Seq Scan on pengiriman_bandung k_1  (cost=0.00..1.01 rows=1 width=8)
               ->  Nested Loop  (cost=0.00..2.06 rows=2 width=5)
                     Join Filter: (p_2.kota = k_2.kota)
                     ->  Seq Scan on pelanggan_jakarta p_2  (cost=0.00..1.01 rows=1 width=13)
                     ->  Seq Scan on pengiriman_jakarta k_2  (cost=0.00..1.02 rows=2 width=8)
(13 rows)

Pada rencana pertama (pengaturan bawaan off), kedua tabel digabung lebih dulu menjadi dua node Append besar, lalu satu join tunggal membandingkan seluruh 3 baris kiriman dengan 2 baris pelanggan lewat filter p.kota = k.kota. Pada rencana kedua (on), join dipecah menjadi dua Nested Loop terpisah: pelanggan_bandung hanya berpasangan dengan pengiriman_bandung, dan pelanggan_jakarta hanya dengan pengiriman_jakarta — filter kota bahkan tidak lagi dibutuhkan karena pasangan partisi sudah dijamin senilai kuncinya. Pada tabel produksi dengan puluhan partisi besar, pemecahan seperti ini mengecilkan memori kerja join per langkah dan membuka peluang join paralel per partisi.

Referensi #

Pruning menjaga kecepatan pembacaan setelah tabel dipecah. Pertanyaan berikutnya: bagaimana dengan index — apakah setiap partisi harus diindeks satu per satu, dan bagaimana aturan constraint unik pada tabel terpartisi? Chapter berikutnya menjawab keduanya.

Berkas Sumber

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

buat-tabel.sql

Unduh berkas
sql
-- Tabel penjualan satu kuartal dengan tiga partisi bulanan.
CREATE TABLE log_penjualan (
    id      bigint NOT NULL,
    tanggal date NOT NULL,
    kota    text NOT NULL,
    total   numeric(12,2) NOT NULL
) PARTITION BY RANGE (tanggal);

CREATE TABLE log_penjualan_2026_01 PARTITION OF log_penjualan
    FOR VALUES FROM ('2026-01-01') TO ('2026-02-01');
CREATE TABLE log_penjualan_2026_02 PARTITION OF log_penjualan
    FOR VALUES FROM ('2026-02-01') TO ('2026-03-01');
CREATE TABLE log_penjualan_2026_03 PARTITION OF log_penjualan
    FOR VALUES FROM ('2026-03-01') TO ('2026-04-01');

-- 9000 baris merata: 100 baris per tanggal, 1 Jan - 31 Mar 2026.
INSERT INTO log_penjualan
SELECT g,
       DATE '2026-01-01' + ((g - 1) % 90),
       (ARRAY['Bandung', 'Jakarta', 'Medan', 'Surabaya'])[1 + (g % 4)],
       ((g * 37) % 1000 + 1) * 1000.00
FROM generate_series(1, 9000) AS g;

SELECT tableoid::regclass AS partisi, count(*) AS baris
FROM log_penjualan
GROUP BY 1
ORDER BY 1;

-- Statistik diisi sekarang agar rencana eksekusi stabil dan dapat dibandingkan.
ANALYZE log_penjualan;

partisi-default.sql

Unduh berkas
sql
-- Tambahkan partisi DEFAULT pada tabel yang sama.
CREATE TABLE log_penjualan_default PARTITION OF log_penjualan DEFAULT;

-- Nilai di dalam rentang Februari: DEFAULT tetap bisa dipangkas
-- karena batasnya diketahui bertentangan dengan predikat.
EXPLAIN SELECT count(*) FROM log_penjualan
WHERE tanggal = DATE '2026-02-15';

-- Nilai di luar semua rentang: hanya DEFAULT yang tersisa di rencana.
EXPLAIN SELECT count(*) FROM log_penjualan
WHERE tanggal = DATE '2026-06-15';

partitionwise-join.sql

Unduh berkas
sql
-- Dua tabel terpartisi LIST dengan partition key yang sama: kota.
CREATE TABLE pelanggan_kota (
    id    integer NOT NULL,
    nama  text NOT NULL,
    kota  text NOT NULL
) PARTITION BY LIST (kota);

CREATE TABLE pelanggan_jakarta PARTITION OF pelanggan_kota
    FOR VALUES IN ('Jakarta');
CREATE TABLE pelanggan_bandung PARTITION OF pelanggan_kota
    FOR VALUES IN ('Bandung');

CREATE TABLE pengiriman_kota (
    id    integer NOT NULL,
    kota  text NOT NULL,
    berat numeric(8,2) NOT NULL
) PARTITION BY LIST (kota);

CREATE TABLE pengiriman_jakarta PARTITION OF pengiriman_kota
    FOR VALUES IN ('Jakarta');
CREATE TABLE pengiriman_bandung PARTITION OF pengiriman_kota
    FOR VALUES IN ('Bandung');

INSERT INTO pelanggan_kota VALUES
    (1, 'Budi', 'Jakarta'),
    (2, 'Siti', 'Bandung');
INSERT INTO pengiriman_kota VALUES
    (1, 'Jakarta', 12.50),
    (2, 'Bandung',  3.25),
    (3, 'Jakarta',  8.00);

ANALYZE pelanggan_kota;
ANALYZE pengiriman_kota;

-- Join antar partitioned table: secara bawaan digabung dulu
-- menjadi dua Append besar, lalu di-join sekali.
SET enable_partitionwise_join = off;
EXPLAIN SELECT p.nama, count(*) AS jumlah_kiriman
FROM pelanggan_kota p
JOIN pengiriman_kota k ON p.kota = k.kota
GROUP BY p.nama;

-- Dengan partition-wise join aktif, join dipecah per pasangan partisi.
SET enable_partitionwise_join = on;
EXPLAIN SELECT p.nama, count(*) AS jumlah_kiriman
FROM pelanggan_kota p
JOIN pengiriman_kota k ON p.kota = k.kota
GROUP BY p.nama;

pruning-nonaktif.sql

Unduh berkas
sql
-- Matikan pruning untuk membandingkan perilaku bawaan.
SET enable_partition_pruning = off;

-- Filter yang sama persis dengan contoh sebelumnya,
-- tetapi kini ketiga partisi ikut direncanakan.
EXPLAIN SELECT count(*) FROM log_penjualan
WHERE tanggal = DATE '2026-02-15';

-- Nyalakan kembali agar contoh-contoh berikutnya berperilaku bawaan.
SET enable_partition_pruning = on;

pruning-runtime.sql

Unduh berkas
sql
-- Paksa generic plan agar nilai parameter tidak diketahui saat planning.
SET plan_cache_mode = force_generic_plan;

-- Prepared statement dengan parameter tanggal.
PREPARE hitung_per_tanggal (date) AS
    SELECT count(*) FROM log_penjualan WHERE tanggal = $1;

-- Pruning kini terjadi saat EKSEKUSI, bukan saat planning:
-- planner menyusun rencana dengan tiga partisi, lalu executor
-- membuang dua subplan setelah nilai $1 diketahui.
EXPLAIN ANALYZE EXECUTE hitung_per_tanggal(DATE '2026-02-15');

pruning-statis.sql

Unduh berkas
sql
-- Tanpa filter: semua partisi harus dibaca.
EXPLAIN SELECT count(*) FROM log_penjualan;

-- Filter kesamaan pada partition key: dua partisi hilang dari rencana.
EXPLAIN SELECT count(*) FROM log_penjualan
WHERE tanggal = DATE '2026-02-15';

-- Filter rentang yang melintasi Feb-Mar: partisi Januari hilang.
EXPLAIN SELECT count(*)
FROM log_penjualan
WHERE tanggal BETWEEN DATE '2026-02-20' AND DATE '2026-03-05';