Chương 01 · Foundations

Database Fundamentals & Relational Model

Tại sao chúng ta cần Database? Mô hình quan hệ của Codd, các loại key, constraint, data type — và kiểu NULL khó nhằn.

1. Vì sao có Database?

Hãy tưởng tượng bạn xây 1 app to-do list cho riêng mình. Bạn lưu mọi task vào file tasks.json:

[
  { "id": 1, "title": "Học SQL", "done": false },
  { "id": 2, "title": "Đi siêu thị", "done": true }
]

App chạy ngon. Một mình bạn dùng. Nhưng giờ tưởng tượng:

  • Có 1000 user — file phải lưu task của ai? Tách 1000 file? Hay 1 file khổng lồ?
  • Hai tab cùng mở — A đánh dấu task #5 done, B sửa title task #5. Ai ghi sau, ai thắng?
  • Bạn muốn báo cáo: "User nào hoàn thành nhiều task nhất tháng này?" — bạn phải load toàn bộ file vào RAM rồi loop.
  • Server crash giữa lúc đang ghi — file bị cắt nửa, JSON parse lỗi, mất sạch dữ liệu.
  • Khi file lên 10GB, mở mất 30 giây.

Mỗi vấn đề trên là một tính năng cốt lõi mà DBMS đã giải quyết:

Vấn đềTính năng DBMS
Truy vấn nhanh trên dữ liệu lớnIndex (B-tree, Hash...)
Nhiều client cùng ghiTransactions, Locking, MVCC
Crash an toànWrite-Ahead Log (WAL), Atomic commit
Truy vấn linh hoạtSQL declarative + Query Optimizer
Đảm bảo ràng buộc dữ liệuConstraints (FK, UNIQUE, CHECK)
Khôi phục dữ liệu cũBackup + Point-in-Time Recovery
Định nghĩa ngắn Database (CSDL): tập dữ liệu có tổ chức. DBMS: phần mềm quản lý DB (Postgres, MySQL, MongoDB...). Khi nói "dùng database X" thực ra ý nói "dùng DBMS X". Nhưng quen miệng vẫn gọi DB.

2. Lịch sử các mô hình dữ liệu

Hiểu vì sao mô hình quan hệ thắng, ta cần biết các mô hình trước nó:

2.1. File systems (1950s–60s)

Mỗi app tự quản lý file phẳng. Không có cấu trúc chung. Hai app cùng đọc → conflict. Khó query.

2.2. Hierarchical model (IBM IMS, 1966)

Dữ liệu hình cây cha-con. Tốt cho dữ liệu phân cấp tự nhiên (org chart, file system) nhưng cứng nhắc — đổi quan hệ là phải refactor cấu trúc.

Company / \ Dept-A Dept-B / \ | Emp1 Emp2 Emp3 ← 1 employee chỉ thuộc 1 department, không thể nhiều

2.3. Network model (CODASYL, 1971)

Cải tiến: cho phép nhiều quan hệ (n-n). Mỗi record kết nối qua "set" pointer. Mạnh hơn nhưng navigation phức tạp — phải code đường đi cụ thể từng truy vấn.

2.4. Relational model (Codd, 1970)

Edgar F. Codd, nhà toán học làm tại IBM, đề xuất mô hình quan hệ trong bài báo "A Relational Model of Data for Large Shared Data Banks". Ý tưởng cách mạng: tất cả dữ liệu đều biểu diễn được bằng các bảng, và truy vấn là phép toán đại số trên bảng (đại số quan hệ).

3 lợi thế then chốt:

  • Đơn giản — bảng có dòng và cột. Ai cũng hiểu.
  • Declarative — bạn nói cần gì, không cần nói cách nào. DB tự tối ưu.
  • Linh hoạt — JOIN cho phép tổ hợp bảng theo ý muốn mà không cần biết trước trong thiết kế.

2.5. Object-Oriented (1990s)

