ข้ามไปยังเนื้อหา

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 ที่คุณจะเห็น
CreateINSERTเพิ่ม row ใหม่ใน tableINSERT 0 1
ReadSELECTคืน row ที่ตรงเงื่อนไข(3 rows)
UpdateUPDATEเปลี่ยน column ใน row ที่มีอยู่UPDATE 1
DeleteDELETEลบ row ออกจาก tableDELETE 1

ทุกอย่างสร้างรอบ row ใน table table คือตารางที่ตั้งชื่อของ column ที่มีชนิดคงที่ row คือหนึ่งระเบียน statement SQL อธิบาย อะไร ที่คุณต้องการ — “ให้หนังสือที่ตีพิมพ์หลังปี 2000” — และเซิร์ฟเวอร์หา วิธี เอง command tag หลังแต่ละ statement บอกว่ามันแตะกี่ row; คุณจะเห็นมันในอีกไม่นาน เมื่อมี table ให้แตะ

ไม่ว่าคุณพิมพ์ใน 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
การเดินทางไป-กลับของ SQL statement

เพื่อให้เป็นรูปธรรม ทุกบทในโมดูลนี้ใช้ชุดข้อมูลเล็กชุดเดียวซ้ำ: authors, books และ sales แต่ละบทเริ่มจาก statement ชุดเดียวกัน คุณจึงเริ่มที่บทใดก็ได้ นี่คือชุดข้อมูลที่เสร็จแล้ว บท Inserting rows สร้างมันทีละ statement:

-- sql/setup.sql
CREATE 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.sql
BEGIN;
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;
BEGIN
INSERT 0 1
id | name | born
----+-------------+------
4 | Temp Author | 2000
(1 row)
UPDATE 1
DELETE 1
ROLLBACK

table สร้างแบบเดียวกันจาก client ใดก็ได้ ตัวอย่างเช่น table authors จากแต่ละ driver รันหนึ่งครั้งกับฐานข้อมูล scratch:

CREATE TABLE authors (
id bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
name text NOT NULL,
born int
);
$ (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 ใน psql driver มักส่งหนึ่ง statement ต่อการเรียก semicolon จึงไม่บังคับที่นั่น
  • การอ่าน (SELECT) ไม่เปลี่ยนข้อมูล อีกสามคำกริยาเปลี่ยน จึงต้องระวังมากกว่า และส่งค่าเป็น placeholder ไม่เคยแปะลงในข้อความ SQL
  • “SELECT ปลอดภัยเสมอ” มันไม่เปลี่ยนข้อมูล แต่ถ้าไม่มี ORDER BY ลำดับ row ไม่ถูกกำหนด และ NULL ในการเปรียบเทียบทิ้ง row เงียบ ๆ
  • “UPDATE และ DELETE ที่ไม่มี WHERE เป็น error” มันถูกต้อง และแตะทุก row (UPDATE 4 ในบท update)
  • “id ไม่มีช่องว่าง” ค่า identity unique ไม่ใช่ไม่มีช่องว่าง: insert ที่ rollback และที่ conflict ใช้ค่าไป
คำหลัก SQL ใดตรงกับส่วน "read" ของ CRUD?
ใน PostgreSQL คุณเปลี่ยนข้อมูลที่เก็บบนดิสก์อย่างไร?
statement ใดไม่แก้ข้อมูลใน table?
command tag INSERT 0 1 บอกอะไร?
คุณส่ง UPDATE authors SET born = 0 โดยไม่มี WHERE จะเกิดอะไร?