CRUD & SQL
ฐานข้อมูลมีประโยชน์เมื่อคุณใส่ข้อมูลเข้า ดึงกลับออก เปลี่ยน และลบมันได้ ใน PostgreSQL คุณทำทั้งหมดนั้นด้วยการส่ง SQL — ภาษาข้อความที่เซิร์ฟเวอร์เข้าใจ คุณไม่เคยแก้ไฟล์ table ด้วยมือ แต่เขียน statement เซิร์ฟเวอร์ parse รัน และคืนจำนวน row ที่ได้รับผลกระทบหรือชุดผลลัพธ์
statement SQL เกือบทุกตัวที่คุณเขียนแมปกับหนึ่งในสี่แนวคิด เรียกรวมกันว่า CRUD: create, read, update และ delete โมดูลนี้พาเดินทีละอันด้วยตัวอย่างที่วางลง psql ได้เลย พร้อมการดำเนินการเดียวกันใน driver ยอดนิยมสี่ตัวเพื่อให้พกความรู้ไปใช้ในโค้ดแอปจริง ทุกอย่างในนั้นรันจริงบน PostgreSQL 18.6 (postgres:18.6, aarch64, Docker Desktop บน Mac, กันยายน 2026) driver คือ pg 8.23.0 (Node.js), psycopg 3.3.6 (Python), pgx v5.11.0 (Go) และ sqlx 0.9.0 (Rust)
สี่คำกริยา
หัวข้อที่มีชื่อว่า “สี่คำกริยา”ตัวย่อ CRUD ตรงกับสี่คำหลักของ SQL เรียนสิ่งเหล่านี้แล้วคุณก็ทำงานฐานข้อมูลประจำวันได้ส่วนใหญ่
| การกระทำ CRUD | คำหลัก SQL | ทำอะไร | Command tag ที่คุณจะเห็น |
|---|---|---|---|
| Create | INSERT | เพิ่ม row ใหม่ใน table | INSERT 0 1 |
| Read | SELECT | คืน row ที่ตรงเงื่อนไข | (3 rows) |
| Update | UPDATE | เปลี่ยน column ใน row ที่มีอยู่ | UPDATE 1 |
| Delete | DELETE | ลบ row ออกจาก table | DELETE 1 |
ทุกอย่างสร้างรอบ row ใน table table คือตารางที่ตั้งชื่อของ column ที่มีชนิดคงที่ row คือหนึ่งระเบียน statement SQL อธิบาย อะไร ที่คุณต้องการ — “ให้หนังสือที่ตีพิมพ์หลังปี 2000” — และเซิร์ฟเวอร์หา วิธี เอง command tag หลังแต่ละ statement บอกว่ามันแตะกี่ row; คุณจะเห็นมันในอีกไม่นาน เมื่อมี table ให้แตะ
statement เดินทางอย่างไร
หัวข้อที่มีชื่อว่า “statement เดินทางอย่างไร”ไม่ว่าคุณพิมพ์ใน psql หรือเรียก driver จากแอป เส้นทางเหมือนกัน: โค้ดของคุณส่ง string SQL ให้เซิร์ฟเวอร์ เซิร์ฟเวอร์ทำงานกับ row ที่เก็บไว้ และคำตอบกลับมา
flowchart LR A[Your app or psql] -->|SQL text| B[PostgreSQL server] B -->|reads and writes| C[(Table rows on disk)] C -->|result set or row count| B B -->|reply| A
ชุดข้อมูลตัวอย่าง
หัวข้อที่มีชื่อว่า “ชุดข้อมูลตัวอย่าง”เพื่อให้เป็นรูปธรรม ทุกบทในโมดูลนี้ใช้ชุดข้อมูลเล็กชุดเดียวซ้ำ: authors, books และ sales แต่ละบทเริ่มจาก statement ชุดเดียวกัน คุณจึงเริ่มที่บทใดก็ได้ นี่คือชุดข้อมูลที่เสร็จแล้ว บท Inserting rows สร้างมันทีละ statement:
-- sql/setup.sqlCREATE TABLE authors ( id bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY, name text NOT NULL, born int);CREATE TABLE books ( id bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY, title text NOT NULL, author_id bigint REFERENCES authors (id), published int, copies_sold int NOT NULL DEFAULT 0);CREATE TABLE sales ( id bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY, book_id bigint REFERENCES books (id), region text NOT NULL, copies int NOT NULL);INSERT INTO authors (name, born) VALUES ('Mara Linde', 1968), ('Owen Pryce', 1975), ('Sela Vance', 1990);INSERT INTO books (title, author_id, published, copies_sold) VALUES ('Quiet Harbors', 1, 2003, 450), ('The Glass Orchard', 1, 2018, 1250), ('Tidal Notes', 2, 2011, 600), ('Unlinked Draft', NULL, 2020, 0);INSERT INTO sales (book_id, region, copies) VALUES (2, 'East', 900), (3, 'West', 400), (1, 'West', 400), (3, 'East', 200), (2, 'West', 150), (1, 'East', 50);column identity (GENERATED ALWAYS AS IDENTITY) แจก id, books.author_id เป็น foreign key ที่เป็น NULL ได้ (Unlinked Draft ไม่มีผู้แต่ง) และ sales มีหกแถวใน 2 ภูมิภาค ทีนี้สี่คำกริยากับ author ชั่วคราว rollback เพื่อให้ชุดข้อมูลเหมือนเดิม สังเกต command tag: INSERT 0 1, UPDATE 1, DELETE 1 และ SELECT รายงาน row ของมันเป็น (1 row):
-- sql/four-verbs.sqlBEGIN;INSERT INTO authors (name, born) VALUES ('Temp Author', 2000);SELECT id, name, born FROM authors WHERE name = 'Temp Author';UPDATE authors SET born = 2001 WHERE name = 'Temp Author';DELETE FROM authors WHERE name = 'Temp Author';ROLLBACK;BEGININSERT 0 1 id | name | born----+-------------+------ 4 | Temp Author | 2000(1 row)
UPDATE 1DELETE 1ROLLBACKtable สร้างแบบเดียวกันจาก client ใดก็ได้ ตัวอย่างเช่น table authors จากแต่ละ driver รันหนึ่งครั้งกับฐานข้อมูล scratch:
CREATE TABLE authors ( id bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY, name text NOT NULL, born int);await pool.query(` CREATE TABLE authors ( id bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY, name text NOT NULL, born int )`);cur.execute(""" CREATE TABLE authors ( id bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY, name text NOT NULL, born int )""")_, err := conn.Exec(ctx, ` CREATE TABLE authors ( id bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY, name text NOT NULL, born int )`)return errsqlx::query( "CREATE TABLE authors ( id bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY, name text NOT NULL, born int )",).execute(&pool).await?;$ (each snippet run once; the scratch database is reset before every run)[CREATE TABLE] Node.js (pg 8.23.0): authors exists: true[CREATE TABLE] Python (psycopg 3.3.6): authors exists: true[CREATE TABLE] Go (pgx v5.11.0): authors exists: true[CREATE TABLE] Rust (sqlx 0.9.0): authors exists: trueใน pgAdmin: เปิด Query Tool บนฐานข้อมูลของคุณ วาง statement CREATE TABLE และกดปุ่มรัน คุณจะเห็น table ใหม่ปรากฏใต้ Schemas แล้ว Tables ใน browser tree ทางซ้าย
โมดูลนี้ครอบคลุมอะไร
หัวข้อที่มีชื่อว่า “โมดูลนี้ครอบคลุมอะไร”- Insert — เพิ่ม row ทีละแถวหรือเป็นชุด ให้ column ใช้ค่า default รับ id ที่สร้างกลับด้วย
RETURNINGและสิ่งที่ batching ให้ (50 000 row ใช้ 6.6 วินาทีทีละ statement, 34 ms ใน statement หลาย row เดียว, 15 ms ด้วยCOPY) - Select และ filtering — เลือก column, กรองด้วย
WHERE, การจัดการNULLที่ทำให้ทุกคนแปลกใจสักครั้ง, การเรียงลำดับ (และ collation เปลี่ยนลำดับอย่างไร) และทำไมLIMITต้องมีORDER BY - Update และ delete — เปลี่ยนและลบ row อย่างปลอดภัยด้วย
RETURNING,WHEREที่หายและเงื่อนไข join ที่หายที่แตะทุก row และ row ตายที่ทั้งสอง statement ทิ้งไว้ - Upsert — insert row หรือ update ถ้ามีอยู่แล้ว ใน statement atomic เดียว; check-then-insert แพ้ race และได้สอง row,
ON CONFLICTไม่
เคล็ดลับและข้อควรระวัง
หัวข้อที่มีชื่อว่า “เคล็ดลับและข้อควรระวัง”- คำหลัก SQL ไม่แยกตัวพิมพ์ (
selectเท่ากับSELECT) แต่คำหลักตัวพิมพ์ใหญ่เป็นธรรมเนียมเพื่อความอ่านง่ายที่ใช้ตลอดคอร์สนี้ - identifier เช่นชื่อ table และ column ถูกพับเป็นตัวพิมพ์เล็กเว้นแต่คุณครอบด้วย double quote ดังนั้น
Authorsกับauthorsหมายถึง table เดียวกัน - statement จบด้วย semicolon ใน
psqldriver มักส่งหนึ่ง statement ต่อการเรียก semicolon จึงไม่บังคับที่นั่น - การอ่าน (
SELECT) ไม่เปลี่ยนข้อมูล อีกสามคำกริยาเปลี่ยน จึงต้องระวังมากกว่า และส่งค่าเป็น placeholder ไม่เคยแปะลงในข้อความ SQL
Common Misconceptions
หัวข้อที่มีชื่อว่า “Common Misconceptions”- “
SELECTปลอดภัยเสมอ” มันไม่เปลี่ยนข้อมูล แต่ถ้าไม่มีORDER BYลำดับ row ไม่ถูกกำหนด และNULLในการเปรียบเทียบทิ้ง row เงียบ ๆ - “
UPDATEและDELETEที่ไม่มีWHEREเป็น error” มันถูกต้อง และแตะทุก row (UPDATE 4ในบท update) - “id ไม่มีช่องว่าง” ค่า identity unique ไม่ใช่ไม่มีช่องว่าง: insert ที่ rollback และที่ conflict ใช้ค่าไป