Menyaring baris dengan nilai yang sudah kita tahu — WHERE kota = 'Bandung' — itu mudah. Bersulit ketika daftar nilainya sendiri harus dihitung dulu: “produk yang pernah terjual” berarti menyaring produk terhadap daftar produk_id yang muncul di tabel penjualan; “produk yang lebih mahal dari semua produk Sembako” berarti membandingkan satu harga dengan sekumpulan harga sekaligus. Klausa WHERE punya perangkat lengkap untuk kedua situasi itu: IN untuk keanggotaan daftar, ANY dan ALL untuk perbandingan terhadap himpunan. Sayangnya perangkat yang sama juga menyimpan jebakan paling terkenal dalam sejarah SQL — NOT IN yang diam-diam mengosongkan hasil ketika daftar berisi NULL. Chapter ini menguji semuanya dengan data yang sama, termasuk memicu jebakannya secara sengaja supaya Anda mengenalinya di produksi.

Tabel Latihan: produk dan penjualan #

Domain tetap section ini: katalog 10 produk Toko Maju Jaya dan 10 transaksi penjualan Januari–Februari 2026. Perhatikan baris penjualan id 8: produk_id-nya NULL karena barang curahan yang terjual tanpa kode produk. Baris itu terlihat jinak sekarang, tetapi di bagian NOT IN nanti baris inilah yang akan mengosongkan seluruh hasil query tanpa satu pun pesan error:

sql buat-tabel.sql
-- Tabel latihan chapter Subquery dalam WHERE:
-- katalog produk dan penjualan Toko Maju Jaya.
CREATE TABLE produk (
    id       integer PRIMARY KEY,
    nama     text NOT NULL,
    kategori text NOT NULL,
    harga    numeric(10,2) NOT NULL,   -- harga satuan, rupiah
    stok     integer NOT NULL
);

CREATE TABLE penjualan (
    id           integer PRIMARY KEY,
    tanggal      date NOT NULL,
    produk_id    integer REFERENCES produk (id),  -- NULL = barang tanpa kode produk
    pelanggan_id integer NOT NULL,
    jumlah       integer NOT NULL                 -- unit terjual
);

INSERT INTO produk (id, nama, kategori, harga, stok) VALUES
    ( 1, 'Kopi Gayo',          'Minuman', 85000.00,  40),
    ( 2, 'Kopi Toraja',        'Minuman', 90000.00,  25),
    ( 3, 'Teh Melati',         'Minuman', 35000.00,  60),
    ( 4, 'Teh Hijau',          'Minuman', 40000.00,  50),
    ( 5, 'Gula Merah',         'Sembako', 15000.00, 100),
    ( 6, 'Beras Pandan Wangi', 'Sembako', 13000.00, 200),
    ( 7, 'Susu Kental Manis',  'Minuman', 18000.00,  30),
    ( 8, 'Keripik Singkong',   'Camilan', 20000.00,  45),
    ( 9, 'Kacang Goreng',      'Camilan', 25000.00,  55),
    (10, 'Madu Hutan',         'Sembako', 55000.00,  15);

INSERT INTO penjualan (id, tanggal, produk_id, pelanggan_id, jumlah) VALUES
    ( 1, DATE '2026-01-05', 1,    1, 10),
    ( 2, DATE '2026-01-08', 3,    2, 25),
    ( 3, DATE '2026-01-12', 5,    1, 40),
    ( 4, DATE '2026-01-15', 2,    1,  8),
    ( 5, DATE '2026-01-20', 1,    4, 12),
    ( 6, DATE '2026-01-22', 4,    2, 15),
    ( 7, DATE '2026-01-25', 5,    5, 30),
    ( 8, DATE '2026-02-02', NULL, 1, 20),
    ( 9, DATE '2026-02-05', 1,    4, 18),
    (10, DATE '2026-02-10', 6,    2, 22);

SELECT id, nama, kategori, harga, stok FROM produk ORDER BY id;
SELECT id, tanggal, produk_id, pelanggan_id, jumlah FROM penjualan ORDER BY id;

Output yang Dihasilkan:

text
CREATE TABLE
CREATE TABLE
INSERT 0 10
INSERT 0 10
 id |        nama        | kategori |  harga   | stok 