Cố nhét OOP vào DB: Object = row. Phổ biến chỉ trong giới Java/C++ một thời. Hiện tại dùng ORM (Object-Relational Mapping) là cách phổ biến hơn.

2.6. NoSQL (2009+)

Khi web scale (Google, Amazon) cần nhiều TB và millions QPS, mô hình quan hệ + JOIN bị bottleneck. NoSQL hy sinh JOIN/ACID đổi lấy scale ngang. Có 4 họ: Key-Value, Document, Wide-column, Graph (sẽ học ở Ch9).

2.7. NewSQL (2012+)

"Có cách nào của cả hai thế giới không?" — Spanner, CockroachDB, YugabyteDB: scale ngang như NoSQL, đảm bảo ACID + SQL như Postgres. Phức tạp hơn, đắt hơn, dùng khi thực sự cần.

Bài học lịch sử Đừng nghĩ NoSQL "thay thế" SQL. Sau 15 năm hype, các công ty lớn (Uber, Airbnb, Stripe) quay về Postgres cho hầu hết workload, chỉ dùng NoSQL cho cache/log/search. Hiểu sâu mô hình quan hệ là vốn dài hạn.

3. Mô hình quan hệ — đại số trên bảng

Trong lý thuyết, một relation là một tập các tuple tuân theo cùng một schema. Trong thực hành, đó là một bảng:

users:
┌────┬───────────────────┬────────────┐
│ id │ email             │ created_at │
├────┼───────────────────┼────────────┤
│  1 │ alice@example.com │ 2026-01-15 │
│  2 │ bob@example.com   │ 2026-02-03 │
│  3 │ carol@example.com │ 2026-02-20 │
└────┴───────────────────┴────────────┘

Vậy relation có 6 phép toán cơ bản (đại số quan hệ):

PhépKý hiệuMô tảSQL tương ứng
SelectionσLọc dòng theo điều kiệnWHERE
ProjectionπChọn các cộtSELECT col1, col2
UnionHợp 2 bảng (cùng schema)UNION
DifferenceHiệu 2 bảngEXCEPT
Cartesian Product×Tích Đề-các 2 bảngCROSS JOIN
RenameρĐổi tên bảng/cộtAS

Các phép phụ (derived) — kết hợp từ 6 phép trên:

  • Intersection (∩) — giao 2 bảng → INTERSECT
  • Join (⋈) — Cartesian + Selection → JOIN ... ON
  • Division (÷) — "tất cả X có quan hệ với mọi Y" — phức tạp hiếm gặp

Ý nghĩa thực tế: mọi câu SQL phức tạp đều có thể chuyển về một biểu thức đại số quan hệ. Đó là cách query optimizer biến đổi và tối ưu query mà vẫn giữ kết quả đúng.

3.1. Codd's 12 rules (1985)

Codd liệt kê 12 quy tắc để gọi 1 DB là "thực sự quan hệ". Tóm gọn 5 ý quan trọng:

  1. Information rule — mọi dữ liệu (bao gồm metadata) lưu dưới dạng giá trị trong bảng.
  2. Guaranteed access — mọi giá trị truy cập được qua (table, primary key, column).
  3. Systematic NULL — NULL là giá trị riêng biệt với "0" hay "rỗng".
  4. Catalog — metadata cũng nằm trong bảng (thư mục hệ thống).
  5. Integrity independence — constraint ràng buộc lưu trong DB, không phụ thuộc app.

Lưu ý: không DBMS thương mại nào tuân thủ 100% 12 rules này. Nhưng Postgres rất gần.

4. Thuật ngữ chính

Thuật ngữ học thuậtTên thực hànhVí dụ
RelationTableusers, orders
TupleRow / Record(1, 'alice@…', '2026-01-15')
AttributeColumn / Fieldid, email, created_at
DomainData typeINTEGER, VARCHAR(255)
CardinalitySố dòng3 (bảng users ở trên)
Degree (arity)Số cột3 cột → degree = 3
SchemaCấu trúc bảngusers(id INT, email VARCHAR, created_at TIMESTAMP)
InstanceTập dòng tại 1 thời điểm3 dòng cụ thể trong bảng

