Mở đầu — vì sao bài này quan trọng
Suốt cả khóa học này, bạn đã quen với việc chỉ "đọc" dữ liệu: SELECT ra, lọc bằng WHERE, gom bằng GROUP BY, ghép bằng JOIN. Đó là 90% công việc thường ngày của một BA, và điều đó hoàn toàn đúng. Phần lớn thời gian, bạn nên chỉ có quyền đọc trên hệ thống production để khỏi vô tình làm hỏng dữ liệu thật.
Nhưng có một sự thật mà nhiều BA né tránh: bạn sẽ không thể tránh được DML mãi. DML — viết tắt của Data Manipulation Language — là nhóm câu lệnh dùng để thay đổi dữ liệu trong bảng: INSERT (thêm dòng mới), UPDATE (sửa dòng đã có), DELETE (xóa dòng). Sớm muộn gì bạn cũng sẽ chạm vào chúng trong những tình huống rất thực tế:
- Bạn cần tự dựng dữ liệu test trong sandbox để kiểm tra một báo cáo hay một logic nghiệp vụ trước khi dev triển khai.
- Có một lô dữ liệu bị sai (sai mã tỉnh, sai trạng thái đơn hàng) và bạn được giao viết câu lệnh fix để team data engineer review rồi chạy.
- Bạn ngồi cạnh kỹ sư trong một buổi xử lý sự cố, và bạn cần đọc hiểu được câu
UPDATEhọ sắp chạy để xác nhận nó đúng nghiệp vụ. - Bạn viết migration script cho một schema mới trong dự án nhỏ mà bạn vừa là BA vừa kiêm "nửa kỹ thuật".
SELECT viết sai chỉ trả ra kết quả sai, bạn xem rồi bỏ. Nhưng một câu UPDATE hay DELETE viết sai sẽ thay đổi vĩnh viễn dữ liệu — và nếu thiếu mệnh đề WHERE, nó có thể quét sạch hoặc ghi đè toàn bộ bảng trong một phần nghìn giây. Bài học này sẽ cho bạn đủ hiểu biết để dùng DML một cách an toàn, đọc được câu lệnh người khác viết, và quan trọng nhất là biết khi nào không nên tự tay chạy.Khái niệm cốt lõi
DML đứng cạnh hai nhóm câu lệnh khác mà bạn nên phân biệt được. DDL (Data Definition Language: CREATE, ALTER, DROP) định nghĩa cấu trúc bảng. DCL (Data Control Language: GRANT, REVOKE) quản lý quyền. Còn DML thao tác trên nội dung dữ liệu. Là BA, bạn chủ yếu cần thành thạo DML ở mức đọc-hiểu và viết cẩn thận.
INSERT — thêm dòng mới
INSERT đưa dữ liệu mới vào bảng. Cú pháp cơ bản:
INSERT INTO khach_hang (ho_ten, sdt, thanh_pho, ngay_tao)
VALUES ('Nguyễn Văn An', '0901234567', 'Hà Nội', '2026-06-27');
Một mẹo quan trọng: luôn liệt kê tên cột rõ ràng trong ngoặc, đừng dựa vào thứ tự cột ngầm định. Nếu ai đó thêm cột mới vào bảng sau này, câu INSERT không ghi tên cột của bạn sẽ vỡ ngay. Bạn cũng có thể chèn nhiều dòng một lần:
INSERT INTO khach_hang (ho_ten, sdt, thanh_pho)
VALUES
('Trần Thị Bình', '0912000001', 'Đà Nẵng'),
('Lê Hữu Cường', '0912000002', 'TP.HCM');
Và mạnh hơn cả — bạn có thể chèn từ một câu SELECT, cực kỳ hữu ích khi dựng bảng test từ dữ liệu thật:
INSERT INTO khach_hang_test (ho_ten, sdt, thanh_pho)
SELECT ho_ten, sdt, thanh_pho
FROM khach_hang
WHERE thanh_pho = 'Hà Nội'
LIMIT 100;
UPDATE — sửa dòng đã có
UPDATE thay đổi giá trị của các dòng đã tồn tại. Đây là câu lệnh nguy hiểm nhất vì bản chất "ghi đè":
UPDATE don_hang
SET trang_thai = 'da_giao'
WHERE ma_don = 'DH20260627001';
Mệnh đề SET chỉ ra cột nào đổi sang giá trị gì. Mệnh đề WHERE quyết định những dòng nào bị ảnh hưởng. Nếu bạn quên WHERE, toàn bộ bảng sẽ bị đổi. Hãy khắc cốt ghi tâm điều này. Bạn cũng có thể cập nhật nhiều cột cùng lúc và dùng biểu thức:
UPDATE san_pham
SET gia_ban = gia_goc * 0.8,
dang_giam_gia = TRUE
WHERE danh_muc = 'thoi_trang';
DELETE — xóa dòng
DELETE loại bỏ dòng khỏi bảng:
DELETE FROM gio_hang
WHERE cap_nhat_lan_cuoi < '2026-01-01';
Cũng như UPDATE, thiếu WHERE nghĩa là xóa sạch bảng. Lưu ý phân biệt: DELETE FROM ban (không WHERE) xóa từng dòng và có thể rollback; còn TRUNCATE TABLE ban là lệnh DDL xóa cực nhanh, không log từng dòng, thường không rollback được. BA gần như không bao giờ nên động đến TRUNCATE trên dữ liệu thật.
Transaction — phao cứu sinh của bạn
Đây là khái niệm cứu mạng mà mọi BA phải biết trước khi gõ DML. Một transaction cho phép bạn nhóm các thay đổi lại, xem trước kết quả, rồi mới quyết định commit (xác nhận) hay rollback (hủy bỏ):
BEGIN; -- bắt đầu transactionUPDATE don_hang
SET trang_thai = 'huy'
WHERE ma_khach = 'KH00123';
-- Kiểm tra: lệnh trên có ảnh hưởng đúng số dòng mong đợi không?
-- Nếu sai → ROLLBACK;
-- Nếu đúng → COMMIT;
Trong PostgreSQL và MySQL (InnoDB), nếu bạn ROLLBACK trước khi COMMIT, mọi thay đổi biến mất như chưa từng xảy ra. Đây là tấm lưới an toàn quan trọng nhất khi bạn buộc phải sửa dữ liệu.
Tình huống thực tế
Tình huống 1 — Sàn TMĐT mô phỏng kiểu Tiki: dựng dữ liệu test cho báo cáo cohort
Bạn là BA tại một công ty bán lẻ online. Team data engineer vừa dựng bảng cohort mới, và bạn cần kiểm tra báo cáo retention (đã học ở Bài 29) chạy đúng không. Nhưng môi trường staging trống trơn — không có đơn hàng nào để báo cáo có gì hiển thị.
Bạn được cấp quyền ghi trên schema sandbox. Bạn viết:
INSERT INTO sandbox.don_hang (ma_khach, ngay_dat, gia_tri)
VALUES
('KH001', '2026-01-05', 250000),
('KH001', '2026-02-10', 180000), -- KH001 quay lại tháng 2
('KH002', '2026-01-08', 500000), -- KH002 chỉ mua 1 lần
('KH003', '2026-01-15', 320000),
('KH003', '2026-03-20', 410000); -- KH003 quay lại tháng 3
Với 5 dòng được thiết kế có chủ đích, bạn biết chính xác kết quả retention phải ra: tháng 1 có 3 khách, tháng 2 giữ lại 1 (33%), tháng 3 giữ lại 1. Khi chạy báo cáo mà con số khớp, bạn yên tâm logic đúng.
Bài học rút ra: dữ liệu test do BA tự dựng có một sức mạnh mà dữ liệu thật không có — bạn biết trước đáp án. Đây là cách kiểm thử báo cáo đáng tin cậy nhất, và nó đòi hỏi bạn thành thạo INSERT. Luôn làm việc này trên schema sandbox/staging, không bao giờ trên production.
Tình huống 2 — Ví điện tử mô phỏng kiểu MoMo: fix dữ liệu sai qua change request
Một sự cố xảy ra: do lỗi tích hợp của một đối tác, 1.240 giao dịch nạp tiền trong ngày 25/06 bị gán sai loai_giao_dich = 'rut_tien' thay vì 'nap_tien'. Báo cáo dòng tiền vì thế lệch hẳn. Sếp giao bạn — BA hiểu nghiệp vụ nhất về luồng này — viết câu lệnh fix để kỹ sư review và chạy.
Bạn không chạy thẳng. Bạn làm theo quy trình change request an toàn. Trước tiên, đếm để biết phạm vi:
SELECT COUNT(*)
FROM giao_dich
WHERE doi_tac = 'PARTNER_X'
AND DATE(thoi_gian) = '2026-06-25'
AND loai_giao_dich = 'rut_tien'
AND so_tien > 0;
-- Kết quả: 1240 → khớp với con số sự cố báo cáo
Sau khi xác nhận đúng 1.240 dòng, bạn soạn câu UPDATE bọc trong transaction:
BEGIN;UPDATE giao_dich
SET loai_giao_dich = 'nap_tien'
WHERE doi_tac = 'PARTNER_X'
AND DATE(thoi_gian) = '2026-06-25'
AND loai_giao_dich = 'rut_tien'
AND so_tien > 0;
-- Kỹ sư kiểm tra: "UPDATE 1240" — đúng số dòng kỳ vọng → COMMIT;
-- Nếu hiện "UPDATE 5800" → có gì đó sai → ROLLBACK ngay.
Điểm tinh tế: mệnh đề WHERE của bạn dùng đúng các điều kiện đã đếm ở bước trên, không thêm không bớt. Con số dòng bị ảnh hưởng phải khớp tuyệt đối với con số bạn đã SELECT COUNT(*) ra trước đó.
Bài học rút ra: quy trình vàng để sửa dữ liệu là đếm trước — sửa sau — đối chiếu số dòng. Câu WHERE trong UPDATE phải y hệt câu WHERE trong SELECT COUNT(*) kiểm tra. Trong lĩnh vực fintech, mọi câu DML đụng đến tiền đều phải qua review và để lại dấu vết — đừng bao giờ tự ý chạy một mình.
Tình huống 3 — Ứng dụng gọi xe mô phỏng kiểu Be/Grab: dọn dữ liệu rác trong staging
Trong môi trường staging của app gọi xe, suốt mấy tháng test, bảng chuyen_di tích tụ hàng chục nghìn chuyến giả do automation test sinh ra, khiến môi trường nặng nề và báo cáo demo cho khách hàng bị nhiễu. Bạn được giao dọn dẹp các chuyến test cũ.
Trước khi DELETE, bạn luôn "thử" bằng SELECT với đúng mệnh đề WHERE định dùng:
-- Bước 1: XEM TRƯỚC thứ sẽ bị xóa
SELECT COUNT(*), MIN(thoi_gian_dat), MAX(thoi_gian_dat)
FROM chuyen_di
WHERE la_chuyen_test = TRUE
AND thoi_gian_dat < '2026-04-01';
-- Kết quả: 48.500 dòng, từ 2025-11 đến 2026-03 → hợp lý
Chỉ khi con số và khoảng thời gian trông hợp lý, bạn mới đổi SELECT thành DELETE, vẫn bọc transaction:
BEGIN;
DELETE FROM chuyen_di
WHERE la_chuyen_test = TRUE
AND thoi_gian_dat < '2026-04-01';
-- "DELETE 48500" → khớp → COMMIT;
Bài học rút ra: với DELETE, kỹ thuật "viết SELECT trước, đổi thành DELETE sau" là tấm khiên hiệu quả nhất. Bạn nhìn tận mắt thứ mình sắp xóa trước khi xóa thật. Điều kiện la_chuyen_test = TRUE cũng cho thấy giá trị của việc thiết kế dữ liệu có cờ phân biệt test/thật ngay từ đầu.
Hướng dẫn từng bước
Đây là quy trình an toàn bạn nên áp dụng mỗi khi buộc phải chạy DML:
- Xác nhận đúng môi trường. Nhìn kỹ bạn đang kết nối tới database nào.
sandbox/stagingthì thoải mái;productionthì dừng lại và tự hỏi mình có thực sự nên là người chạy không. Tốt nhất là dùng connection riêng chỉ-staging để tránh nhầm.
- Viết
SELECTthăm dò trước. VớiUPDATE/DELETE, luôn chạySELECT COUNT()(hoặcSELECTxem mẫu) với chính xác mệnh đềWHEREbạn định dùng. Ghi nhớ con số dòng.
- Mở transaction bằng
BEGIN;. Đừng để DML "trần" không có lưới an toàn.
- Chạy câu DML. Đọc kỹ thông báo: "UPDATE 1240", "DELETE 48500". So với con số ở bước 2.
- Đối chiếu rồi quyết định. Số dòng khớp kỳ vọng →
COMMIT;. Lệch một chút thôi →ROLLBACK;và điều tra lạiWHERE.
- Kiểm tra hậu kỳ. Sau
COMMIT, chạy lại mộtSELECTđể xác nhận dữ liệu giờ đã đúng như mong muốn.
- Để lại dấu vết. Ghi vào change ticket: câu lệnh đã chạy, số dòng ảnh hưởng, thời điểm, người review. Trong môi trường chuyên nghiệp đây không phải tùy chọn.
Lỗi thường gặp & mẹo
Quên mệnh đề WHERE. Đây là tai nạn kinh điển và tàn khốc nhất. UPDATE don_hang SET trang_thai = 'huy'; sẽ hủy mọi đơn hàng trong hệ thống. Mẹo: tập thói quen gõ WHERE ngay sau khi gõ tên bảng, trước cả khi gõ SET. Một số công cụ (như MySQL Workbench) có "safe update mode" chặn UPDATE/DELETE không có WHERE trên khóa chính — hãy bật nó lên.
Chạy DML mà không có transaction. Không BEGIN/COMMIT nghĩa là sai một phát là mất luôn, không cứu được. Luôn bọc transaction khi sửa dữ liệu thật.
WHERE của UPDATE rộng hơn WHERE của SELECT kiểm tra. Bạn SELECT COUNT(*) ra 1.240 nhưng câu UPDATE lại thiếu một điều kiện nên đụng 5.800 dòng. Luôn copy-paste y hệt mệnh đề WHERE giữa hai câu, đừng gõ lại từ trí nhớ.
Nhầm NULL trong điều kiện. WHERE thanh_pho != 'Hà Nội' sẽ bỏ sót các dòng có thanh_pho là NULL, vì so sánh với NULL luôn ra "unknown". Nếu muốn gồm cả NULL, viết WHERE thanh_pho != 'Hà Nội' OR thanh_pho IS NULL. (Bạn đã học NULL handling ở Bài 7 — nó cực kỳ quan trọng trong DML.)
Đụng phải ràng buộc khóa ngoại khi DELETE. Xóa một khách hàng đang có đơn hàng tham chiếu tới sẽ bị database từ chối (foreign key constraint), hoặc tệ hơn — nếu cấu hình CASCADE — sẽ xóa lan sang cả các bảng con. Hiểu quan hệ bảng trước khi xóa.
Mẹo vàng: với BA, đường an toàn nhất để "sửa data" thường không phải tự chạy UPDATE, mà là viết câu lệnh đã được kiểm chứng rồi gửi cho data engineer chạy qua change request. Bạn đóng góp hiểu biết nghiệp vụ (biết cái gì sai và phải đổi thành gì), họ đóng góp sự an toàn vận hành. Đây là sự phân vai lành mạnh nhất.
Bài tập thực hành
Hãy thực hành trên một schema sandbox (đừng dùng dữ liệu thật):
- INSERT: Tạo bảng
khach_hang_testvà chèn 5 khách hàng với các thành phố khác nhau, trong đó có ít nhất một dòng để trống cộtsdt(tứcNULL). Dùng cú pháp chèn nhiều dòng trong một câu lệnh.
- INSERT từ SELECT: Giả sử có bảng
khach_hangthật, viết câu lệnh chép 50 khách ở TP.HCM sang bảngkhach_hang_test.
- UPDATE an toàn: Bạn cần đổi
trang_thaicủa tất cả đơn hàng "cho_xac_nhan" tạo trước ngày 01/06/2026 thành "qua_han". Viết đủ ba câu: (a)SELECT COUNT(*)thăm dò, (b)BEGIN;+UPDATE, (c) câu kiểm tra sau khi đổi. Tự hỏi: nếu số dòng không khớp thì bạn làm gì?
- DELETE có xem trước: Viết một câu
SELECTxem trước các dòng test cũ sẽ bị xóa, rồi chuyển thànhDELETEtương ứng, bọc trong transaction.
- Tình huống xử lý: Một đồng nghiệp gửi bạn câu lệnh
DELETE FROM nguoi_dung WHERE trang_thai = 'inactive'để review. Hãy chỉ ra ít nhất 2 câu hỏi an toàn bạn cần đặt ra trước khi đồng ý cho chạy (gợi ý: môi trường nào? đã đếm số dòng chưa? có khóa ngoại không? có transaction không?).
Tóm tắt
DML — INSERT, UPDATE, DELETE — là nhóm câu lệnh thay đổi dữ liệu, khác hẳn với SELECT chỉ đọc mà bạn dùng hằng ngày. Là BA, bạn không cần dùng DML thường xuyên, nhưng bạn phải hiểu chúng để dựng dữ liệu test trong sandbox, viết câu lệnh fix data qua change request, và đọc hiểu được câu lệnh kỹ sư sắp chạy.
Ba điều cốt lõi cần khắc ghi: một, UPDATE/DELETE mà thiếu WHERE sẽ tàn phá cả bảng — luôn gõ WHERE trước. Hai, quy trình vàng là đếm trước, sửa sau, đối chiếu số dòng, với mệnh đề WHERE y hệt nhau giữa câu kiểm tra và câu thực thi. Ba, luôn bọc thao tác trong transaction (BEGIN … COMMIT/ROLLBACK) để có lưới an toàn. Và mẹo lớn nhất: trên dữ liệu production, con đường khôn ngoan của BA thường là viết câu lệnh, không tự chạy — bạn góp hiểu biết nghiệp vụ, để kỹ sư đảm bảo an toàn vận hành. Hiểu DML không phải để bạn thành người xóa nhầm dữ liệu, mà để bạn thành người ngăn việc đó xảy ra.