Python adalah bahasa yang paling sering dipadukan dengan PostgreSQL untuk analisis data, backend API, sampai skrip operasional — dan psycopg adalah driver yang menjadi jembatannya. Berbeda dengan dua chapter sebelumnya yang berjalan dokumentatif, lingkungan latihan kursus ini memasang psycopg 3 sungguhan, sehingga seluruh contoh chapter ini benar-benar dieksekusi melawan PostgreSQL 17 dan output-nya direkam apa adanya. Kita akan mulai dari koneksi pertama dan pembuatan tabel produk Toko Maju Jaya dari dalam Python, lanjut ke query berparameter %s, baris berbentuk dictionary, transaksi dengan context manager, sisip massal executemany, dan ditutup dengan penanganan error berbasis SQLSTATE.

Instalasi psycopg #

psycopg 3 dipasang dari PyPI; paket psycopg[binary] membawa wheel yang sudah memuat pustaka binary sehingga tidak perlu compiler:

bash
pip install "psycopg[binary]"

Paket inti bernama psycopg (tanpa angka) dan bisa diimpor sebagai import psycopg. Versi 3 menggantikan psycopg 2 yang legendaris dengan arsitektur baru, tetapi sebagian besar pola dasarnya — connect, cursor, execute — tetap dikenali. Seluruh contoh di bawah diuji dengan psycopg 3.3.

Koneksi Pertama: Kata Kunci dan Variabel Environment #

psycopg.connect() menerima parameter koneksi sebagai kata kunci, sebagai satu string conninfo ala libpq, atau keduanya sekaligus. Contoh-contoh chapter ini memakai bentuk kata kunci dan membaca nilai dari variabel environment PG* — konvensi libpq yang sama dengan psql, sehingga kode berjalan tanpa perubahan di lingkungan latihan maupun di server Anda. File pertama membuat tabel produk dan mengisinya lewat executemany:

python buat-tabel.py
import os

import psycopg

# Parameter koneksi mengikuti variabel environment PG* -- konvensi
# libpq yang sama dengan psql. Sesuaikan untuk server Anda; kata
# sandi cukup diberikan lewat variabel environment PGPASSWORD.
KONEKSI = dict(
    host=os.environ.get("PGHOST", "localhost"),
    port=os.environ.get("PGPORT", "5432"),
    user=os.environ.get("PGUSER", "postgres"),
    dbname=os.environ.get("PGDATABASE", "toko_db"),
)

# Data awal produk Toko Maju Jaya untuk seluruh contoh chapter ini.
PRODUK_AWAL = [
    (1, "Mouse Kantor Ergonomis", 85000, 25),
    (2, "Papan Ketik Mekanik", 750000, 12),
    (3, "Monitor 24 inci IPS", 1850000, 8),
    (4, "Kabel HDMI 2 meter", 65000, 40),
]

# Blok `with` menutup koneksi di akhir; keluar tanpa error
# berarti seluruh pernyataan di dalamnya di-commit.
with psycopg.connect(**KONEKSI) as conn:
    conn.execute("DROP TABLE IF EXISTS produk")
    conn.execute(
        """
        CREATE TABLE produk (
            id    integer PRIMARY KEY,
            nama  text NOT NULL,
            harga bigint NOT NULL CHECK (harga >= 0),
            stok  integer NOT NULL DEFAULT 0
        )
        """
    )
    cur = conn.cursor()
    cur.executemany(
        "INSERT INTO produk (id, nama, harga, stok) VALUES (%s, %s, %s, %s)",
        PRODUK_AWAL,
    )
    print("Tabel produk dibuat,", len(PRODUK_AWAL), "baris terisi")

# Verifikasi: baca kembali isi tabel dari koneksi kedua.
with psycopg.connect(**KONEKSI) as conn:
    cur = conn.execute("SELECT id, nama, harga FROM produk ORDER BY id")
    for id_, nama, harga in cur.fetchall():
        print(f"  {id_} {nama} Rp{harga:,}".replace(",", "."))

Output yang Dihasilkan:

text
Tabel produk dibuat, 4 baris terisi
  1 Mouse Kantor Ergonomis Rp85.000
  2 Papan Ketik Mekanik Rp750.000
  3 Monitor 24 inci IPS Rp1.850.000
  4 Kabel HDMI 2 meter Rp65.000