Schema ổn định, ít đổi. Instance đổi liên tục mỗi giây mỗi giờ. Khi thiết kế DB, ta thiết kế schema. Khi vận hành, ta thao tác trên instance.

Một thuật ngữ dễ lẫn "Database schema" có 2 nghĩa: (a) toàn bộ cấu trúc DB (mọi bảng + quan hệ), (b) trong Postgres, "schema" còn là namespace chứa nhóm bảng (giống package). Khi nói chuyện rõ ngữ cảnh để tránh hiểu nhầm.

5. Keys — sáu loại bạn cần phân biệt

5.1. Super key

Tập cột mà kết hợp lại đủ để xác định duy nhất một dòng. Có thể là siêu tập nhiều cột không cần thiết.

Ví dụ bảng users(id, email, name, phone): {id}, {id, email}, {id, email, name}... đều là super keys.

5.2. Candidate key

Super key tối giản — bỏ bất kỳ cột nào thì không còn unique. Ví dụ: {id}{email} đều có thể là candidate.

5.3. Primary key (PK)

Một candidate key được chọn làm khóa chính. Mỗi bảng chỉ có 1 PK. PK không được NULL, không được trùng.

CREATE TABLE users (
  id    BIGSERIAL PRIMARY KEY,
  email VARCHAR(255) NOT NULL UNIQUE
);

5.4. Alternate (Secondary) key

Các candidate key không được chọn làm PK. Trong ví dụ trên, email là alternate key (vẫn UNIQUE).

5.5. Foreign key (FK)

Một (hoặc nhiều) cột tham chiếu PK của bảng khác. Đây là cách mô hình quan hệ "kết nối" các bảng.

CREATE TABLE orders (
  id      BIGSERIAL PRIMARY KEY,
  user_id BIGINT NOT NULL REFERENCES users(id),
  total   DECIMAL(10,2) NOT NULL
);

FK đảm bảo referential integrity — không thể có order trỏ đến user_id không tồn tại.

Khi xóa user mà có order trỏ tới? Bạn chọn 1 trong các ON DELETE action:

  • NO ACTION / RESTRICT — báo lỗi, không cho xóa (mặc định).
  • CASCADE — xóa user → xóa luôn mọi order của user đó.
  • SET NULL — xóa user → orders.user_id = NULL.
  • SET DEFAULT — xóa user → orders.user_id về giá trị default.

5.6. Composite key

PK gồm nhiều cột. Thường thấy ở bảng nối many-to-many:

CREATE TABLE order_items (
  order_id   BIGINT NOT NULL REFERENCES orders(id),
  product_id BIGINT NOT NULL REFERENCES products(id),
  qty        INT NOT NULL DEFAULT 1,
  PRIMARY KEY (order_id, product_id)  -- composite PK
);

5.7. Surrogate key vs Natural key

Surrogate key

Khóa nhân tạo do DB sinh — thường BIGSERIAL, UUID. Không có ý nghĩa nghiệp vụ.

Ưu: luôn ổn định (không đổi), gọn (8 byte), nhanh.

Nhược: không tự mô tả; cần JOIN để biết nghĩa.

Natural key

Khóa từ dữ liệu thật — email, số CCCD, mã sản phẩm SKU.

Ưu: có ý nghĩa, dễ debug.

Nhược: có thể đổi (user đổi email!), thường dài/chậm hơn surrogate.

Khuyến nghị thực hành: dùng surrogate key (id BIGSERIAL hoặc UUID v7) cho PK, đồng thời thêm UNIQUE cho natural key (như email).

6. Constraints — luật bất biến của dữ liệu