----+--------------------+----------+----------+------
  1 | Kopi Gayo          | Minuman  | 85000.00 |   40
  2 | Kopi Toraja        | Minuman  | 90000.00 |   25
  3 | Teh Melati         | Minuman  | 35000.00 |   60
  4 | Teh Hijau          | Minuman  | 40000.00 |   50
  5 | Gula Merah         | Sembako  | 15000.00 |  100
  6 | Beras Pandan Wangi | Sembako  | 13000.00 |  200
  7 | Susu Kental Manis  | Minuman  | 18000.00 |   30
  8 | Keripik Singkong   | Camilan  | 20000.00 |   45
  9 | Kacang Goreng      | Camilan  | 25000.00 |   55
 10 | Madu Hutan         | Sembako  | 55000.00 |   15
(10 rows)

 id |  tanggal   | produk_id | pelanggan_id | jumlah 
----+------------+-----------+--------------+--------
  1 | 2026-01-05 |         1 |            1 |     10
  2 | 2026-01-08 |         3 |            2 |     25
  3 | 2026-01-12 |         5 |            1 |     40
  4 | 2026-01-15 |         2 |            1 |      8
  5 | 2026-01-20 |         1 |            4 |     12
  6 | 2026-01-22 |         4 |            2 |     15
  7 | 2026-01-25 |         5 |            5 |     30
  8 | 2026-02-02 |           |            1 |     20
  9 | 2026-02-05 |         1 |            4 |     18
 10 | 2026-02-10 |         6 |            2 |     22
(10 rows)

IN dengan Subquery #

Operator IN sudah Anda kenali di chapter IN dan NOT IN dengan daftar yang ditulis manual — IN ('Kopi', 'Teh'). Versi subquery-nya menggantikan daftar manual dengan hasil query: subquery dijalankan lebih dulu, hasilnya menjadi daftar nilai, lalu WHERE menyaring baris yang nilainya terdapat di daftar itu. Syaratnya satu: subquery harus menghasilkan tepat satu kolom (jumlah baris bebas):

sql in-dasar.sql
-- IN (subquery): subquery menghasilkan daftar nilai,
-- outer query menyaring baris yang nilainya ada di daftar itu.

-- Produk yang pernah terjual: daftar produk_id dari tabel penjualan.
SELECT p.id, p.nama
FROM produk p
WHERE p.id IN (SELECT produk_id FROM penjualan)
ORDER BY p.id;

-- Subquery boleh seserius query biasa: filter tanggal di dalamnya
-- menyusutkan daftar menjadi produk yang terjual pada Januari saja.
SELECT p.id, p.nama
FROM produk p
WHERE p.id IN (SELECT produk_id
               FROM penjualan
               WHERE tanggal < DATE '2026-02-01')
ORDER BY p.id;

Output yang Dihasilkan:

text
 id |        nama        
----+--------------------
  1 | Kopi Gayo
  2 | Kopi Toraja
  3 | Teh Melati
  4 | Teh Hijau
  5 | Gula Merah
  6 | Beras Pandan Wangi
(6 rows)

 id |    nama     
----+-------------
  1 | Kopi Gayo
  2 | Kopi Toraja
  3 | Teh Melati
  4 | Teh Hijau
  5 | Gula Merah
(5 rows)

Query pertama menghasilkan enam produk — id 1 sampai 6, tepat produk yang punya catatan di tabel penjualan. Empat produk lain (Susu Kental Manis, Keripik Singkong, Kacang Goreng, Madu Hutan) tidak pernah terjual sehingga id-nya tidak ada di daftar. Query kedua memperlihatkan bahwa subquery adalah query sungguhan: filter tanggal < DATE '2026-02-01' di dalamnya memangkas daftar menjadi hanya transaksi Januari, dan Beras Pandan Wangi (id 6) pun hilang karena satu-satunya penjualannya terjadi 10 Februari. Duplikasi di daftar tidak masalah — produk_id 1 muncul tiga kali di penjualan, tetapi keanggotaan daftar tidak menghitung jumlah kemunculan.

ANY dan ALL: Membandingkan dengan Himpunan #

IN menjawab “apakah nilainya sama dengan salah satu anggota?”. ANY dan ALL memperluasnya ke operator perbandingan apa pun: > ANY, < ALL, <> ANY, dan seterusnya. Maknanya dibaca harfiah — > ANY berarti “lebih besar dari minimal satu anggota himpunan”, > ALL berarti “lebih besar dari setiap anggota himpunan”. Kata SOME adalah sinonim resmi ANY; keduanya identik:

sql any-all.sql
-- ANY dan ALL membandingkan satu nilai dengan himpunan hasil subquery.
-- Himpunan pembanding: harga ketiga produk Sembako (15000, 13000, 55000).