Mari kita bedah bagian-bagian pentingnya:

  1. KONEKSI = dict(...) — kata sandi sengaja tidak ditulis di kode; psycopg membacanya dari PGPASSWORD bila variabel itu ada, sama seperti psql.
  2. with psycopg.connect(...) as conn: — psycopg membuka transaksi otomatis pada pernyataan pertama; keluar dari blok tanpa exception menjalankan commit(), keluar karena exception menjalankan rollback() lalu menutup koneksi. DDL seperti CREATE TABLE ikut menikmati jaminan itu.
  3. conn.execute(...) — pintasan untuk kursor sekali pakai; psycopg 3 membuat cursor implisit dan mengeksekusinya langsung.
  4. cur.executemany(...) — satu template SQL dengan %s, dijalankan berulang untuk setiap elemen PRODUK_AWAL; seluruh baris masuk dalam satu transaksi.
  5. Blok kedua (with kedua) — koneksi baru terpisah; baris yang terbaca membuktikan transaksi blok pertama benar-benar ter-commit.

Query dengan Parameter %s #

Placeolder psycopg adalah %s, bukan ? ala SQLite atau $1 ala PostgreSQL murni — psycopg menerjemahkan %s menjadi parameter bernomor $n saat berbicara dengan server, sehingga mekanisme pemisahan struktur-dan-datanya tetap yang sama. Nilai parameter dikirim sebagai tuple di argumen kedua execute:

python query-dasar.py
import os

import psycopg

# Parameter koneksi mengikuti variabel environment PG* --
# konvensi libpq yang sama dengan psql.
KONEKSI = dict(
    host=os.environ.get("PGHOST", "localhost"),
    port=os.environ.get("PGPORT", "5432"),
    user=os.environ.get("PGUSER", "postgres"),
    dbname=os.environ.get("PGDATABASE", "toko_db"),
)

with psycopg.connect(**KONEKSI) as conn:
    # 1) Parameter %s: nilai dikirim terpisah dari teks SQL.
    cur = conn.execute(
        "SELECT id, nama, harga FROM produk WHERE harga <= %s ORDER BY id",
        (200000,),
    )
    print("Produk dengan harga di bawah Rp200.000:")
    for id_, nama, harga in cur.fetchall():
        print(f"  {id_}. {nama} Rp{harga:,}".replace(",", "."))

    # 2) fetchone: mengambil satu baris hasil, cocok untuk count.
    cur = conn.execute("SELECT count(*) FROM produk")
    print("Jumlah produk:", cur.fetchone()[0])

    # 3) Iterasi langsung atas cursor: baris dibaca satu per satu.
    cur = conn.execute(
        "SELECT nama FROM produk WHERE stok >= %s ORDER BY id", (20,)
    )
    print("Produk dengan stok >= 20:")
    for (nama,) in cur:
        print(" ", nama)

Output yang Dihasilkan:

text
Produk dengan harga di bawah Rp200.000:
  1. Mouse Kantor Ergonomis Rp85.000
  4. Kabel HDMI 2 meter Rp65.000
Jumlah produk: 4
Produk dengan stok >= 20:
  Mouse Kantor Ergonomis
  Kabel HDMI 2 meter

Tiga cara membaca hasil tampil di atas: fetchall() mengambil seluruh baris ke memori sekaligus, fetchone() mengambil satu baris (kolom tunggal count(*) dibaca lewat indeks [0]), dan iterasi langsung atas cursor membaca baris satu per satu — pilihan tepat untuk hasil besar karena baris tidak menumpuk di memori aplikasi. Perhatikan juga tuple satu elemen ditulis (200000,) dengan koma; tanpa koma, Python membacanya sebagai ekspresi biasa, bukan tuple.

Baris sebagai Dictionary: dict_row #

Secara default setiap baris berupa tuple posisional. Kolom diakses lewat indeks bisa salah baca saat query diubah; untuk itu psycopg menyediakan row factory, dan dict_row mengubah hasil menjadi dictionary yang kuncinya nama kolom:

python dict-row.py
import os

import psycopg
from psycopg.rows import dict_row

# Parameter koneksi mengikuti variabel environment PG* --
# konvensi libpq yang sama dengan psql.
KONEKSI = dict(
    host=os.environ.get("PGHOST", "localhost"),
    port=os.environ.get("PGPORT", "5432"),
    user=os.environ.get("PGUSER", "postgres"),
    dbname=os.environ.get("PGDATABASE", "toko_db"),
)