Constraint là quy tắc mà mọi row trong bảng phải tuân theo. DB tự enforce, app không cần check (và không nên tin app).

6.1. NOT NULL

Cột không được phép NULL.

CREATE TABLE users (
  id    BIGSERIAL PRIMARY KEY,
  email VARCHAR(255) NOT NULL,
  name  VARCHAR(100)              -- có thể NULL
);

6.2. UNIQUE

Đảm bảo mọi dòng có giá trị khác nhau ở cột đó (NULL được coi là khác mọi NULL khác — Postgres mặc định cho phép nhiều NULL).

ALTER TABLE users ADD CONSTRAINT users_email_uniq UNIQUE (email);
-- Composite unique:
ALTER TABLE addresses ADD UNIQUE (user_id, label);  -- 1 user có nhiều label, mỗi label duy nhất

6.3. PRIMARY KEY

NOT NULL + UNIQUE + tự tạo index. Mỗi bảng 1 PK duy nhất.

6.4. FOREIGN KEY

Đã trình bày ở 5.5.

6.5. CHECK

Biểu thức boolean phải đúng cho mọi dòng:

CREATE TABLE products (
  id    BIGSERIAL PRIMARY KEY,
  name  VARCHAR(255) NOT NULL,
  price DECIMAL(10,2) NOT NULL CHECK (price >= 0),
  stock INT NOT NULL DEFAULT 0 CHECK (stock >= 0)
);

-- Check phức tạp:
ALTER TABLE orders ADD CONSTRAINT valid_status
  CHECK (status IN ('pending','paid','shipped','delivered','cancelled'));

6.6. DEFAULT

Giá trị tự gán nếu INSERT bỏ qua cột:

created_at TIMESTAMP NOT NULL DEFAULT NOW(),
status     VARCHAR(20) NOT NULL DEFAULT 'pending',
uuid       UUID NOT NULL DEFAULT gen_random_uuid()

6.7. EXCLUSION (Postgres-only, ít gặp)

Tổng quát hóa UNIQUE — đảm bảo không có 2 dòng "xung đột" theo bất kỳ phép so sánh nào, ví dụ không 2 booking trùng khoảng thời gian:

CREATE TABLE bookings (
  room_id INT,
  during  tstzrange,
  EXCLUDE USING gist (room_id WITH =, during WITH &&)
);
Quy tắc vàng Đặt constraint ở DB layer, không chỉ app layer. Một ngày app sẽ có bug, có script SQL chạy tay, có dev mới chạy migration sai — chỉ DB là tuyến phòng thủ cuối cùng bảo vệ tính toàn vẹn dữ liệu.

7. Data types — chọn đúng kiểu, đúng kích cỡ

7.1. Numeric

TypePhạm viKhi nào dùng
SMALLINT−32 768 → 32 767 (2 byte)Tuổi, level, status code nhỏ
INTEGER (INT)±2.1 tỷ (4 byte)ID nhỏ, count thông thường
BIGINT±9.2 × 10¹⁸ (8 byte)ID hệ lớn, timestamp ms
DECIMAL(p,s) / NUMERICChính xác (variable)Tiền tệ, ratio cần đúng tuyệt đối
REAL / FLOAT4~7 chữ số (4 byte)Tọa độ, dữ liệu khoa học
DOUBLE PRECISION / FLOAT8~15 chữ số (8 byte)Khoa học, ML feature
Lưu ý: tiền tệ Không bao giờ dùng FLOAT cho tiền. 0.1 + 0.2 ≠ 0.3 trong float. Dùng DECIMAL(10,2) hoặc lưu cents dưới dạng INT/BIGINT (số nguyên cents).

7.2. Text

TypeMô tả
CHAR(n)Cố định n ký tự, padding bằng space. Hiếm dùng.
VARCHAR(n)Tối đa n ký tự, không pad. Phổ biến.
TEXTKhông giới hạn (lên TB). Postgres khuyên dùng TEXT thay VARCHAR khi không cần limit cứng.

