Skip to content

Ngày 7: Ôn tập 1 (Ngày 1 đến 6) ​

Tóm tắt: Bài ôn tập chặng đầu. Không có kiến thức mới: bạn dùng lại những gì đã học từ Ngày 1 đến Ngày 6 (khám phá schema, kiểu dữ liệu và ràng buộc, SELECT/WHERE/NULL, ORDER BY/LIMIT, biểu thức và hàm, INSERT/UPDATE/DELETE) để giải 16 bài tập trên DSC Shop, chia ba mức độ, có đáp án và kết quả thật để tự chấm.

Sau bài này bạn có thể:

  • Tự kiểm tra mình đã nắm chắc phần nào của Ngày 1 đến 6 và phần nào cần đọc lại.
  • Viết được truy vấn đọc dữ liệu kết hợp lọc, xử lý NULL, sắp xếp, phân trang và cột tính toán.
  • Tạo được một bảng nháp có đủ ràng buộc, rồi thêm, sửa, xóa dữ liệu trên đó theo quy trình an toàn.

Cần biết trước: Ngày 1, Ngày 2, Ngày 3, Ngày 4, Ngày 5, Ngày 6.

Cách làm bài ​

  1. Mở psql hoặc Adminer, kết nối database sql_challenge (xem setup/README.md).
  2. Tự viết truy vấn trước, chạy thử, rồi mới mở "Đáp án" để so. Kết quả của bạn phải khớp từng dòng (thứ tự dòng khớp khi đề có yêu cầu sắp xếp).
  3. Mọi đáp án được chạy trên dữ liệu gốc. Bài nào sửa dữ liệu thì đáp án bọc trong BEGIN ... ROLLBACK hoặc làm trên bảng nháp, nên làm xong dữ liệu shop vẫn như cũ. Nếu lỡ sửa thật, chạy lại script init:
sh
docker compose exec -T db psql -U postgres -d sql_challenge -f /docker-entrypoint-initdb.d/01_schema.sql -f /docker-entrypoint-initdb.d/02_seed.sql
  1. Chỉ dùng kiến thức đến Ngày 6: chưa dùng COUNT/SUM (Ngày 8), GROUP BY (Ngày 9) hay JOIN (Ngày 10).

Gợi ý tự chấm: làm đúng 12/16 bài trở lên là sẵn sàng sang chặng 2. Sai bài nào, đọc lại ngày ghi trong ngoặc ở đầu bài đó.

Ôn nhanh ​