# row_factory=dict_row: setiap baris berupa dictionary {kolom: nilai},
# bukan tuple posisional. Kolom dipanggil lewat namanya.
with psycopg.connect(**KONEKSI, row_factory=dict_row) as conn:
    cur = conn.execute(
        "SELECT nama, harga, stok FROM produk WHERE stok < %s ORDER BY id",
        (20,),
    )
    print("Produk dengan stok di bawah 20 (dictionary):")
    for baris in cur.fetchall():
        teks = f"  {baris['nama']}: Rp{baris['harga']:,} (stok {baris['stok']})"
        print(teks.replace(",", "."))

Output yang Dihasilkan:

text
Produk dengan stok di bawah 20 (dictionary):
  Papan Ketik Mekanik: Rp750.000 (stok 12)
  Monitor 24 inci IPS: Rp1.850.000 (stok 8)

Parameter row_factory dipasang saat koneksi dibuat sehingga seluruh cursor mewarisinya; psycopg juga menyediakan namedtuple_row untuk akses baris.nama yang lebih hemat ketikan. Ketika query hanya memilih beberapa kolom dari join kompleks, akses bernama seperti baris['nama'] jauh lebih tahan salah urutan daripada baris[0].

Transaksi dan Context Manager #

psycopg tidak punya perintah “mulai transaksi” yang harus dipanggil manual: transaksi dimulai otomatis pada pernyataan pertama, dan conn.commit() atau conn.rollback() menutupnya. Context manager with membungkus siklus itu — inilah perbedaan paling terasa dibanding psycopg 2. File berikut menjalankan dua transaksi: yang pertama sukses dan ter-commit, yang kedua gagal di tengah jalan dan dibatalkan seluruhnya:

python transaksi.py
import os

import psycopg
from psycopg import errors

# Parameter koneksi mengikuti variabel environment PG* --
# konvensi libpq yang sama dengan psql.
KONEKSI = dict(
    host=os.environ.get("PGHOST", "localhost"),
    port=os.environ.get("PGPORT", "5432"),
    user=os.environ.get("PGUSER", "postgres"),
    dbname=os.environ.get("PGDATABASE", "toko_db"),
)

# Transaksi 1: dua perubahan stok yang harus terjadi bersama-sama
# (pindahan barang dari produk 2 ke produk 1).
with psycopg.connect(**KONEKSI) as conn:
    conn.execute("UPDATE produk SET stok = stok - 2 WHERE id = 2")
    conn.execute("UPDATE produk SET stok = stok + 2 WHERE id = 1")
    # Keluar dari blok `with` tanpa error -> commit otomatis.
print("Transaksi 1 di-commit: stok produk 1 dan 2 berubah bersamaan")

# Transaksi 2: error di tengah jalan. Exception lolos dari blok
# `with` -> psycopg menjalankan rollback sebelum menutup koneksi,
# sehingga UPDATE pertama ikut dibatalkan.
try:
    with psycopg.connect(**KONEKSI) as conn:
        conn.execute("UPDATE produk SET stok = stok - 5 WHERE id = 3")
        conn.execute(
            "INSERT INTO produk (id, nama, harga, stok)"
            " VALUES (%s, %s, %s, %s)",
            (1, "Produk Duplikat", 100000, 1),
        )
except errors.UniqueViolation:
    print("Transaksi 2 dibatalkan: INSERT duplikat, UPDATE ikut batal")

# Bukti akhir: hanya perubahan transaksi 1 yang tersimpan.
with psycopg.connect(**KONEKSI) as conn:
    cur = conn.execute(
        "SELECT id, stok FROM produk WHERE id IN (1, 2, 3) ORDER BY id"
    )
    print("Stok akhir (id, stok):", cur.fetchall())

Output yang Dihasilkan:

text
Transaksi 1 di-commit: stok produk 1 dan 2 berubah bersamaan
Transaksi 2 dibatalkan: INSERT duplikat, UPDATE ikut batal
Stok akhir (id, stok): [(1, 27), (2, 10), (3, 8)]

Baca outputnya baris demi baris. Transaksi 1 memindahkan 2 unit stok: produk 1 naik dari 25 ke 27, produk 2 turun dari 12 ke 10 — keduanya terjadi bersama-sama. Transaksi 2 gagal karena INSERT memakai id = 1 yang sudah ada; UPDATE yang menurunkan stok produk 3 sebelumnya ikut dibatalkan, terbukti dari stok produk 3 yang tetap 8. Pola try/except di sekeliling blok with bukan hiasan: exception harus tertangkap agar program bisa melanjutkan, sementara rollback-nya sudah ditangani context manager.