-- = ANY: sama dengan minimal satu anggota -- perilakunya identik dengan IN.
SELECT p.id, p.nama
FROM produk p
WHERE p.id = ANY (SELECT produk_id FROM penjualan)
ORDER BY p.id;

-- > ANY: melebihi minimal satu harga Sembako.
-- Berarti cukup melebihi harga termurah Sembako (13000.00).
SELECT p.nama, p.harga
FROM produk p
WHERE p.harga > ANY (SELECT harga FROM produk WHERE kategori = 'Sembako')
ORDER BY p.harga DESC, p.nama;

-- > ALL: melebihi semua harga Sembako.
-- Berarti harus melebihi harga termahal Sembako (55000.00).
SELECT p.nama, p.harga
FROM produk p
WHERE p.harga > ALL (SELECT harga FROM produk WHERE kategori = 'Sembako')
ORDER BY p.harga DESC, p.nama;

-- Patokan himpunan pembanding, untuk crosscheck dua query di atas.
SELECT min(harga) AS termurah, max(harga) AS termahal
FROM produk
WHERE kategori = 'Sembako';

Output yang Dihasilkan:

text
 id |        nama        
----+--------------------
  1 | Kopi Gayo
  2 | Kopi Toraja
  3 | Teh Melati
  4 | Teh Hijau
  5 | Gula Merah
  6 | Beras Pandan Wangi
(6 rows)

       nama        |  harga   
-------------------+----------
 Kopi Toraja       | 90000.00
 Kopi Gayo         | 85000.00
 Madu Hutan        | 55000.00
 Teh Hijau         | 40000.00
 Teh Melati        | 35000.00
 Kacang Goreng     | 25000.00
 Keripik Singkong  | 20000.00
 Susu Kental Manis | 18000.00
 Gula Merah        | 15000.00
(9 rows)

    nama     |  harga   
-------------+----------
 Kopi Toraja | 90000.00
 Kopi Gayo   | 85000.00
(2 rows)

 termurah | termahal 
----------+----------
 13000.00 | 55000.00
(1 row)

Query = ANY menghasilkan enam produk yang persis sama dengan IN di bagian sebelumnya — IN pada dasarnya adalah = ANY yang ditulis lebih ringkas. Query > ANY menghasilkan sembilan produk: cukup mengalahkan harga termurah Sembako (13000.00), dan hanya Beras Pandan Wangi yang gugur karena harganya tepat sama dengan batas bawah itu (13000.00 tidak lebih besar dari 13000.00). Query > ALL jauh lebih ketat — hanya Kopi Toraja dan Kopi Gayo yang melampaui harga termahal Sembako (55000.00, yang dipegang Madu Hutan). Hubungan keduanya dengan min() dan max() layak dihafalkan:

OperatorMakna harfiahSetara dengan
x > ANY (himpunan)x melebihi minimal satu anggotax > min(himpunan)
x > ALL (himpunan)x melebihi setiap anggotax > max(himpunan)
x = ANY (himpunan)x sama dengan satu anggotax IN (himpunan)
x <> ALL (himpunan)x berbeda dari semua anggotax NOT IN (himpunan)

Perhatikan baris terakhir tabel: <> ALL memang setara NOT IN — termasuk kelemahannya terhadap NULL, yang segera kita bedah.

Jebakan NOT IN dengan NULL #

Inilah babak yang dijanjikan pembuka chapter. Tujuannya sederhana: produk yang belum pernah terjual. Insting pertama kebanyakan penulis SQL adalah NOT IN atas seluruh kolom produk_id — dan insting itu menghasilkan nol baris:

-- Tujuan: produk yang BELUM PERNAH terjual.
-- Penjualan id 8 memiliki produk_id NULL (barang tanpa kode produk).

-- Versi naif: langsung NOT IN atas seluruh kolom produk_id.
-- Hasil: nol baris -- padahal empat produk memang belum terjual!
SELECT p.id, p.nama
FROM produk p
WHERE p.id NOT IN (SELECT produk_id FROM penjualan)
ORDER BY p.id;

-- Versi sadar-NULL: buang NULL dari daftar subquery lebih dulu.
SELECT p.id, p.nama
FROM produk p
WHERE p.id NOT IN (SELECT produk_id
                   FROM penjualan
                   WHERE produk_id IS NOT NULL)
ORDER BY p.id;

Output yang Dihasilkan:

text
 id | nama 
----+------
(0 rows)

 id |       nama        
