Chapter sebelumnya mengajarkan cara membuat dan mengakses array; sekarang kita mengajukan pertanyaan yang jauh lebih sering muncul di aplikasi nyata: “baris mana yang array-nya memuat nilai ini?”, “produk mana yang punya tag elektronik sekaligus diskon?”, dan “harga mana yang melewati semua ambang batas?”. PostgreSQL menjawab semua itu dengan satu rombongan operator khusus array. Kita memakai kembali tabel produk milik Toko Maju Jaya, kali ini dengan enam baris yang sengaja memuat array kosong, kolom NULL, dan elemen NULL — tiga kasus yang membuat operator array berperilaku mengejutkan.
Sebelum membahas operator satu per satu, kita bangun dulu meja ujinya — enam baris produk dengan seluruh variasi kekosongan array:
CREATE TABLE produk (
id integer PRIMARY KEY,
nama text NOT NULL,
harga numeric(10, 2) NOT NULL,
tag text[]
);
INSERT INTO produk (id, nama, harga, tag) VALUES
(1, 'Buku Tulis 384 Halaman', 9500, ARRAY['alat tulis', 'diskon']),
(2, 'Laptop Kerja 14 inci', 8500000, ARRAY['elektronik', 'kantor']),
(3, 'Mouse Wireless', 185000, ARRAY['elektronik', 'diskon']),
(4, 'Kertas HVS A4 80gsm', 55000, '{}'),
(5, 'Stapler Besar', 24000, NULL),
(6, 'Kamera Mirrorless', 12000000, ARRAY['elektronik', NULL]);
SELECT id, nama, harga, tag
FROM produk
ORDER BY id;Output yang Dihasilkan:
CREATE TABLE
INSERT 0 6
id | nama | harga | tag
----+------------------------+-------------+-----------------------
1 | Buku Tulis 384 Halaman | 9500.00 | {"alat tulis",diskon}
2 | Laptop Kerja 14 inci | 8500000.00 | {elektronik,kantor}
3 | Mouse Wireless | 185000.00 | {elektronik,diskon}
4 | Kertas HVS A4 80gsm | 55000.00 | {}
5 | Stapler Besar | 24000.00 |
6 | Kamera Mirrorless | 12000000.00 | {elektronik,NULL}
(6 rows)Peta Lengkap Operator Array #
Dengan data di atas, berikut peta seluruh operator yang bekerja pada array:
| Operator | Makna | Contoh |
|---|---|---|
= , <> | sama / tidak sama, dibandingkan elemen demi elemen | tag = ARRAY['diskon'] |
< , > , <= , >= | urutan leksikografis seperti kamus | ARRAY['a'] < ARRAY['a','b'] |
@> | array kiri memuat semua elemen array kanan | tag @> ARRAY['elektronik','diskon'] |
<@ | array kiri terkandung dalam array kanan | tag <@ ARRAY['elektronik','kantor'] |
&& | kedua array beririsan (minimal satu elemen sama) | tag && ARRAY['diskon','murah'] |
|| | penggabungan: array+array, array+elemen, elemen+array | tag || 'promo'::text |
x = ANY (arr) | x cocok dengan salah satu elemen | 'diskon' = ANY (tag) |
x < ANY (arr) , x > ALL (arr) | x dibandingkan ke tiap elemen (varian ANY/ALL untuk semua pembanding) | harga > ALL (batas) |
Perhatikan satu kekosongan yang disengaja: tidak ada operator - untuk array. Menghapus elemen ditangani fungsi array_remove(), yang dibahas di chapter Fungsi Array
.
Membandingkan Array Secara Leksikografis #
Operator < dan > membandingkan array seperti kamus membandingkan kata: elemen pertama yang berbeda menentukan hasil, dan array yang lebih pendek menang bila menjadi prefiks array lain. Mari kita lihat perilakunya secara berurutan:
-- kesamaan dan ketidaksamaan isi array, elemen demi elemen
SELECT ARRAY['alat tulis', 'diskon'] = ARRAY['alat tulis', 'diskon'] AS sama,
ARRAY['a', 'b'] <> ARRAY['a', 'c'] AS beda;
-- perbandingan < > bersifat leksikografis, seperti urutan kamus
SELECT ARRAY['a'] < ARRAY['a', 'b'] AS prefiks_lebih_pendek,
ARRAY['a', 'c'] > ARRAY['a', 'b'] AS elemen_menentukan,
ARRAY[1, 2] < ARRAY[1, 10] AS angka_per_elemen,
ARRAY['b', 'a'] < ARRAY['a', 'z'] AS elemen_pertama_menang;
-- NULL elemen dianggap SAMA dengan NULL elemen lain pada operator =
SELECT ARRAY[NULL::int, 1] = ARRAY[NULL::int, 1] AS null_dianggap_sama;Output yang Dihasilkan:
sama | beda
------+------
t | t
(1 row)
prefiks_lebih_pendek | elemen_menentukan | angka_per_elemen | elemen_pertama_menang
----------------------+-------------------+------------------+-----------------------
t | t | t | f
(1 row)
null_dianggap_sama
--------------------
t
(1 row)Baris prefiks_lebih_pendek membuktikan ARRAY['a'] < ARRAY['a','b'] — prefiks selalu lebih kecil. Baris elemen_pertama_menang memperlihatkan perbandingan berhenti di elemen pertama yang berbeda: 'b' melawan 'z' mengalahkan sisa elemen. Baris angka_per_elemen menunjukkan perbandingan numerik membandingkan nilai, bukan teksnya (2 lebih kecil dari 10). Yang paling mengejutkan ada di baris terakhir: ARRAY[NULL,1] = ARRAY[NULL,1] menghasilkan true — pada operator array, elemen NULL dianggap sama dengan elemen NULL lainnya, berbeda dari perbandingan skalar NULL = NULL yang menghasilkan NULL.
Containment (@>) dan Overlap (&&) #
Tiga operator ini menjawab pertanyaan keanggotaan dengan tingkat ketatatan yang berbeda. @> (contains) menuntut semua elemen di sisi kanan hadir; && (overlap) cukup satu elemen bersama; <@ adalah kebalikan @>:
-- @> : array kiri MEMUAT semua elemen array kanan
SELECT id, nama
FROM produk
WHERE tag @> ARRAY['elektronik']
ORDER BY id;
-- @> menuntut semua elemen hadir sekaligus
SELECT id, nama
FROM produk
WHERE tag @> ARRAY['elektronik', 'diskon']
ORDER BY id;
-- <@ : kebalikannya, seluruh array kiri terkandung di array kanan
SELECT id, nama
FROM produk
WHERE tag <@ ARRAY['elektronik', 'kantor', 'diskon', 'alat tulis']
ORDER BY id;
-- && : cukup SATU elemen yang sama (overlap/irisan)
SELECT id, nama
FROM produk
WHERE tag && ARRAY['diskon', 'murah']
ORDER BY id;Output yang Dihasilkan:
id | nama
----+----------------------
2 | Laptop Kerja 14 inci
3 | Mouse Wireless
6 | Kamera Mirrorless
(3 rows)
id | nama
----+----------------
3 | Mouse Wireless
(1 row)
id | nama
----+------------------------
1 | Buku Tulis 384 Halaman
2 | Laptop Kerja 14 inci
3 | Mouse Wireless
4 | Kertas HVS A4 80gsm
(4 rows)
id | nama
----+------------------------
1 | Buku Tulis 384 Halaman
3 | Mouse Wireless
(2 rows)Query pertama mencari produk bertag elektronik dan mengembalikan tiga baris — termasuk Kamera Mirrorless yang elemen keduanya NULL; elemen NULL tidak mengganggu @> selama elemen yang dicari memang ditemukan. Query kedua menaikkan tuntutan menjadi dua tag sekaligus, sehingga hanya Mouse Wireless lolos. Query <@ membalik arah: seluruh tag produk harus berada dalam daftar yang diberikan — Laptop Kerja lolos karena elektronik dan kantor keduanya ada di daftar, sedangkan Buku Tulis gugur karena alat tulis tidak ada. Query terakhir && menangkap semua produk yang memuat diskon atau murah — Buku Tulis dan Mouse Wireless masuk lewat diskon meskipun tidak ada satu pun produk bertag murah.
Penggabungan dengan Operator || #
Operator || menyatukan array dengan array, array dengan satu elemen (di akhir), atau satu elemen dengan array (di awal). Ketiganya mengembalikan array baru — nilai aslinya tidak berubah:
-- array || array : seluruh isi kanan menempel di belakang
SELECT ARRAY['elektronik', 'kantor'] || ARRAY['premium', 'garansi'] AS gabung_array;
-- array || elemen : satu elemen baru di akhir (cast eksplisit ke tipe elemen)
SELECT ARRAY['elektronik'] || 'premium'::text AS tambah_di_akhir;
-- elemen || array : satu elemen baru di awal
SELECT 'unggulan'::text || ARRAY['elektronik'] AS tambah_di_awal;
-- pola UPDATE yang paling sering dipakai: tambah satu tag
UPDATE produk SET tag = tag || 'promo'::text
WHERE id = 1;
SELECT id, nama, tag
FROM produk
WHERE id = 1;
-- menghapus elemen bukan lewat operator, tapi lewat fungsi array_remove
SELECT array_remove(ARRAY['elektronik', 'kantor', 'diskon'], 'kantor') AS tanpa_kantor;Output yang Dihasilkan:
gabung_array
-------------------------------------
{elektronik,kantor,premium,garansi}
(1 row)
tambah_di_akhir
----------------------
{elektronik,premium}
(1 row)
tambah_di_awal
-----------------------
{unggulan,elektronik}
(1 row)
UPDATE 1
id | nama | tag
----+------------------------+-----------------------------
1 | Buku Tulis 384 Halaman | {"alat tulis",diskon,promo}
(1 row)
tanpa_kantor
---------------------
{elektronik,diskon}
(1 row)Tiga SELECT pertama memperlihatkan ketiga bentuk penggabungan pada array literal. Bentuk tag || 'promo'::text dalam UPDATE adalah pola yang paling sering dipakai di aplikasi: menambah satu tag pada baris tertentu. Cast eksplisit ::text tetap wajib seperti dibahas di chapter sebelumnya — tanpa cast, PostgreSQL mencoba mem-parse 'promo' sebagai array dan gagal dengan error malformed array literal. Untuk menghapus elemen, satu-satunya jalur adalah fungsi array_remove() sebagaimana diperlihatkan di baris terakhir file.
ANY dan ALL: Pola Paling Sering Dipakai #
Dari semua operator di tabel di atas, = ANY adalah yang paling sering muncul di query produksi. Bentuk 'nilai' = ANY (kolom_array) membaca “nilai ini sama dengan salah satu elemen kolom” — kebalikan arah dari IN, yang membandingkan satu kolom ke daftar tetap:
-- = ANY : benar bila ADA satu elemen array yang memenuhi perbandingan
SELECT id, nama
FROM produk
WHERE 'elektronik' = ANY (tag)
ORDER BY id;
-- IN pada daftar tetap sebenarnya adalah = ANY pada array
SELECT 3 = ANY (ARRAY[1, 3, 5]) AS seperti_in,
4 = ANY (ARRAY[1, 3, 5]) AS tidak_ada;
-- operator lain juga bisa: < ANY = lebih kecil dari SALAH SATU ambang
SELECT id, nama, harga
FROM produk
WHERE harga < ANY (ARRAY[20000, 100000])
ORDER BY id;
-- > ALL : harus lebih besar dari SEMUA ambang sekaligus
SELECT id, nama, harga
FROM produk
WHERE harga > ALL (ARRAY[20000, 100000])
ORDER BY id;Output yang Dihasilkan:
id | nama
----+----------------------
2 | Laptop Kerja 14 inci
3 | Mouse Wireless
6 | Kamera Mirrorless
(3 rows)
seperti_in | tidak_ada
------------+-----------
t | f
(1 row)
id | nama | harga
----+------------------------+----------
1 | Buku Tulis 384 Halaman | 9500.00
4 | Kertas HVS A4 80gsm | 55000.00
5 | Stapler Besar | 24000.00
(3 rows)
id | nama | harga
----+----------------------+-------------
2 | Laptop Kerja 14 inci | 8500000.00
3 | Mouse Wireless | 185000.00
6 | Kamera Mirrorless | 12000000.00
(3 rows)Query pertama menemukan semua produk bertag elektronik — hasilnya identik dengan tag @> ARRAY['elektronik'], dan memilih antara keduanya selera gaya: = ANY lebih ringkas untuk satu nilai, @> menang begitu kriteria berjumlah dua atau lebih. Dua query terakhir memperlihatkan ANY dan ALL pada pembanding angka: harga < ANY (ARRAY[20000, 100000]) berarti “lebih murah dari salah satu ambang” (cukup satu terpenuhi), sedangkan harga > ALL (...) menuntut harga melewati semua ambang. Perhatikan hasilnya saling melengkapi: < ANY mengembalikan produk murah, > ALL mengembalikan produk premium.
Jebakan NULL pada Operator Array #
Baris 5 (kolom NULL) dan baris 6 (elemen NULL) dari tabel kita kini masuk ke panggung. Ketika operator array menemukan kecocokan, elemen NULL diabaikan. Ketika tidak ada kecocokan sama sekali dan array memuat NULL, hasilnya NULL — bukan false — dan di klausa WHERE hal itu berarti baris hilang diam-diam:
-- elemen NULL tidak mengganggu @> maupun = ANY yang menemukan kecocokan
SELECT id, nama, tag
FROM produk
WHERE tag @> ARRAY['elektronik']
ORDER BY id;
-- tapi bila TIDAK ada kecocokan dan array memuat NULL: hasil NULL, bukan false
SELECT 3 = ANY (ARRAY[NULL::int, 1]) AS tak_cocok_ada_null,
3 = ANY (ARRAY[1]) AS tak_cocok_biasa,
1 = ANY (ARRAY[NULL::int, 1]) AS cocok_abaikan_null;
-- implikasi nyata: "produk yang TIDAK bertag diskon" kehilangan baris diam-diam
SELECT id, nama
FROM produk
WHERE NOT ('diskon' = ANY (tag))
ORDER BY id;
-- perbaikannya: COALESCE mengubah NULL menjadi false secara eksplisit
SELECT id, nama
FROM produk
WHERE NOT COALESCE('diskon' = ANY (tag), false)
ORDER BY id;Output yang Dihasilkan:
id | nama | tag
----+----------------------+---------------------
2 | Laptop Kerja 14 inci | {elektronik,kantor}
3 | Mouse Wireless | {elektronik,diskon}
6 | Kamera Mirrorless | {elektronik,NULL}
(3 rows)
tak_cocok_ada_null | tak_cocok_biasa | cocok_abaikan_null
--------------------+-----------------+--------------------
| f | t
(1 row)
id | nama
----+----------------------
2 | Laptop Kerja 14 inci
4 | Kertas HVS A4 80gsm
(2 rows)
id | nama
----+----------------------
2 | Laptop Kerja 14 inci
4 | Kertas HVS A4 80gsm
5 | Stapler Besar
6 | Kamera Mirrorless
(4 rows)Query pertama membuktikan @> tetap bekerja pada array ber-elemen NULL. SELECT kedua adalah inti jebakannya: 3 = ANY (ARRAY[NULL, 1]) menghasilkan NULL karena elemen 1 tidak cocok dan elemen NULL “tidak tahu jawabannya”, sementara 3 = ANY (ARRAY[1]) yang tanpa NULL jujur menghasilkan false. Query ketiga memperlihatkan korban nyata: “produk yang tidak bertag diskon” seharusnya mengembalikan empat baris (Stapler dan Kamera masuk kategori itu), tetapi hanya dua yang muncul — baris dengan array NULL dan baris dengan elemen NULL tersaring tanpa pesan error. Query keempat adalah obatnya: COALESCE(..., false) mengubah NULL menjadi false secara eksplisit sehingga keempat baris yang benar kembali.
Operator memungkinkan kita bertanya pada array; fungsi memungkinkan kita mengubah bentuknya — membongkar array menjadi baris lewat unnest(), merakit baris menjadi array lewat array_agg(), sampai menghitung, memotong, dan mengganti isinya. Chapter berikutnya mengulas seluruh kotak peralatan itu satu per satu.
Referensi #
Berkas Sumber
Berkas pendukung halaman ini dapat diunduh dan dijalankan langsung sesuai penjelasan di atas.
any-dan-all.sql
Unduh berkas-- = ANY : benar bila ADA satu elemen array yang memenuhi perbandingan
SELECT id, nama
FROM produk
WHERE 'elektronik' = ANY (tag)
ORDER BY id;
-- IN pada daftar tetap sebenarnya adalah = ANY pada array
SELECT 3 = ANY (ARRAY[1, 3, 5]) AS seperti_in,
4 = ANY (ARRAY[1, 3, 5]) AS tidak_ada;
-- operator lain juga bisa: < ANY = lebih kecil dari SALAH SATU ambang
SELECT id, nama, harga
FROM produk
WHERE harga < ANY (ARRAY[20000, 100000])
ORDER BY id;
-- > ALL : harus lebih besar dari SEMUA ambang sekaligus
SELECT id, nama, harga
FROM produk
WHERE harga > ALL (ARRAY[20000, 100000])
ORDER BY id;
buat-tabel.sql
Unduh berkasCREATE TABLE produk (
id integer PRIMARY KEY,
nama text NOT NULL,
harga numeric(10, 2) NOT NULL,
tag text[]
);
INSERT INTO produk (id, nama, harga, tag) VALUES
(1, 'Buku Tulis 384 Halaman', 9500, ARRAY['alat tulis', 'diskon']),
(2, 'Laptop Kerja 14 inci', 8500000, ARRAY['elektronik', 'kantor']),
(3, 'Mouse Wireless', 185000, ARRAY['elektronik', 'diskon']),
(4, 'Kertas HVS A4 80gsm', 55000, '{}'),
(5, 'Stapler Besar', 24000, NULL),
(6, 'Kamera Mirrorless', 12000000, ARRAY['elektronik', NULL]);
SELECT id, nama, harga, tag
FROM produk
ORDER BY id;
operator-concat.sql
Unduh berkas-- array || array : seluruh isi kanan menempel di belakang
SELECT ARRAY['elektronik', 'kantor'] || ARRAY['premium', 'garansi'] AS gabung_array;
-- array || elemen : satu elemen baru di akhir (cast eksplisit ke tipe elemen)
SELECT ARRAY['elektronik'] || 'premium'::text AS tambah_di_akhir;
-- elemen || array : satu elemen baru di awal
SELECT 'unggulan'::text || ARRAY['elektronik'] AS tambah_di_awal;
-- pola UPDATE yang paling sering dipakai: tambah satu tag
UPDATE produk SET tag = tag || 'promo'::text
WHERE id = 1;
SELECT id, nama, tag
FROM produk
WHERE id = 1;
-- menghapus elemen bukan lewat operator, tapi lewat fungsi array_remove
SELECT array_remove(ARRAY['elektronik', 'kantor', 'diskon'], 'kantor') AS tanpa_kantor;
operator-contains.sql
Unduh berkas-- @> : array kiri MEMUAT semua elemen array kanan
SELECT id, nama
FROM produk
WHERE tag @> ARRAY['elektronik']
ORDER BY id;
-- @> menuntut semua elemen hadir sekaligus
SELECT id, nama
FROM produk
WHERE tag @> ARRAY['elektronik', 'diskon']
ORDER BY id;
-- <@ : kebalikannya, seluruh array kiri terkandung di array kanan
SELECT id, nama
FROM produk
WHERE tag <@ ARRAY['elektronik', 'kantor', 'diskon', 'alat tulis']
ORDER BY id;
-- && : cukup SATU elemen yang sama (overlap/irisan)
SELECT id, nama
FROM produk
WHERE tag && ARRAY['diskon', 'murah']
ORDER BY id;
operator-null.sql
Unduh berkas-- elemen NULL tidak mengganggu @> maupun = ANY yang menemukan kecocokan
SELECT id, nama, tag
FROM produk
WHERE tag @> ARRAY['elektronik']
ORDER BY id;
-- tapi bila TIDAK ada kecocokan dan array memuat NULL: hasil NULL, bukan false
SELECT 3 = ANY (ARRAY[NULL::int, 1]) AS tak_cocok_ada_null,
3 = ANY (ARRAY[1]) AS tak_cocok_biasa,
1 = ANY (ARRAY[NULL::int, 1]) AS cocok_abaikan_null;
-- implikasi nyata: "produk yang TIDAK bertag diskon" kehilangan baris diam-diam
SELECT id, nama
FROM produk
WHERE NOT ('diskon' = ANY (tag))
ORDER BY id;
-- perbaikannya: COALESCE mengubah NULL menjadi false secara eksplisit
SELECT id, nama
FROM produk
WHERE NOT COALESCE('diskon' = ANY (tag), false)
ORDER BY id;
operator-perbandingan.sql
Unduh berkas-- kesamaan dan ketidaksamaan isi array, elemen demi elemen
SELECT ARRAY['alat tulis', 'diskon'] = ARRAY['alat tulis', 'diskon'] AS sama,
ARRAY['a', 'b'] <> ARRAY['a', 'c'] AS beda;
-- perbandingan < > bersifat leksikografis, seperti urutan kamus
SELECT ARRAY['a'] < ARRAY['a', 'b'] AS prefiks_lebih_pendek,
ARRAY['a', 'c'] > ARRAY['a', 'b'] AS elemen_menentukan,
ARRAY[1, 2] < ARRAY[1, 10] AS angka_per_elemen,
ARRAY['b', 'a'] < ARRAY['a', 'z'] AS elemen_pertama_menang;
-- NULL elemen dianggap SAMA dengan NULL elemen lain pada operator =
SELECT ARRAY[NULL::int, 1] = ARRAY[NULL::int, 1] AS null_dianggap_sama;