Sisip Massal dengan executemany #

Memasukkan banyak baris dengan loop execute satu-satu berarti bolak-balik pesan ke server. executemany mengirim satu template untuk seluruh daftar baris dalam satu transaksi:

python executemany.py
import os

import psycopg

# Parameter koneksi mengikuti variabel environment PG* --
# konvensi libpq yang sama dengan psql.
KONEKSI = dict(
    host=os.environ.get("PGHOST", "localhost"),
    port=os.environ.get("PGPORT", "5432"),
    user=os.environ.get("PGUSER", "postgres"),
    dbname=os.environ.get("PGDATABASE", "toko_db"),
)

# Pengiriman baru Toko Maju Jaya: satu daftar baris, satu panggilan
# executemany -- semuanya berjalan dalam satu transaksi.
PRODUK_BARU = [
    (81, "Mouse Wireless Senyap", 185000, 15),
    (82, "Webcam 1080p", 420000, 6),
    (83, "Headset dengan Mikrofon", 350000, 9),
    (84, "Lampu Meja LED", 120000, 20),
]

with psycopg.connect(**KONEKSI) as conn:
    cur = conn.cursor()
    cur.executemany(
        "INSERT INTO produk (id, nama, harga, stok) VALUES (%s, %s, %s, %s)",
        PRODUK_BARU,
    )
    print("executemany memasukkan", len(PRODUK_BARU), "baris dalam satu transaksi")

    cur.execute("SELECT id, nama FROM produk WHERE id >= 80 ORDER BY id")
    print("Isi tabel sekarang (id >= 80):")
    for id_, nama in cur.fetchall():
        print(f"  {id_} {nama}")

Output yang Dihasilkan:

text
executemany memasukkan 4 baris dalam satu transaksi
Isi tabel sekarang (id >= 80):
  81 Mouse Wireless Senyap
  82 Webcam 1080p
  83 Headset dengan Mikrofon
  84 Lampu Meja LED

Menangani Error: errors dan SQLSTATE #

Setiap galat dari server tiba sebagai exception di modul psycopg.errors, satu kelas per SQLSTATE — UniqueViolation untuk 23505, ForeignKeyViolation untuk 23503, dan seterusnya. Objek error membawa sqlstate serta diag (pesan detail, nama constraint) untuk keputusan yang presisi:

python error-handling.py
import os

import psycopg
from psycopg import errors

# Parameter koneksi mengikuti variabel environment PG* --
# konvensi libpq yang sama dengan psql.
KONEKSI = dict(
    host=os.environ.get("PGHOST", "localhost"),
    port=os.environ.get("PGPORT", "5432"),
    user=os.environ.get("PGUSER", "postgres"),
    dbname=os.environ.get("PGDATABASE", "toko_db"),
)

with psycopg.connect(**KONEKSI) as conn:
    cur = conn.cursor()

    # INSERT pertama: sah, masih dalam transaksi yang berjalan.
    cur.execute(
        "INSERT INTO produk (id, nama, harga, stok) VALUES (%s, %s, %s, %s)",
        (90, "Docking Station USB-C", 950000, 5),
    )
    print("INSERT id=90: sukses")

    # INSERT kedua memakai id yang sama: server menolak, psycopg
    # menerjemahkan SQLSTATE 23505 menjadi errors.UniqueViolation.
    try:
        cur.execute(
            "INSERT INTO produk (id, nama, harga, stok) VALUES (%s, %s, %s, %s)",
            (90, "Docking Station USB-C", 950000, 5),
        )
    except errors.UniqueViolation as e:
        print("INSERT ulang id=90: ditolak server")
        print("  sqlstate  :", e.sqlstate)
        print("  pesan     :", e.diag.message_primary)
        print("  constraint:", e.diag.constraint_name)
        # rollback membatalkan transaksi (termasuk INSERT id=90
        # yang tadinya sukses) dan mengembalikan koneksi ke kondisi
        # sehat sehingga bisa dipakai untuk transaksi baru.
        conn.rollback()

    # Transaksi baru pada koneksi yang sama.
    cur.execute(
        "INSERT INTO produk (id, nama, harga, stok) VALUES (%s, %s, %s, %s)",
        (91, "Mouse Pad Besar", 95000, 30),
    )
    print("Setelah rollback, transaksi baru berjalan normal")