NgàyÝ chính cần nhớ
1SQL là ngôn ngữ khai báo: bạn nói muốn gì, database tự tìm cách lấy. \dt liệt kê bảng, \d ten_bang xem cấu trúc. Nhóm lệnh: DDL (CREATE, ALTER, DROP), DML (SELECT, INSERT, UPDATE, DELETE), DCL (GRANT, REVOKE), TCL (BEGIN, COMMIT, ROLLBACK)
2Tiền dùng NUMERIC, không dùng FLOAT. Khóa chính GENERATED ALWAYS AS IDENTITY. Ràng buộc NOT NULL, UNIQUE, CHECK, DEFAULT, khóa ngoại REFERENCES ... ON DELETE
3SELECT ... FROM ... WHERE; IN, BETWEEN, LIKE/ILIKE; IS NULL, không bao giờ = NULL; NOT > AND > OR, nên dùng ngoặc khi trộn AND/OR
4ORDER BY nhiều cột, ASC/DESC, NULLS FIRST/LAST; LIMIT ... OFFSET ...; phân trang cần ORDER BY xác định duy nhất thứ tự
5INT / INT chia nguyên; `
6INSERT ... RETURNING, INSERT ... SELECT, ON CONFLICT; UPDATE ... FROM; DELETE; TRUNCATE. Quy trình an toàn: SELECT xem trước, BEGIN, kiểm tra, COMMIT/ROLLBACK

Lỗi thường gặp (tổng hợp chặng 1) ​

LỗiVí dụ saiCách đúng
So sánh với NULL bằng =WHERE phone = NULL (luôn 0 dòng)WHERE phone IS NULL
Quên rằng <> bỏ qua NULLWHERE city <> 'Hà Nội' không trả khách chưa có citythêm OR city IS NULL nếu muốn lấy cả họ (Bài 7)
Trộn AND/OR không ngoặcstatus = 'paid' OR status = 'shipped' AND ...(status = 'paid' OR status = 'shipped') AND ... (Bài 8)
Chia nguyên(price - cost_price) / price ra 0nhân 100.0 hoặc ép ::NUMERIC trước khi chia (Bài 9)
Phân trang không có thứ tự duy nhấtORDER BY price LIMIT 5 OFFSET 5 khi nhiều sản phẩm cùng giáthêm cột khóa: ORDER BY price DESC, product_id (Bài 11)
Dùng FLOAT cho tiềnprice FLOATprice NUMERIC(12,0)
UPDATE/DELETE không xem trướcsửa theo tên, sửa nhầm hai "Nguyễn Văn An"SELECT trước, lọc theo khóa chính, chạy trong BEGIN

Bài tập ​

Cơ bản ​

Bài 1 (Ngày 1, 2). Không đọc file schema, dùng lệnh psql để xem cấu trúc bảng order_items. Trả lời: khóa chính gồm những cột nào? Cột discount có kiểu gì, mặc định bao nhiêu, bị ràng buộc gì? Xóa một đơn hàng thì các dòng của nó trong order_items ra sao?

Đáp án
text
\d order_items

Kết quả:

text
                  Table "shop.order_items"
   Column   |     Type      | Collation | Nullable | Default
------------+---------------+-----------+----------+---------
 order_id   | integer       |           | not null |
 product_id | integer       |           | not null |
 quantity   | integer       |           | not null |
 unit_price | numeric(12,0) |           | not null |
 discount   | numeric(3,2)  |           | not null | 0
Indexes:
    "order_items_pkey" PRIMARY KEY, btree (order_id, product_id)
    "order_items_product_id_idx" btree (product_id)
Check constraints:
    "order_items_discount_check" CHECK (discount >= 0::numeric AND discount <= 1::numeric)
    "order_items_quantity_check" CHECK (quantity > 0)
    "order_items_unit_price_check" CHECK (unit_price >= 0::numeric)
Foreign-key constraints:
    "order_items_order_id_fkey" FOREIGN KEY (order_id) REFERENCES orders(order_id) ON DELETE CASCADE
    "order_items_product_id_fkey" FOREIGN KEY (product_id) REFERENCES products(product_id)
  • Khóa chính ghép từ hai cột (order_id, product_id): một sản phẩm chỉ xuất hiện một lần trong mỗi đơn.
  • discount là numeric(3,2) (tối đa 9.99, hai chữ số thập phân), mặc định 0, CHECK trong khoảng 0 đến 1.
  • ON DELETE CASCADE: xóa đơn thì các dòng hàng của đơn bị xóa theo.

Bài 2 (Ngày 3, 4). Liệt kê sku, name, price của các sản phẩm có giá từ 5 triệu đến 10 triệu (tính cả hai đầu), giá cao xếp trước.

Đáp án
sql
SELECT sku, name, price
FROM products
WHERE price BETWEEN 5000000 AND 10000000
ORDER BY price DESC;

Kết quả:

skunameprice
PC-003PC văn phòng DSC Basic9990000
TAB-002Samsung Galaxy Tab S9 FE9490000
ACC-004Tai nghe Sony WH-1000XM57990000
PC-001Màn hình LG UltraGear 27 inch6490000
ACC-005Tai nghe AirPods Pro 25990000

Bài 3 (Ngày 3). Bộ phận chăm sóc khách cần danh sách khách thiếu số điện thoại hoặc thiếu email: customer_id, full_name, email, phone, sắp theo customer_id.

Đáp án
sql
SELECT customer_id, full_name, email, phone
FROM customers
WHERE phone IS NULL OR email IS NULL
ORDER BY customer_id;

Kết quả:

customer_idfull_nameemailphone
3Lê Hoàng Cường[email protected]NULL
5Hoàng Thị EmNULL0934567890
7Đặng Thu Giang[email protected]NULL
13Phan Thị Oanh[email protected]NULL
17Châu Ngọc TrâmNULLNULL

Bài 4 (Ngày 3, 4). 5 sản phẩm đắt nhất đang kinh doanh (is_active = TRUE).

Đáp án
sql
SELECT sku, name, price
FROM products
WHERE is_active = TRUE
ORDER BY price DESC
LIMIT 5;

Kết quả:

skunameprice
LAP-001ASUS ROG Strix G1632990000
LAP-002Lenovo Legion 529990000
LAP-005MacBook Air M2 13 inch24990000
LAP-003Acer Nitro V 1521990000
PHN-001iPhone 15 128GB19990000

Bài 5 (Ngày 3). Tìm các khách là sinh viên HCMUTE (email kết thúc bằng @student.hcmute.edu.vn).

Đáp án
sql
SELECT customer_id, full_name, email
FROM customers
WHERE email LIKE '%@student.hcmute.edu.vn'
ORDER BY customer_id;

Kết quả:

customer_idfull_nameemail
6Võ Quốc Phong[email protected]
10Dương Minh Khoa[email protected]
14Huỳnh Tấn Phát[email protected]

Bài 6 (Ngày 3, 4). Tìm sản phẩm của Logitech hoặc Samsung dựa vào tên, không phân biệt hoa thường (người dùng có thể gõ logitech). Sắp theo giá tăng dần.

Đáp án
sql
SELECT sku, name, price
FROM products
WHERE name ILIKE '%logitech%'
   OR name ILIKE '%samsung%'
ORDER BY price;

Kết quả:

skunameprice
ACC-001Chuột Logitech G102399000
ACC-002Chuột Logitech MX Master 3S2490000
TAB-002Samsung Galaxy Tab S9 FE9490000
PHN-002Samsung Galaxy S2418990000

ILIKE là của PostgreSQL. Ở hệ khác, viết LOWER(name) LIKE '%logitech%'.

Vận dụng ​

Bài 7 (Ngày 3, logic ba trị). Viết truy vấn "khách không ở Hà Nội" bằng WHERE city <> 'Hà Nội'. So với số khách thực tế (18 khách, 4 người ở Hà Nội), kết quả thiếu ai? Sửa lại để lấy cả những người đó.

Đáp án
sql
SELECT customer_id, full_name, city
FROM customers
WHERE city <> 'Hà Nội'
ORDER BY customer_id;

Kết quả: 12 dòng, trong khi 18 - 4 = 14. Thiếu khách 11 và 17 vì city của họ là NULL: NULL <> 'Hà Nội' cho ra UNKNOWN, không phải TRUE, nên WHERE loại dòng đó.

sql
SELECT customer_id, full_name, city
FROM customers
WHERE city <> 'Hà Nội' OR city IS NULL
ORDER BY customer_id;
customer_idfull_namecity
1Nguyễn Văn AnTP. Hồ Chí Minh
3Lê Hoàng CườngĐà Nẵng
4Phạm Minh DuyTP. Hồ Chí Minh
5Hoàng Thị EmCần Thơ
6Võ Quốc PhongHồ Chí Minh
8Bùi Văn Hùngđà nẵng
9Ngô Thị LanHải Phòng
10Dương Minh KhoaTP. Hồ Chí Minh
11Lý Thanh MaiNULL
12Trịnh Công NamBiên Hòa
14Huỳnh Tấn PhátTP.HCM
16Tạ Quang SơnCần Thơ
17Châu Ngọc TrâmNULL
18Mai Đức UyĐà Nẵng

(14 dòng.) Câu hỏi nghiệp vụ quyết định có lấy khách NULL hay không; điều quan trọng là bạn chọn có chủ đích.

Bài 8 (Ngày 3, thứ tự ưu tiên). Tìm các đơn đang xử lý (trạng thái paid hoặc shipped) giao tới TP. Hồ Chí Minh. Viết thử không có ngoặc và có ngoặc, giải thích vì sao kết quả khác nhau.

Đáp án

Sai (không ngoặc):

sql
SELECT order_id, status, shipping_city
FROM orders
WHERE status = 'paid' OR status = 'shipped' AND shipping_city = 'TP. Hồ Chí Minh'
ORDER BY order_id;
order_idstatusshipping_city
37shippedTP. Hồ Chí Minh
38paidHà Nội
39paidHà Nội

AND ưu tiên hơn OR, nên câu trên được hiểu là status = 'paid' OR (status = 'shipped' AND shipping_city = '...'): mọi đơn paid đều lọt vào, kể cả đơn giao Hà Nội.

Đúng:

sql
SELECT order_id, status, shipping_city
FROM orders
WHERE (status = 'paid' OR status = 'shipped')
  AND shipping_city = 'TP. Hồ Chí Minh'
ORDER BY order_id;
order_idstatusshipping_city
37shippedTP. Hồ Chí Minh

Gọn hơn: WHERE status IN ('paid', 'shipped') AND shipping_city = 'TP. Hồ Chí Minh'.

Bài 9 (Ngày 4, 5). Với mỗi sản phẩm, tính tiền lãi price - cost_price và tỉ lệ lãi trên giá bán (%, làm tròn 1 chữ số). Lấy 5 sản phẩm có tỉ lệ lãi cao nhất; sản phẩm chưa có giá nhập phải nằm cuối, không được chiếm chỗ đầu bảng.

Đáp án
sql
SELECT sku, name, price, cost_price,
       price - cost_price                            AS lai,
       ROUND((price - cost_price) * 100.0 / price, 1) AS ti_le_lai
FROM products
ORDER BY ti_le_lai DESC NULLS LAST
LIMIT 5;

Kết quả:

skunamepricecost_pricelaiti_le_lai
ACC-008Cáp USB-C to USB-C 1m150000600009000060.0
ACC-001Chuột Logitech G10239900025000014900037.3
ACC-007Sạc Anker 65W89000060000029000032.6
PHN-004Nokia 105 4G69000050000019000027.5
ACC-003Bàn phím cơ Keychron K21890000150000039000020.6

Hai điểm cần chú ý: với DESC, PostgreSQL mặc định đặt NULL lên đầu, nên phải ghi NULLS LAST; nhân 100.0 trước khi chia để không bị chia nguyên. ORDER BY dùng được alias ti_le_lai vì nó chạy sau SELECT.

Bài 10 (Ngày 5). Tính tuổi của 5 khách trẻ nhất (bỏ qua khách chưa có ngày sinh). Để kết quả cố định, tính tuổi tại ngày 2026-10-17.

Đáp án
sql
SELECT full_name, birth_date,
       EXTRACT(YEAR FROM AGE(DATE '2026-10-17', birth_date)) AS tuoi
FROM customers
WHERE birth_date IS NOT NULL
ORDER BY birth_date DESC
LIMIT 5;

Kết quả:

full_namebirth_datetuoi
Dương Minh Khoa2005-02-1421
Huỳnh Tấn Phát2004-08-2022
Võ Quốc Phong2004-05-1222
Phạm Minh Duy2003-01-3023
Tạ Quang Sơn2002-12-1223

Trong thực tế bạn viết AGE(CURRENT_DATE, birth_date) hoặc AGE(birth_date); khi đó kết quả thay đổi theo ngày chạy. Trẻ nhất nghĩa là sinh muộn nhất, nên sắp birth_date DESC.

Bài 11 (Ngày 4). Trang sản phẩm hiển thị 5 sản phẩm mỗi trang, sắp giá giảm dần. Viết truy vấn lấy trang 2. Vì sao cần thêm product_id vào ORDER BY?

Đáp án
sql
SELECT product_id, sku, name, price
FROM products
ORDER BY price DESC, product_id
LIMIT 5 OFFSET 5;

Kết quả:

product_idskunameprice
11PHN-002Samsung Galaxy S2418990000
4LAP-004Dell Inspiron 1418490000
6LAP-006HP Pavilion 1516990000
14TAB-001iPad Air M2 11 inch16990000
9PC-003PC văn phòng DSC Basic9990000

HP Pavilion 15 và iPad Air M2 11 inch cùng giá 16.990.000. Nếu chỉ ORDER BY price DESC, thứ tự giữa chúng không được đảm bảo, và khi chúng nằm ở ranh giới trang, một sản phẩm có thể hiện ở cả hai trang hoặc biến mất. Thêm cột khóa chính làm thứ tự xác định duy nhất. Trang n dùng OFFSET (n - 1) * 5.

Bài 12 (Ngày 5). Tạo danh bạ khách ở Hà Nội dạng một cột Tên - Số điện thoại; ai chưa có số thì ghi chưa có SĐT. Sắp theo tên.

Đáp án
sql
SELECT customer_id,
       full_name || ' - ' || COALESCE(phone, 'chưa có SĐT') AS danh_ba
FROM customers
WHERE city = 'Hà Nội'
ORDER BY full_name;

Kết quả:

customer_iddanh_ba
7Đặng Thu Giang - chưa có SĐT
15Nguyễn Văn An - 0966777888
13Phan Thị Oanh - chưa có SĐT
2Trần Thị Bình - 0912345678

Không có COALESCE, full_name || ' - ' || NULL cho ra NULL cả chuỗi: khách 7 và 13 sẽ có danh bạ trống. (CONCAT thì bỏ qua NULL, nhưng khi đó dòng kết thúc bằng ' - ' lơ lửng.)

Bài 13 (Ngày 5). Liệt kê 5 dòng hàng có giảm giá mang lại thành tiền lớn nhất: order_id, product_id, quantity, unit_price, mức giảm dạng chữ như 5%, và thành tiền quantity * unit_price * (1 - discount) làm tròn đến đồng.

Đáp án
sql
SELECT order_id, product_id, quantity, unit_price,
       (discount * 100)::INT || '%'                    AS giam,
       ROUND(quantity * unit_price * (1 - discount)) AS thanh_tien
FROM order_items
WHERE discount > 0
ORDER BY thanh_tien DESC
LIMIT 5;

Kết quả:

order_idproduct_idquantityunit_pricegiamthanh_tien
2711329900005%31340500
1851249900005%23740500
731229900005%21840500
24101199900005%18990500
351111899000010%17091000

discount * 100 là 5.00; ép ::INT để thành 5 trước khi nối chuỗi.

Bài 14 (Ngày 5). Liệt kê các đơn đặt trong tháng 11/2025: order_id, ngày đặt (chỉ phần ngày), giờ đặt (chỉ phần giờ) và ghi chú. Lọc bằng điều kiện khoảng trên order_date.

Đáp án
sql
SELECT order_id,
       order_date::DATE AS ngay,
       order_date::TIME AS gio,
       note
FROM orders
WHERE order_date >= '2025-11-01'
  AND order_date <  '2025-12-01'
ORDER BY order_date;

Kết quả:

order_idngaygionote
212025-11-1119:00:00NULL
222025-11-1123:30:00Săn sale 11/11
232025-11-2514:45:00NULL

Điều kiện khoảng >= ngày đầu tháng AND < ngày đầu tháng sau không bỏ sót đơn đặt lúc 23:30 ngày cuối tháng, khác với BETWEEN '2025-11-01' AND '2025-11-30' (chỉ tới 00:00 ngày 30). Giờ hiển thị theo múi giờ của phiên làm việc; môi trường Docker của khóa học đặt sẵn Asia/Ho_Chi_Minh.

Thử thách ​

Bài 15 (Ngày 2, 6). Cửa hàng muốn thử tính năng mã giảm giá. Trên một bảng nháp review_vouchers:

  1. Tạo bảng với các cột: voucher_id (khóa chính tự sinh), code (tối đa 20 ký tự, bắt buộc, không trùng), discount_percent (số nguyên, bắt buộc, từ 1 đến 50), min_order (tiền, mặc định 0, không âm), valid_until (ngày, bắt buộc), is_used (mặc định chưa dùng).
  2. Thêm 3 mã trong một lệnh và trả về id: DSC10 giảm 10%, không có đơn tối thiểu, hạn 31/12/2026; SINHVIEN15 giảm 15%, đơn tối thiểu 2 triệu, hạn 30/11/2026; BLACKFRIDAY giảm 30%, đơn tối thiểu 10 triệu, hạn 28/11/2026.
  3. Thử thêm mã SALE70 giảm 70% và đọc thông báo lỗi.
  4. Dùng UPSERT để "phát hành lại" DSC10: giảm 12%, hạn mới 31/01/2027.
  5. Xóa các mã hết hạn trước 01/12/2026 (xem trước bằng SELECT), rồi xóa bảng nháp.
Đáp án
sql
DROP TABLE IF EXISTS review_vouchers;
CREATE TABLE review_vouchers (
    voucher_id       INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
    code             VARCHAR(20)   NOT NULL UNIQUE,
    discount_percent INT           NOT NULL CHECK (discount_percent BETWEEN 1 AND 50),
    min_order        NUMERIC(12,0) NOT NULL DEFAULT 0 CHECK (min_order >= 0),
    valid_until      DATE          NOT NULL,
    is_used          BOOLEAN       NOT NULL DEFAULT FALSE
);

INSERT INTO review_vouchers (code, discount_percent, min_order, valid_until)
VALUES
    ('DSC10',       10,        0, '2026-12-31'),
    ('SINHVIEN15',  15,  2000000, '2026-11-30'),
    ('BLACKFRIDAY', 30, 10000000, '2026-11-28')
RETURNING voucher_id, code, discount_percent, min_order, is_used;
voucher_idcodediscount_percentmin_orderis_used
1DSC10100f
2SINHVIEN15152000000f
3BLACKFRIDAY3010000000f
sql
INSERT INTO review_vouchers (code, discount_percent, valid_until)
VALUES ('SALE70', 70, '2026-12-31');
text
ERROR:  new row for relation "review_vouchers" violates check constraint "review_vouchers_discount_percent_check"
DETAIL:  Failing row contains (4, SALE70, 70, 0, 2026-12-31, f).
sql
INSERT INTO review_vouchers (code, discount_percent, valid_until)
VALUES ('DSC10', 12, '2027-01-31')
ON CONFLICT (code) DO UPDATE
SET discount_percent = EXCLUDED.discount_percent,
    valid_until      = EXCLUDED.valid_until
RETURNING voucher_id, code, discount_percent, valid_until;
voucher_idcodediscount_percentvalid_until
1DSC10122027-01-31

DSC10 giữ nguyên voucher_id = 1: dòng cũ được cập nhật, không có dòng mới.

sql
SELECT code, discount_percent, valid_until
FROM review_vouchers
WHERE valid_until < '2026-12-01'
ORDER BY valid_until;

DELETE FROM review_vouchers
WHERE valid_until < '2026-12-01'
RETURNING code;

DROP TABLE review_vouchers;

Xem trước và kết quả RETURNING đều là 2 mã:

codediscount_percentvalid_until
BLACKFRIDAY302026-11-28
SINHVIEN15152026-11-30
code
SINHVIEN15
BLACKFRIDAY

(Thứ tự dòng của RETURNING theo thứ tự dòng được xóa, không theo ORDER BY nào.)

Bài 16 (Ngày 6). Sếp muốn tăng 3% giá (làm tròn đến nghìn) cho mọi sản phẩm thuộc danh mục Chuột & Bàn phím, chỉ biết tên danh mục. Làm đủ quy trình an toàn: xem trước, chạy UPDATE ... FROM trong transaction với RETURNING, kiểm tra số dòng. Ở bài ôn tập này, kết thúc bằng ROLLBACK.

Đáp án

Xem trước:

sql
SELECT category_id FROM categories WHERE name = 'Chuột & Bàn phím';   -- 10

SELECT sku, name, price
FROM products
WHERE category_id = 10
ORDER BY sku;
skunameprice
ACC-001Chuột Logitech G102399000
ACC-002Chuột Logitech MX Master 3S2490000
ACC-003Bàn phím cơ Keychron K21890000
ACC-009Lót chuột DSC XXL250000

Chạy thử:

sql
BEGIN;

UPDATE products
SET price = ROUND(products.price * 1.03, -3)
FROM categories
WHERE categories.category_id = products.category_id
  AND categories.name = 'Chuột & Bàn phím'
RETURNING products.sku, products.name, products.price;

ROLLBACK;
skunameprice
ACC-001Chuột Logitech G102411000
ACC-002Chuột Logitech MX Master 3S2565000
ACC-003Bàn phím cơ Keychron K21947000
ACC-009Lót chuột DSC XXL258000

psql báo UPDATE 4, khớp 4 dòng xem trước. Nếu là thật, thay ROLLBACK bằng COMMIT.

Tóm tắt ​

  • Đọc dữ liệu: SELECT cột cần dùng, WHERE để lọc (nhớ ngoặc khi trộn AND/OR), ORDER BY + LIMIT/OFFSET để sắp và phân trang với thứ tự xác định duy nhất.
  • NULL là "chưa biết": dùng IS NULL/IS NOT NULL, nhớ rằng <> và phép nối chuỗi cũng gặp NULL; COALESCE để thay giá trị mặc định, NULLS LAST khi sắp xếp.
  • Biểu thức: cẩn thận chia nguyên, dùng ROUND, ép kiểu ::, lọc ngày bằng điều kiện khoảng.
  • Ghi dữ liệu: ràng buộc là người gác cổng; sửa/xóa theo khóa chính, xem trước bằng SELECT, chạy trong BEGIN, kiểm tra rồi mới COMMIT.
  • Chặng 2 bắt đầu từ Ngày 8: gom nhiều dòng thành con số thống kê.

Đọc thêm ​


⬅️ Ngày 6: Thêm, sửa, xóa dữ liệu (INSERT, UPDATE, DELETE) | 📚 Mục lục | Ngày 8: Hàm tổng hợp (COUNT, SUM, AVG, MIN, MAX) ➡️

Phát hành theo giấy phép MIT.