----+-------------------
  7 | Susu Kental Manis
  8 | Keripik Singkong
  9 | Kacang Goreng
 10 | Madu Hutan
(4 rows)

Query pertama mengembalikan (0 rows) — bukan empat produk yang benar. Biang keladinya ada di daftar subquery yang memuat NULL dari penjualan id 8. Logika NOT IN diperluas menjadi rangkaian perbandingan: x NOT IN (1, 3, 5, NULL) berarti x <> 1 AND x <> 3 AND x <> 5 AND x <> NULL. Perbandingan terakhir selalu menghasilkan UNKNOWN — dan AND bersama UNKNOWN tidak pernah menghasilkan true, sehingga tidak ada satu pun baris yang lolos. Query kedua membetalkannya dengan memfilter NULL di dalam subquery (WHERE produk_id IS NOT NULL), dan empat produk yang benar muncul: Susu Kental Manis, Keripik Singkong, Kacang Goreng, Madu Hutan.

Subquery yang Hasilnya Kosong #

Kondisi pinggiran lain yang jarang dicoba pemula: subquery yang tidak menghasilkan satu baris pun. Dengan asumsi “belum ada transaksi tahun depan”, mari kita lihat apa yang dilakukan IN dan NOT IN terhadap daftar kosong:

-- Subquery yang hasilnya kosong (nol baris) punya logika sendiri.

-- IN atas daftar kosong: tidak ada nilai yang bisa cocok -- nol baris.
SELECT p.id, p.nama
FROM produk p
WHERE p.id IN (SELECT produk_id
               FROM penjualan
               WHERE tanggal >= DATE '2027-01-01')
ORDER BY p.id;

-- NOT IN atas daftar kosong: "tidak sama dengan apa pun dari tidak ada"
-- bernilai true untuk semua baris -- seluruh tabel lolos.
SELECT count(*) AS jumlah_baris
FROM produk
WHERE id NOT IN (SELECT produk_id
                 FROM penjualan
                 WHERE tanggal >= DATE '2027-01-01');

Output yang Dihasilkan:

text
 id | nama 
----+------
(0 rows)

 jumlah_baris 
--------------
           10
(1 row)

Hasilnya simetris dan masuk akal: IN atas daftar kosong menghasilkan nol baris karena tidak ada nilai yang bisa cocok dengan sesuatu yang tidak ada; NOT IN atas daftar kosong justru meloloskan seluruh 10 produk karena “berbeda dari setiap anggota daftar kosong” memang benar untuk nilai apa pun. Bedakan dua kondisi pinggiran ini — hasil kosong dan hasil mengandung NULL — karena nasib NOT IN di keduanya berlawanan:

Kondisi daftar subqueryINNOT IN
Daftar kosong (nol baris)nol barissemua baris lolos
Daftar mengandung NULLhanya nilai yang persis sama yang lolosnol baris

Row Constructor: Membandingkan Pasangan Kolom #

Semua contoh sejauh ini membandingkan satu kolom. SQL juga mengizinkan beberapa kolom dibandingkan sekaligus lewat row constructor: (kolom1, kolom2) IN ((nilai1, nilai2), ...) — pasangan harus cocok secara lengkap, bukan per kolom terpisah:

-- Row constructor: membandingkan beberapa kolom sekaligus
-- dengan daftar pasangan nilai.

-- Transaksi yang pasangan (produk_id, pelanggan_id)-nya
-- persis (1,1) atau (3,2).
SELECT pn.id, pn.tanggal, pn.produk_id, pn.pelanggan_id, pn.jumlah
FROM penjualan pn
WHERE (pn.produk_id, pn.pelanggan_id) IN ((1, 1), (3, 2))
ORDER BY pn.id;

-- Subquery dua kolom: transaksi yang pasangan (produk_id, pelanggan_id)-nya
-- juga muncul pada transaksi besar (jumlah >= 20).
SELECT pn.id, pn.produk_id, pn.pelanggan_id, pn.jumlah
FROM penjualan pn
WHERE (pn.produk_id, pn.pelanggan_id) IN (SELECT pn2.produk_id, pn2.pelanggan_id
                                          FROM penjualan pn2
                                          WHERE pn2.jumlah >= 20)
  AND pn.produk_id IS NOT NULL
ORDER BY pn.id;

Output yang Dihasilkan:

text
 id |  tanggal   | produk_id | pelanggan_id | jumlah 
----+------------+-----------+--------------+--------
  1 | 2026-01-05 |         1 |            1 |     10
  2 | 2026-01-08 |         3 |            2 |     25