7.3. Date & Time

DATEChỉ ngày: 2026-05-09
TIMEChỉ giờ: 14:30:00
TIMESTAMPNgày + giờ, KHÔNG timezone
TIMESTAMPTZNgày + giờ + timezone (lưu UTC, hiển thị theo client tz). Khuyên dùng.
INTERVALKhoảng thời gian: '3 days', '1 hour 30 minutes'

7.4. Binary & JSON

  • BYTEA — binary blob (file nhỏ, hash). File lớn nên lưu S3, DB chỉ giữ URL.
  • JSON / JSONB (Postgres) — JSONB là binary nén + index được + nhanh hơn JSON text.
  • UUID — 16 byte, format 550e8400-e29b-41d4-a716-446655440000.

7.5. Special (Postgres)

  • ARRAYINTEGER[], TEXT[]. Tiện cho tag, list nhỏ.
  • ENUM — kiểu liệt kê: CREATE TYPE order_status AS ENUM ('pending','paid','shipped').
  • RANGEtstzrange cho khoảng thời gian, int4range cho khoảng số.
  • GEOMETRY/GEOGRAPHY (PostGIS) — bản đồ, khoảng cách, polygon.

8. NULL — giá trị "không biết"

NULL là một trong những concept khó nhất với người mới. Vì:

  • NULL không phải 0, không phải chuỗi rỗng '', không phải false.
  • NULL có nghĩa là "chưa biết" hoặc "không áp dụng".
  • Mọi phép toán với NULL → kết quả NULL: NULL + 5 = NULL, NULL = NULL → UNKNOWN (không true!).

8.1. Logic 3 giá trị

SQL không phải 2-valued (TRUE/FALSE) mà là 3-valued: TRUE / FALSE / UNKNOWN.

xx = NULLx IS NULLNOT (x = NULL)
5UNKNOWNFALSEUNKNOWN
NULLUNKNOWNTRUEUNKNOWN

Cẩn thận: WHERE col != 5 sẽ bỏ qua dòng có col = NULL! Vì NULL != 5 → UNKNOWN, không phải TRUE.

-- Lấy users không có email = 'spam@x':
SELECT * FROM users WHERE email != 'spam@x';
-- ❌ Bỏ qua user có email NULL

SELECT * FROM users WHERE email != 'spam@x' OR email IS NULL;
-- ✓ Đầy đủ

8.2. Hàm xử lý NULL

-- COALESCE: trả về tham số đầu tiên không NULL
SELECT COALESCE(nickname, full_name, 'Unknown') FROM users;

-- NULLIF: trả NULL nếu hai tham số bằng nhau
SELECT NULLIF(divisor, 0);  -- tránh chia 0

-- IS NULL / IS NOT NULL
WHERE deleted_at IS NULL  -- chỉ lấy chưa xóa

8.3. NULL trong aggregate

COUNT(*) đếm cả dòng NULL. COUNT(col) bỏ qua dòng có col NULL. SUM/AVG bỏ qua NULL.

-- Bảng users có 100 dòng, 30 dòng email NULL
SELECT COUNT(*) FROM users;        -- 100
SELECT COUNT(email) FROM users;    -- 70
SELECT COUNT(DISTINCT email) FROM users;  -- số email khác nhau, NULL bị loại
Tranh cãi muôn thuở Một số senior khuyên tránh NULL hoàn toàn — dùng NOT NULL + DEFAULT. Nó tránh được 90% bug, nhưng đôi khi NULL thực sự là cách đúng để biểu diễn "chưa có dữ liệu". Hãy chọn có ý thức, không mặc định.

9. Hands-on — cài Postgres và tạo bảng đầu tiên

9.1. Cài Postgres bằng Docker (khuyên dùng)