# Verifikasi: hanya baris dari transaksi kedua yang tersimpan.
with psycopg.connect(**KONEKSI) as conn:
    cur = conn.execute("SELECT id, nama FROM produk WHERE id >= 90 ORDER BY id")
    print("Baris dengan id >= 90 yang tersimpan:")
    for id_, nama in cur.fetchall():
        print(f"  {id_} {nama}")

# lookup: mencari kelas error dari kode SQLSTATE, berguna saat
# kode error baru saja diketahui dari log server.
for kode in ("23505", "23503"):
    print(f"errors.lookup({kode!r}) ->", errors.lookup(kode).__name__)

Output yang Dihasilkan:

text
INSERT id=90: sukses
INSERT ulang id=90: ditolak server
  sqlstate  : 23505
  pesan     : duplicate key value violates unique constraint "produk_pkey"
  constraint: produk_pkey
Setelah rollback, transaksi baru berjalan normal
Baris dengan id >= 90 yang tersimpan:
  91 Mouse Pad Besar
errors.lookup('23505') -> UniqueViolation
errors.lookup('23503') -> ForeignKeyViolation

Ada dua pelajaran penting di output itu. Pertama, INSERT id=90 yang tadinya sukses ikut lenyap — rollback membatalkan seluruh transaksi, bukan hanya pernyataan yang gagal; itulah arti atomicty yang dibahas di chapter transaksi. Kedua, setelah rollback() koneksi kembali sehat dan transaksi baru berjalan normal pada koneksi yang sama — tanpa rollback, setiap pernyataan berikutnya akan ditolak dengan pesan current transaction is aborted sampai transaksi itu ditutup. Fungsi errors.lookup() bekerja ke arah sebaliknya: dari kode SQLSTATE yang terbaca di log, temukan kelasnya untuk ditangkap.

Python dengan psycopg kini tersambung penuh ke PostgreSQL. Berikutnya bahasa JavaScript di sisi server: Node.js dengan paket node-postgres — parameter $1 yang itu, transaksi manual BEGIN/COMMIT, dan object error yang membawa SQLSTATE yang sama.

Referensi #

Berkas Sumber

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

buat-tabel.py

Unduh berkas
python
import os

import psycopg

# Parameter koneksi mengikuti variabel environment PG* -- konvensi
# libpq yang sama dengan psql. Sesuaikan untuk server Anda; kata
# sandi cukup diberikan lewat variabel environment PGPASSWORD.
KONEKSI = dict(
    host=os.environ.get("PGHOST", "localhost"),
    port=os.environ.get("PGPORT", "5432"),
    user=os.environ.get("PGUSER", "postgres"),
    dbname=os.environ.get("PGDATABASE", "toko_db"),
)

# Data awal produk Toko Maju Jaya untuk seluruh contoh chapter ini.
PRODUK_AWAL = [
    (1, "Mouse Kantor Ergonomis", 85000, 25),
    (2, "Papan Ketik Mekanik", 750000, 12),
    (3, "Monitor 24 inci IPS", 1850000, 8),
    (4, "Kabel HDMI 2 meter", 65000, 40),
]

# Blok `with` menutup koneksi di akhir; keluar tanpa error
# berarti seluruh pernyataan di dalamnya di-commit.
with psycopg.connect(**KONEKSI) as conn:
    conn.execute("DROP TABLE IF EXISTS produk")
    conn.execute(
        """
        CREATE TABLE produk (
            id    integer PRIMARY KEY,
            nama  text NOT NULL,
            harga bigint NOT NULL CHECK (harga >= 0),
            stok  integer NOT NULL DEFAULT 0
        )
        """
    )
    cur = conn.cursor()
    cur.executemany(
        "INSERT INTO produk (id, nama, harga, stok) VALUES (%s, %s, %s, %s)",
        PRODUK_AWAL,
    )
    print("Tabel produk dibuat,", len(PRODUK_AWAL), "baris terisi")

# Verifikasi: baca kembali isi tabel dari koneksi kedua.
with psycopg.connect(**KONEKSI) as conn:
    cur = conn.execute("SELECT id, nama, harga FROM produk ORDER BY id")
    for id_, nama, harga in cur.fetchall():
        print(f"  {id_} {nama} Rp{harga:,}".replace(",", "."))