(2 rows)

 id | produk_id | pelanggan_id | jumlah 
----+-----------+--------------+--------
  2 |         3 |            2 |     25
  3 |         5 |            1 |     40
  7 |         5 |            5 |     30
 10 |         6 |            2 |     22
(4 rows)

Query pertama menjaring dua transaksi: penjualan 1 (Kopi Gayo ke pelanggan 1) dan penjualan 2 (Teh Melati ke pelanggan 2), karena pasangan (produk_id, pelanggan_id)-nya persis (1,1) dan (3,2). Transaksi lain seperti penjualan 3 — produk 5 ke pelanggan 1 — tidak lolos walau pelanggan_id-nya cocok dengan pasangan pertama, karena pasangannya harus cocok utuh. Query kedua memakai row constructor bersubquery: daftar pasangan berasal dari transaksi bervolume besar (jumlah >= 20), lalu seluruh tabel dicek keanggotaannya terhadap daftar pasangan itu — hasilnya empat transaksi yang kombinasi produk-pelanggannya juga pernah terjadi dalam jumlah besar.

Klausa WHERE kini sanggup menyaring dengan daftar dan himpunan hasil query lain. Posisi subquery berikutnya justru membalik perannya total: dari alat penyaring menjadi sumber baris — subquery yang duduk di FROM dan diperlakukan seperti tabel. Chapter berikutnya membahasnya: Subquery dalam FROM .

Referensi #

Berkas Sumber

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

any-all.sql

Unduh berkas
sql
-- ANY dan ALL membandingkan satu nilai dengan himpunan hasil subquery.
-- Himpunan pembanding: harga ketiga produk Sembako (15000, 13000, 55000).

-- = ANY: sama dengan minimal satu anggota -- perilakunya identik dengan IN.
SELECT p.id, p.nama
FROM produk p
WHERE p.id = ANY (SELECT produk_id FROM penjualan)
ORDER BY p.id;

-- > ANY: melebihi minimal satu harga Sembako.
-- Berarti cukup melebihi harga termurah Sembako (13000.00).
SELECT p.nama, p.harga
FROM produk p
WHERE p.harga > ANY (SELECT harga FROM produk WHERE kategori = 'Sembako')
ORDER BY p.harga DESC, p.nama;

-- > ALL: melebihi semua harga Sembako.
-- Berarti harus melebihi harga termahal Sembako (55000.00).
SELECT p.nama, p.harga
FROM produk p
WHERE p.harga > ALL (SELECT harga FROM produk WHERE kategori = 'Sembako')
ORDER BY p.harga DESC, p.nama;

-- Patokan himpunan pembanding, untuk crosscheck dua query di atas.
SELECT min(harga) AS termurah, max(harga) AS termahal
FROM produk
WHERE kategori = 'Sembako';

buat-tabel.sql

Unduh berkas
sql
-- Tabel latihan chapter Subquery dalam WHERE:
-- katalog produk dan penjualan Toko Maju Jaya.
CREATE TABLE produk (
    id       integer PRIMARY KEY,
    nama     text NOT NULL,
    kategori text NOT NULL,
    harga    numeric(10,2) NOT NULL,   -- harga satuan, rupiah
    stok     integer NOT NULL
);

CREATE TABLE penjualan (
    id           integer PRIMARY KEY,
    tanggal      date NOT NULL,
    produk_id    integer REFERENCES produk (id),  -- NULL = barang tanpa kode produk
    pelanggan_id integer NOT NULL,
    jumlah       integer NOT NULL                 -- unit terjual
);

INSERT INTO produk (id, nama, kategori, harga, stok) VALUES
    ( 1, 'Kopi Gayo',          'Minuman', 85000.00,  40),
    ( 2, 'Kopi Toraja',        'Minuman', 90000.00,  25),
    ( 3, 'Teh Melati',         'Minuman', 35000.00,  60),
    ( 4, 'Teh Hijau',          'Minuman', 40000.00,  50),
    ( 5, 'Gula Merah',         'Sembako', 15000.00, 100),
    ( 6, 'Beras Pandan Wangi', 'Sembako', 13000.00, 200),
    ( 7, 'Susu Kental Manis',  'Minuman', 18000.00,  30),
    ( 8, 'Keripik Singkong',   'Camilan', 20000.00,  45),
    ( 9, 'Kacang Goreng',      'Camilan', 25000.00,  55),
    (10, 'Madu Hutan',         'Sembako', 55000.00,  15);