# File docker-compose.yml
version: '3.8'
services:
  db:
    image: postgres:16
    environment:
      POSTGRES_PASSWORD: secret
      POSTGRES_DB: learning
    ports:
      - "5432:5432"
    volumes:
      - pg_data:/var/lib/postgresql/data
volumes:
  pg_data:
docker compose up -d
docker compose exec db psql -U postgres -d learning

9.2. Tạo bảng đầu tiên

CREATE TABLE users (
  id         BIGSERIAL PRIMARY KEY,
  email      VARCHAR(255) NOT NULL UNIQUE,
  name       VARCHAR(100),
  role       VARCHAR(20)  NOT NULL DEFAULT 'user'
                         CHECK (role IN ('user','admin','moderator')),
  created_at TIMESTAMPTZ  NOT NULL DEFAULT NOW(),
  deleted_at TIMESTAMPTZ
);

INSERT INTO users (email, name) VALUES
  ('alice@example.com', 'Alice'),
  ('bob@example.com',   'Bob'),
  ('carol@example.com', 'Carol');

SELECT * FROM users;

9.3. Thử vi phạm constraint — xem DB chặn lại

-- Trùng email (UNIQUE)
INSERT INTO users (email, name) VALUES ('alice@example.com', 'Alice 2');
-- ERROR: duplicate key value violates unique constraint "users_email_key"

-- NULL ở cột NOT NULL
INSERT INTO users (email) VALUES (NULL);
-- ERROR: null value in column "email" violates not-null constraint

-- CHECK fail
INSERT INTO users (email, role) VALUES ('x@x.com', 'super-admin');
-- ERROR: new row for relation "users" violates check constraint

Đây là DB bảo vệ bạn. Nếu app bug và cố ghi dữ liệu sai, DB từ chối — đảm bảo dữ liệu luôn nhất quán.

10. Bài tập

Bài 1. Cho bảng products(id, sku, name, price, stock, created_at):

  • Đặt PK phù hợp.
  • SKU phải duy nhất, dạng VARCHAR(50).
  • Price không âm, stock không âm.
  • created_at tự động lấy thời điểm INSERT.
  • Viết CREATE TABLE đầy đủ.

Bài 2. Thiết kế schema cho hệ thống Blog:

  • users(id, email, name, password_hash, created_at)
  • posts(id, author_id, title, body, published_at, status)
  • comments(id, post_id, author_id, body, created_at)
  • likes(user_id, post_id, created_at) — composite PK

Viết đầy đủ CREATE TABLE với FK, các constraint hợp lý.

Bài 3. Cho query: SELECT name FROM users WHERE deleted_at != NOW(); — vì sao kết quả luôn rỗng? Sửa lại đúng ý "lấy user chưa bị xóa".

Bài 4. Phân biệt:

  • Super key vs Candidate key vs Primary key
  • Surrogate key vs Natural key
  • UNIQUE vs PRIMARY KEY

Bài 5. ON DELETE thuộc các loại nào? Cho ví dụ thực tế khi nên chọn CASCADE, khi nên chọn RESTRICT, khi nên chọn SET NULL.

11. Quiz — kiểm tra hiểu bài

Quiz cuối Chương 1

Phát biểu nào ĐÚNG về NULL trong SQL?

  • NULL = 0 trong số học
  • NULL = NULL trả về UNKNOWN, không phải TRUE
  • NULL không thể gán cho cột bất kỳ
  • COUNT(*) bỏ qua dòng có giá trị NULL
SQL dùng logic 3 giá trị (TRUE/FALSE/UNKNOWN). NULL = NULL trả về UNKNOWN. Đó là lý do phải dùng IS NULL để kiểm tra. COUNT(*) đếm cả dòng có NULL; COUNT(col) mới bỏ qua NULL.

Một bảng có thể có:

  • Nhiều PRIMARY KEY
  • Một PRIMARY KEY và không có UNIQUE nào
  • Một PRIMARY KEY và nhiều UNIQUE
  • Không có PRIMARY KEY là không hợp lệ