dict-row.py

Unduh berkas
python
import os

import psycopg
from psycopg.rows import dict_row

# Parameter koneksi mengikuti variabel environment PG* --
# konvensi libpq yang sama dengan psql.
KONEKSI = dict(
    host=os.environ.get("PGHOST", "localhost"),
    port=os.environ.get("PGPORT", "5432"),
    user=os.environ.get("PGUSER", "postgres"),
    dbname=os.environ.get("PGDATABASE", "toko_db"),
)

# row_factory=dict_row: setiap baris berupa dictionary {kolom: nilai},
# bukan tuple posisional. Kolom dipanggil lewat namanya.
with psycopg.connect(**KONEKSI, row_factory=dict_row) as conn:
    cur = conn.execute(
        "SELECT nama, harga, stok FROM produk WHERE stok < %s ORDER BY id",
        (20,),
    )
    print("Produk dengan stok di bawah 20 (dictionary):")
    for baris in cur.fetchall():
        teks = f"  {baris['nama']}: Rp{baris['harga']:,} (stok {baris['stok']})"
        print(teks.replace(",", "."))

error-handling.py

Unduh berkas
python
import os

import psycopg
from psycopg import errors

# Parameter koneksi mengikuti variabel environment PG* --
# konvensi libpq yang sama dengan psql.
KONEKSI = dict(
    host=os.environ.get("PGHOST", "localhost"),
    port=os.environ.get("PGPORT", "5432"),
    user=os.environ.get("PGUSER", "postgres"),
    dbname=os.environ.get("PGDATABASE", "toko_db"),
)

with psycopg.connect(**KONEKSI) as conn:
    cur = conn.cursor()

    # INSERT pertama: sah, masih dalam transaksi yang berjalan.
    cur.execute(
        "INSERT INTO produk (id, nama, harga, stok) VALUES (%s, %s, %s, %s)",
        (90, "Docking Station USB-C", 950000, 5),
    )
    print("INSERT id=90: sukses")

    # INSERT kedua memakai id yang sama: server menolak, psycopg
    # menerjemahkan SQLSTATE 23505 menjadi errors.UniqueViolation.
    try:
        cur.execute(
            "INSERT INTO produk (id, nama, harga, stok) VALUES (%s, %s, %s, %s)",
            (90, "Docking Station USB-C", 950000, 5),
        )
    except errors.UniqueViolation as e:
        print("INSERT ulang id=90: ditolak server")
        print("  sqlstate  :", e.sqlstate)
        print("  pesan     :", e.diag.message_primary)
        print("  constraint:", e.diag.constraint_name)
        # rollback membatalkan transaksi (termasuk INSERT id=90
        # yang tadinya sukses) dan mengembalikan koneksi ke kondisi
        # sehat sehingga bisa dipakai untuk transaksi baru.
        conn.rollback()

    # Transaksi baru pada koneksi yang sama.
    cur.execute(
        "INSERT INTO produk (id, nama, harga, stok) VALUES (%s, %s, %s, %s)",
        (91, "Mouse Pad Besar", 95000, 30),
    )
    print("Setelah rollback, transaksi baru berjalan normal")

# Verifikasi: hanya baris dari transaksi kedua yang tersimpan.
with psycopg.connect(**KONEKSI) as conn:
    cur = conn.execute("SELECT id, nama FROM produk WHERE id >= 90 ORDER BY id")
    print("Baris dengan id >= 90 yang tersimpan:")
    for id_, nama in cur.fetchall():
        print(f"  {id_} {nama}")

# lookup: mencari kelas error dari kode SQLSTATE, berguna saat
# kode error baru saja diketahui dari log server.
for kode in ("23505", "23503"):
    print(f"errors.lookup({kode!r}) ->", errors.lookup(kode).__name__)

executemany.py

Unduh berkas
python
import os

import psycopg

# Parameter koneksi mengikuti variabel environment PG* --
# konvensi libpq yang sama dengan psql.
KONEKSI = dict(
    host=os.environ.get("PGHOST", "localhost"),
    port=os.environ.get("PGPORT", "5432"),
    user=os.environ.get("PGUSER", "postgres"),
    dbname=os.environ.get("PGDATABASE", "toko_db"),
)