INSERT INTO penjualan (id, tanggal, produk_id, pelanggan_id, jumlah) VALUES
    ( 1, DATE '2026-01-05', 1,    1, 10),
    ( 2, DATE '2026-01-08', 3,    2, 25),
    ( 3, DATE '2026-01-12', 5,    1, 40),
    ( 4, DATE '2026-01-15', 2,    1,  8),
    ( 5, DATE '2026-01-20', 1,    4, 12),
    ( 6, DATE '2026-01-22', 4,    2, 15),
    ( 7, DATE '2026-01-25', 5,    5, 30),
    ( 8, DATE '2026-02-02', NULL, 1, 20),
    ( 9, DATE '2026-02-05', 1,    4, 18),
    (10, DATE '2026-02-10', 6,    2, 22);

SELECT id, nama, kategori, harga, stok FROM produk ORDER BY id;
SELECT id, tanggal, produk_id, pelanggan_id, jumlah FROM penjualan ORDER BY id;

hasil-kosong.sql

Unduh berkas
sql
-- Subquery yang hasilnya kosong (nol baris) punya logika sendiri.

-- IN atas daftar kosong: tidak ada nilai yang bisa cocok -- nol baris.
SELECT p.id, p.nama
FROM produk p
WHERE p.id IN (SELECT produk_id
               FROM penjualan
               WHERE tanggal >= DATE '2027-01-01')
ORDER BY p.id;

-- NOT IN atas daftar kosong: "tidak sama dengan apa pun dari tidak ada"
-- bernilai true untuk semua baris -- seluruh tabel lolos.
SELECT count(*) AS jumlah_baris
FROM produk
WHERE id NOT IN (SELECT produk_id
                 FROM penjualan
                 WHERE tanggal >= DATE '2027-01-01');

in-dasar.sql

Unduh berkas
sql
-- IN (subquery): subquery menghasilkan daftar nilai,
-- outer query menyaring baris yang nilainya ada di daftar itu.

-- Produk yang pernah terjual: daftar produk_id dari tabel penjualan.
SELECT p.id, p.nama
FROM produk p
WHERE p.id IN (SELECT produk_id FROM penjualan)
ORDER BY p.id;

-- Subquery boleh seserius query biasa: filter tanggal di dalamnya
-- menyusutkan daftar menjadi produk yang terjual pada Januari saja.
SELECT p.id, p.nama
FROM produk p
WHERE p.id IN (SELECT produk_id
               FROM penjualan
               WHERE tanggal < DATE '2026-02-01')
ORDER BY p.id;

not-in-jebakan.sql

Unduh berkas
sql
-- Tujuan: produk yang BELUM PERNAH terjual.
-- Penjualan id 8 memiliki produk_id NULL (barang tanpa kode produk).

-- Versi naif: langsung NOT IN atas seluruh kolom produk_id.
-- Hasil: nol baris -- padahal empat produk memang belum terjual!
SELECT p.id, p.nama
FROM produk p
WHERE p.id NOT IN (SELECT produk_id FROM penjualan)
ORDER BY p.id;

-- Versi sadar-NULL: buang NULL dari daftar subquery lebih dulu.
SELECT p.id, p.nama
FROM produk p
WHERE p.id NOT IN (SELECT produk_id
                   FROM penjualan
                   WHERE produk_id IS NOT NULL)
ORDER BY p.id;

row-constructor.sql

Unduh berkas
sql
-- Row constructor: membandingkan beberapa kolom sekaligus
-- dengan daftar pasangan nilai.

-- Transaksi yang pasangan (produk_id, pelanggan_id)-nya
-- persis (1,1) atau (3,2).
SELECT pn.id, pn.tanggal, pn.produk_id, pn.pelanggan_id, pn.jumlah
FROM penjualan pn
WHERE (pn.produk_id, pn.pelanggan_id) IN ((1, 1), (3, 2))
ORDER BY pn.id;

-- Subquery dua kolom: transaksi yang pasangan (produk_id, pelanggan_id)-nya
-- juga muncul pada transaksi besar (jumlah >= 20).
SELECT pn.id, pn.produk_id, pn.pelanggan_id, pn.jumlah
FROM penjualan pn
WHERE (pn.produk_id, pn.pelanggan_id) IN (SELECT pn2.produk_id, pn2.pelanggan_id
                                          FROM penjualan pn2
                                          WHERE pn2.jumlah >= 20)
  AND pn.produk_id IS NOT NULL
ORDER BY pn.id;