Mỗi bảng tối đa 1 PRIMARY KEY, nhưng có thể có nhiều UNIQUE. Bảng không có PK là hợp lệ về mặt cú pháp (Postgres cho phép) nhưng không khuyến nghị.

FK ON DELETE CASCADE nghĩa là:

  • Xóa parent → các child trỏ đến cũng bị xóa theo
  • Xóa parent → child bị set về NULL
  • Không cho phép xóa parent nếu còn child
  • Khi child bị xóa, parent cũng xóa
CASCADE lan truyền việc xóa từ parent sang child. Phù hợp cho quan hệ "phụ thuộc hoàn toàn" (vd: xóa order → xóa order_items). KHÔNG phù hợp khi child có ý nghĩa độc lập (vd: xóa user → KHÔNG nên xóa post của user, dùng SET NULL hoặc anonymize).

Vì sao không dùng FLOAT để lưu tiền?

  • FLOAT chiếm nhiều dung lượng hơn DECIMAL
  • FLOAT không hỗ trợ phép cộng
  • FLOAT có lỗi làm tròn nhị phân (0.1 + 0.2 ≠ 0.3)
  • FLOAT không có thứ tự sắp xếp
FLOAT/DOUBLE dùng IEEE 754 binary, không biểu diễn chính xác số thập phân như 0.1. Tích lũy lỗi sẽ tạo sai số tiền tệ (mất xu, lệch sổ sách). Dùng DECIMAL(p,s) hoặc lưu cents dạng INTEGER.

Edgar F. Codd đề xuất mô hình quan hệ năm:

  • 1995
  • 1985
  • 1980
  • 1970
Codd publish bài báo "A Relational Model of Data for Large Shared Data Banks" năm 1970. 12 rules đến năm 1985. Mô hình tồn tại 50+ năm và vẫn là chuẩn vàng.

Trong các phát biểu sau, đâu là TRUE về relation (bảng)?

  • Thứ tự dòng quan trọng — dòng 1 khác dòng 2 đảo ngược
  • Relation là một TẬP các tuple — không có thứ tự, không trùng lặp
  • Một relation có thể có nhiều tuple giống hệt nhau
  • Relation phải có ít nhất 1 cột FK
Theo lý thuyết quan hệ, relation = tập các tuple. Tập không có thứ tự, không trùng lặp. Trong thực hành SQL, không có PK/UNIQUE thì DB cho phép dòng trùng — đó là lệch khỏi lý thuyết thuần. PK đảm bảo tính tập hợp đúng nghĩa.

Bạn có bảng users(id, email) với 100 dòng, trong đó 30 dòng email = NULL. Câu nào sai?

  • SELECT COUNT(*) FROM users → 100
  • SELECT COUNT(email) FROM users → 70
  • SELECT * FROM users WHERE email != 'a@x.com' → trả 70 dòng (giả sử 0 dòng có email='a@x.com')
  • SELECT * FROM users WHERE email IS NULL → 30 dòng
Câu 3 SAI. email != 'a@x.com' với email NULL trả về UNKNOWN, không phải TRUE. WHERE chỉ giữ TRUE → 30 dòng NULL bị loại. Kết quả là 70, không phải 100. Phải bổ sung OR email IS NULL nếu muốn lấy đầy đủ.

Composite primary key:

  • Là PK gồm nhiều cột; thường gặp ở bảng nối many-to-many
  • Là PK gồm một cột UUID
  • Là PK do app tự sinh thay vì DB
  • Là alias của UNIQUE constraint
Composite PK (khóa chính phức) dùng nhiều cột làm khóa chính. Phổ biến: bảng order_items(order_id, product_id), likes(user_id, post_id). Mỗi tổ hợp duy nhất.

Hoàn thành Chương 1. Tiếp theo: Chương 2 — SQL Cơ Bản (DDL/DML/DQL/DCL/TCL) →