# Pengiriman baru Toko Maju Jaya: satu daftar baris, satu panggilan
# executemany -- semuanya berjalan dalam satu transaksi.
PRODUK_BARU = [
    (81, "Mouse Wireless Senyap", 185000, 15),
    (82, "Webcam 1080p", 420000, 6),
    (83, "Headset dengan Mikrofon", 350000, 9),
    (84, "Lampu Meja LED", 120000, 20),
]

with psycopg.connect(**KONEKSI) as conn:
    cur = conn.cursor()
    cur.executemany(
        "INSERT INTO produk (id, nama, harga, stok) VALUES (%s, %s, %s, %s)",
        PRODUK_BARU,
    )
    print("executemany memasukkan", len(PRODUK_BARU), "baris dalam satu transaksi")

    cur.execute("SELECT id, nama FROM produk WHERE id >= 80 ORDER BY id")
    print("Isi tabel sekarang (id >= 80):")
    for id_, nama in cur.fetchall():
        print(f"  {id_} {nama}")

query-dasar.py

Unduh berkas
python
import os

import psycopg

# Parameter koneksi mengikuti variabel environment PG* --
# konvensi libpq yang sama dengan psql.
KONEKSI = dict(
    host=os.environ.get("PGHOST", "localhost"),
    port=os.environ.get("PGPORT", "5432"),
    user=os.environ.get("PGUSER", "postgres"),
    dbname=os.environ.get("PGDATABASE", "toko_db"),
)

with psycopg.connect(**KONEKSI) as conn:
    # 1) Parameter %s: nilai dikirim terpisah dari teks SQL.
    cur = conn.execute(
        "SELECT id, nama, harga FROM produk WHERE harga <= %s ORDER BY id",
        (200000,),
    )
    print("Produk dengan harga di bawah Rp200.000:")
    for id_, nama, harga in cur.fetchall():
        print(f"  {id_}. {nama} Rp{harga:,}".replace(",", "."))

    # 2) fetchone: mengambil satu baris hasil, cocok untuk count.
    cur = conn.execute("SELECT count(*) FROM produk")
    print("Jumlah produk:", cur.fetchone()[0])

    # 3) Iterasi langsung atas cursor: baris dibaca satu per satu.
    cur = conn.execute(
        "SELECT nama FROM produk WHERE stok >= %s ORDER BY id", (20,)
    )
    print("Produk dengan stok >= 20:")
    for (nama,) in cur:
        print(" ", nama)

transaksi.py

Unduh berkas
python
import os

import psycopg
from psycopg import errors

# Parameter koneksi mengikuti variabel environment PG* --
# konvensi libpq yang sama dengan psql.
KONEKSI = dict(
    host=os.environ.get("PGHOST", "localhost"),
    port=os.environ.get("PGPORT", "5432"),
    user=os.environ.get("PGUSER", "postgres"),
    dbname=os.environ.get("PGDATABASE", "toko_db"),
)

# Transaksi 1: dua perubahan stok yang harus terjadi bersama-sama
# (pindahan barang dari produk 2 ke produk 1).
with psycopg.connect(**KONEKSI) as conn:
    conn.execute("UPDATE produk SET stok = stok - 2 WHERE id = 2")
    conn.execute("UPDATE produk SET stok = stok + 2 WHERE id = 1")
    # Keluar dari blok `with` tanpa error -> commit otomatis.
print("Transaksi 1 di-commit: stok produk 1 dan 2 berubah bersamaan")

# Transaksi 2: error di tengah jalan. Exception lolos dari blok
# `with` -> psycopg menjalankan rollback sebelum menutup koneksi,
# sehingga UPDATE pertama ikut dibatalkan.
try:
    with psycopg.connect(**KONEKSI) as conn:
        conn.execute("UPDATE produk SET stok = stok - 5 WHERE id = 3")
        conn.execute(
            "INSERT INTO produk (id, nama, harga, stok)"
            " VALUES (%s, %s, %s, %s)",
            (1, "Produk Duplikat", 100000, 1),
        )
except errors.UniqueViolation:
    print("Transaksi 2 dibatalkan: INSERT duplikat, UPDATE ikut batal")

# Bukti akhir: hanya perubahan transaksi 1 yang tersimpan.
with psycopg.connect(**KONEKSI) as conn:
    cur = conn.execute(
        "SELECT id, stok FROM produk WHERE id IN (1, 2, 3) ORDER BY id"
    )
    print("Stok akhir (id, stok):", cur.fetchall())