Buổi 4: Kiểu dữ liệu và hàm xử lý dữ liệu
Dữ liệu trong công ty chứng khoán hiếm khi "sạch" ngay để đưa vào báo cáo: mã khách hàng cần tách phần số, tên cần chuẩn hóa chữ hoa, ngày đăng ký cần đổi thành "đã tham gia bao nhiêu ngày", số dư cần phân loại VIP/Thường, giá cần làm tròn... Buổi này trang bị bộ hàm dựng sẵn để biến dữ liệu thô thành thông tin dùng được — và quan trọng không kém, biết chỗ SQL Server và MySQL khác nhau để không bị "ngã" khi chuyển hệ.
Toàn bộ ví dụ chạy trên bộ dữ liệu mẫu của khóa (xem mục "Bộ dữ liệu mẫu của khóa học" ở Buổi 1). "Hôm nay" cố định là 2026-09-25: trong tài liệu và bài chấm, ta viết thẳng '2026-09-25' thay vì GETDATE()/NOW(), để kết quả lặp lại được ở mọi lần chạy.
Mẹo: Buổi này thiên về bảng tra cứu. Hãy giữ bảng so sánh hai hệ ở từng mục bên cạnh khi làm bài, và bấm "Chạy thử" trên từng ví dụ để thấy kết quả thật.
Mục tiêu buổi học
- Sau buổi này bạn có thể chọn đúng kiểu dữ liệu (số nguyên, số thực, chuỗi, ngày giờ, boolean) cho dữ liệu chứng khoán và chuyển kiểu bằng
CAST. - Sau buổi này bạn có thể dùng hàm chuỗi: độ dài, cắt/nối chuỗi, chữ hoa/thường,
TRIM,REPLACE, tìm vị trí ký tự. - Sau buổi này bạn có thể dùng hàm ngày giờ: cộng/trừ ngày, chênh lệch ngày,
YEAR/MONTH/DAY, định dạng ngày; tính số ngày tham gia, tuổi. - Sau buổi này bạn có thể dùng hàm toán học:
ABS,ROUND,CEILING,FLOOR,POWER,SQRT,MOD. - Sau buổi này bạn có thể dùng hàm điều kiện
CASE,IF/IIF,COALESCE/IFNULL/ISNULLvà nhận biết khác biệt giữa SQL Server và MySQL.
Bản đồ buổi học
Buổi học 3 giờ (180 phút), khoảng 32% lý thuyết + demo (~57 phút) và 68% thực hành (~123 phút, gồm cả thời gian chữa bài). Mỗi phần lý thuyết được xen kẽ ngay bằng bài thực hành tương ứng: học đến đâu làm đến đó. Phần lý thuyết chủ yếu là bảng tra cứu nên được giảng gọn — hãy dùng bảng so sánh hai hệ làm tài liệu mở sẵn khi làm bài.
| Phần | Nội dung | Thời lượng gợi ý |
|---|---|---|
| 1 | Các kiểu dữ liệu phổ biến, CAST |
14 phút (lý thuyết + demo) |
| 2 | Hàm xử lý chuỗi | 10 phút (lý thuyết + demo) |
| Thực hành | Bài 4.1 — Xử lý chuỗi (2 bài) | 21 phút |
| 3 | Hàm xử lý ngày giờ | 11 phút (lý thuyết + demo) |
| Thực hành | Bài 4.2 — Xử lý ngày giờ (5 bài) | 40 phút |
| 4 | Hàm toán học | 6 phút (lý thuyết + demo) |
| Thực hành | Bài 4.4 — Hàm toán học (bổ sung) (2 bài) | 24 phút |
| 5 | Hàm điều kiện: CASE, IF/IIF, COALESCE |
9 phút (lý thuyết + demo) |
| Thực hành | Bài 4.3 — Sử dụng IF và CASE (3 bài, có 2 bài khó) | 38 phút |
| 6 | Tổng kết và bảng tra cứu hai hệ | 7 phút |
| Tổng | 180 phút — lý thuyết + demo ~57 phút, thực hành ~123 phút (68%) |
1. Các kiểu dữ liệu phổ biến
Chọn kiểu dữ liệu đúng quyết định độ chính xác của phép tính, dung lượng lưu trữ và cả hiệu năng truy vấn.
1.1. Số nguyên
| Kiểu | SQL Server | MySQL | Dùng cho |
|---|---|---|---|
TINYINT |
0..255 | -128..127 (hoặc UNSIGNED 0..255) |
Cờ trạng thái nhỏ, mã loại |
SMALLINT |
±32.767 | ±32.767 | Số lượng nhỏ |
INT |
±2,1 tỷ | ±2,1 tỷ | Khóa chính, số lượng lệnh (quantity) |
BIGINT |
±9,2 × 10^18 | ±9,2 × 10^18 | Khối lượng giao dịch tích lũy (volume), số dư cực lớn |
Trong bộ dữ liệu: Customers.customer_id, Orders.quantity là INT; MarketPrices.volume là BIGINT vì tổng khối lượng cả thị trường có thể vượt 2,1 tỷ.
Phép chia số nguyên là bẫy kinh điển. Cùng một biểu thức 7 / 2, hai hệ cho hai kết quả:
SQL Server — cả hai vế là số nguyên thì kết quả nguyên, cắt phần thập phân:
SELECT 7 / 2 AS ket_qua;3MySQL — toán tử / luôn cho số thập phân (muốn chia nguyên dùng DIV):
SELECT 7 / 2 AS ket_qua;3.5000Để có cùng kết quả ở cả hai hệ, viết một vế dạng thập phân (7 / 2.0, hoặc 100.0 * a / b) rồi ép kiểu kết quả:
SELECT CAST(7 / 2.0 AS DECIMAL(18,2)) AS chia_thap_phan,
CAST(100.0 * 3 / 8 AS DECIMAL(18,2)) AS phan_tram;3.50 37.50Lưu ý: Quy tắc của khóa học: mọi tỷ lệ/phần trăm đều nhân
100.0(hoặc chiax.0) và bọcCAST(ROUND(..., 2) AS DECIMAL(18,2)).
1.2. Số thực
| Kiểu | Bản chất | Dùng khi |
|---|---|---|
DECIMAL(p, s) / NUMERIC(p, s) |
Số thập phân chính xác: tối đa p chữ số, trong đó s chữ số sau dấu phẩy |
Tiền, giá, số dư, tỷ lệ — luôn ưu tiên |
FLOAT, DOUBLE (MySQL) / FLOAT, REAL (SQL Server) |
Số dấu phẩy động xấp xỉ (sai số ~1e-15) | Tính toán khoa học, thống kê (căn, lũy thừa) |
Ví dụ của khóa: Accounts.balance là DECIMAL(18,2), MarketPrices.close_price là DECIMAL(12,2), CustomerStats.total_trade_value là DECIMAL(20,2).
Không bao giờ lưu tiền bằng FLOAT. Hãy so sánh 0.1 + 0.2 với 0.3 bằng hai kiểu (hằng có hậu tố e0 là số dấu phẩy động):
SELECT CASE WHEN 0.1e0 + 0.2e0 = 0.3e0 THEN 'bang nhau' ELSE 'khac nhau' END AS kieu_float,
CASE WHEN CAST(0.1 AS DECIMAL(10,2)) + CAST(0.2 AS DECIMAL(10,2)) = CAST(0.3 AS DECIMAL(10,2))
THEN 'bang nhau' ELSE 'khac nhau' END AS kieu_decimal;khac nhau bang nhauDấu phẩy động cho kết quả khác nhau — sai lệch nhỏ nhưng cộng hàng triệu dòng sẽ thành sai lệch tiền thật. Chỉ dùng FLOAT/DOUBLE cho bước tính trung gian như SQRT, POWER (mục 4).
1.3. Chuỗi
| Kiểu | Đặc điểm |
|---|---|
CHAR(n) |
Độ dài cố định n, tự đệm khoảng trắng (mã cố định như 'HNX') |
VARCHAR(n) |
Độ dài thay đổi tối đa n (tên, email, mã khách hàng) |
TEXT (MySQL) / VARCHAR(MAX) (SQL Server) |
Văn bản dài (ghi chú) |
NVARCHAR(n) (SQL Server) |
Chuỗi Unicode (tiếng Việt có dấu). MySQL dùng VARCHAR với bộ ký tự utf8mb4 |
Dữ liệu mẫu của khóa không dấu (Ha Noi, Nguyen Van An) để cùng chạy đúng ở mọi cấu hình, nhưng khi làm việc thật với tiếng Việt có dấu trong SQL Server, hằng chuỗi phải viết dạng N'Nguyễn Văn An' (tiền tố N), nếu không dấu bị mất.
Kiểu chuỗi ngắn (customer_code NVARCHAR(10), symbol NVARCHAR(10)) và ràng buộc UNIQUE trên mã khách hàng cho thấy: mã định danh nên là chuỗi có độ dài chuẩn hóa, không phải số, vì có thể chứa tiền tố (KH) và số 0 đứng đầu (KH000123).
1.4. Ngày giờ
| Kiểu | Nội dung | Ghi chú |
|---|---|---|
DATE |
Chỉ ngày yyyy-mm-dd |
created_date, trade_date, birth_date — dùng kiểu này cho bài chấm |
TIME |
Chỉ giờ | |
DATETIME |
Ngày + giờ | SQL Server: độ chính xác ~3ms; MySQL: đến giây |
TIMESTAMP |
MySQL: thời điểm, tự đổi theo múi giờ phiên làm việc, giới hạn đến năm 2038. SQL Server: kiểu timestamp là bộ đếm phiên bản dòng, không phải ngày giờ (dùng DATETIME2) |
Cẩn thận khi chuyển hệ |
Cột DATE so sánh trực tiếp với chuỗi dạng 'yyyy-mm-dd' và in ra đúng định dạng đó ở cả hai hệ:
SELECT customer_id, created_date
FROM Customers
WHERE created_date >= '2026-09-01'
ORDER BY customer_id;5 2026-09-23
12 2026-09-19
19 2026-09-14
26 2026-09-23AuditLog.changed_at là DATETIME có DEFAULT GETDATE() (MySQL: CURRENT_TIMESTAMP) — ta sẽ dùng nó ở phần trigger. Trong bài chấm tự động, chỉ dùng cột DATE, vì thời điểm hiện tại luôn thay đổi.
1.5. Boolean
- MySQL: có
BOOLEAN(thực chất làTINYINT(1)), với hằngTRUE/FALSE=1/0, và có thểSELECTtrực tiếp một phép so sánh. - SQL Server: không có kiểu boolean thật; dùng
BIT(giá trị0,1,NULL). Điều kiện không phải biểu thức giá trị nên phải bọc trongCASE WHEN.
MySQL:
SELECT TRUE AS dung, FALSE AS sai, 5 > 3 AS so_sanh;1 0 1SQL Server:
SELECT CAST(1 AS BIT) AS cot_bit,
CASE WHEN 5 > 3 THEN 1 ELSE 0 END AS so_sanh;1 1Cách viết chung cho hai hệ: dùng TINYINT/BIT với 1/0 và luôn so sánh tường minh (WHERE is_active = 1).
1.6. Chuyển kiểu bằng CAST
CAST(biểu_thức AS kiểu) chuyển giá trị sang kiểu khác. Tên kiểu đích khác nhau giữa hai hệ:
| Mục đích | SQL Server | MySQL |
|---|---|---|
| Sang số nguyên | CAST(x AS INT) |
CAST(x AS SIGNED) — AS INT báo lỗi |
| Sang số thập phân | CAST(x AS DECIMAL(18,2)) |
CAST(x AS DECIMAL(18,2)) |
| Sang chuỗi | CAST(x AS VARCHAR(10)) |
CAST(x AS CHAR(10)) |
| Sang ngày | CAST(x AS DATE) |
CAST(x AS DATE) |
Dạng khác của CAST |
CONVERT(INT, x) |
CONVERT(x, SIGNED) |
DECIMAL(18,2) và DATE dùng chung được ở cả hai hệ nên là lựa chọn ưu tiên khi viết SQL "đa nền tảng".
SQL Server — ép số thập phân sang số nguyên bằng INT thì cắt bỏ phần lẻ:
SELECT CAST(95680.50 AS DECIMAL(10,0)) AS sang_decimal,
CAST(27270.50 AS INT) AS sang_int;95681 27270MySQL — ép sang SIGNED thì làm tròn:
SELECT CAST(95680.50 AS DECIMAL(10,0)) AS sang_decimal,
CAST(27270.50 AS SIGNED) AS sang_signed;95681 27271SQL Server vs MySQL:
CAST(27270.50 AS INT)cho 27270 ở SQL Server nhưngCAST(27270.50 AS SIGNED)cho 27271 ở MySQL. Muốn kết quả giống nhau ở cả hai hệ, làm tròn tường minh:CAST(ROUND(x, 0) AS DECIMAL(18,0)).
CAST cũng dùng để biến chuỗi thành số, ví dụ lấy phần số của mã khách hàng (KH100007 → 100007):
SQL Server:
SELECT customer_id, CAST(SUBSTRING(customer_code, 3, 6) AS INT) AS so_ma
FROM Customers WHERE customer_id IN (1, 2) ORDER BY customer_id;1 100007
2 100014MySQL:
SELECT customer_id, CAST(SUBSTRING(customer_code, 3, 6) AS SIGNED) AS so_ma
FROM Customers WHERE customer_id IN (1, 2) ORDER BY customer_id;1 100007
2 100014Cảnh báo: Chuyển kiểu ngầm (implicit conversion) xảy ra khi so sánh cột chuỗi với hằng số, ví dụ
WHERE customer_code = 100007. SQL Server cố đổi cả cột'KH100007'sang số và báo lỗiConversion failed when converting the nvarchar value 'KH100007' to data type int. MySQL lặng lẽ đổi chuỗi thành số ('KH100007'thành 0) nên câu lệnh không lỗi nhưng trả về rỗng — dễ bị bỏ sót hơn. Luôn so sánh cùng kiểu:WHERE customer_code = 'KH100007'.
1.7. Chọn kiểu nào cho dữ liệu chứng khoán?
| Dữ liệu | Kiểu nên dùng | Lý do |
|---|---|---|
Giá cổ phiếu (close_price) |
DECIMAL(12,2) |
Chính xác đến đồng, không sai số |
| Số dư, giá trị giao dịch | DECIMAL(18,2) / DECIMAL(20,2) |
Đủ lớn cho hàng nghìn tỷ đồng |
| Tỷ lệ phí (0,05%) | DECIMAL(6,4) |
Giữ đúng 4 chữ số thập phân |
Khối lượng khớp (volume) |
BIGINT |
Tổng cả thị trường có thể vượt 2,1 tỷ |
| Số lượng trong một lệnh | INT |
Đủ dùng, tiết kiệm |
| Mã chứng khoán, mã khách hàng | VARCHAR(10) / NVARCHAR(10) |
Có chữ và số 0 đứng đầu |
| Ngày giao dịch, ngày sinh | DATE |
Không cần giờ |
| Cờ đúng/sai (đã xác thực?) | BIT (SQL Server) / TINYINT(1) (MySQL) |
Không có boolean thật ở SQL Server |
Ví dụ nghiệp vụ: Nếu lưu
close_pricebằngFLOAT, giá trị danh mục 10 triệu dòng có thể lệch vài đồng so với sổ sách của bộ phận kế toán — và lệch này không tái lập được. VớiDECIMAL(12,2)số liệu luôn khớp.
Sai lầm thường gặp: dùng FLOAT cho tiền; để chuỗi so sánh với số (chuyển kiểu ngầm, ở trên); quên rằng INT * INT có thể tràn (quantity * price nếu cả hai là INT lớn) — hãy ép một vế sang BIGINT/DECIMAL trước khi nhân.
2. Hàm xử lý chuỗi
Ví dụ nền: customer_code dạng KH100007 (2 chữ + 6 số), full_name như Nguyen Van An, email như [email protected].
2.1. Độ dài, cắt, nối, đổi hoa/thường
| Nhu cầu | SQL Server | MySQL | Ví dụ → kết quả |
|---|---|---|---|
| Độ dài (số ký tự) | LEN(s) |
CHAR_LENGTH(s) |
'Nguyen Van An' → 13 |
| Cắt N ký tự cuối / đầu | RIGHT(s, n) / LEFT(s, n) |
giống | RIGHT('KH100007', 3) → 007 |
| Lấy đoạn giữa | SUBSTRING(s, bắt_đầu, độ_dài) |
SUBSTRING(s, bắt_đầu[, độ_dài]) |
SUBSTRING('KH100007', 3, 6) → 100007 |
| Nối chuỗi | CONCAT(a, b, ...) hoặc a + b |
CONCAT(a, b, ...) |
|
| Chữ hoa / thường | UPPER(s) / LOWER(s) |
giống | |
| Cắt khoảng trắng đầu-cuối | TRIM(s) (từ 2017), hoặc LTRIM(RTRIM(s)) |
TRIM(s) |
TRIM(' abc ') → abc |
| Thay thế | REPLACE(s, cũ, mới) |
giống | |
| Vị trí ký tự (đếm từ 1, 0 = không có) | CHARINDEX(cần_tìm, s) |
INSTR(s, cần_tìm) (hoặc LOCATE(cần_tìm, s)) |
INSTR('Nguyen Van An', ' ') → 7 |
Kết quả thực tế trên bộ dữ liệu (chạy giống nhau ở cả hai hệ):
SELECT customer_id, customer_code,
RIGHT(customer_code, 3) AS r3,
SUBSTRING(customer_code, 3, 6) AS so,
UPPER(full_name) AS hoa,
LOWER(full_name) AS thuong
FROM Customers WHERE customer_id IN (1, 6, 31)
ORDER BY customer_id;1 KH100007 007 100007 NGUYEN VAN AN nguyen van an
6 KH100042 042 100042 VU THI HOA vu thi hoa
31 KH100217 217 100217 NGAN HANG TMCP DAU TU VIET ngan hang tmcp dau tu vietTRIM, LTRIM, RTRIM bỏ khoảng trắng thừa (dữ liệu nhập tay thường có) và LEFT lấy các ký tự đầu:
SELECT TRIM(' KH100007 ') AS da_trim,
LTRIM(' abc') AS cat_trai,
RTRIM('abc ') AS cat_phai,
LEFT('KH100007', 2) AS tien_to;KH100007 abc abc KHREPLACE dùng để chuẩn hóa tên công ty:
SELECT customer_id, REPLACE(full_name, 'Cong ty CP', 'CTCP') AS ten_gon
FROM Customers WHERE customer_id IN (35, 38)
ORDER BY customer_id;35 CTCP Chung khoan An Binh
38 CTCP Xay dung Song HongGhép LEFT, RIGHT và CONCAT để che số điện thoại khi in báo cáo:
SELECT customer_id, CONCAT(LEFT(phone, 4), '****', RIGHT(phone, 3)) AS sdt_che
FROM Customers WHERE customer_id IN (1, 2, 3)
ORDER BY customer_id;1 0910****137
2 0910****274
3 0910****4112.2. Ba khác biệt SQL Server vs MySQL cần thuộc
(1) LEN khác CHAR_LENGTH khi có khoảng trắng cuối.
SQL Server — LEN bỏ qua khoảng trắng cuối:
SELECT LEN('KH100007 ') AS do_dai;8MySQL — CHAR_LENGTH đếm cả khoảng trắng; LENGTH đếm byte, không phải ký tự:
SELECT CHAR_LENGTH('KH100007 ') AS char_length, LENGTH('KH100007') AS length_byte;10 8Với tiếng Việt có dấu trong MySQL, LENGTH('Đà Nẵng') lớn hơn số ký tự thật; luôn dùng CHAR_LENGTH. Cách chung: TRIM trước khi đếm.
(2) CONCAT xử lý NULL khác nhau. Cột email của khách 6 là NULL:
SQL Server — coi NULL là chuỗi rỗng:
SELECT customer_id, CONCAT(full_name, ' <', email, '>') AS voi_email
FROM Customers WHERE customer_id IN (5, 6) ORDER BY customer_id;5 Hoang Van Em <[email protected]>
6 Vu Thi Hoa <>MySQL — trả về NULL nếu bất kỳ đối số nào là NULL:
SELECT customer_id, CONCAT(full_name, ' <', email, '>') AS voi_email
FROM Customers WHERE customer_id IN (5, 6) ORDER BY customer_id;5 Hoang Van Em <[email protected]>
6 NULLCách an toàn cho cả hai: CONCAT(full_name, ' <', COALESCE(email, ''), '>').
SELECT customer_id, CONCAT(full_name, ' <', COALESCE(email, ''), '>') AS an_toan
FROM Customers WHERE customer_id IN (5, 6) ORDER BY customer_id;5 Hoang Van Em <[email protected]>
6 Vu Thi Hoa <>(3) Vị trí chuỗi con. T-SQL CHARINDEX(cần_tìm, s) — thứ tự tham số ngược với MySQL INSTR(s, cần_tìm). Cả hai đều trả 0 nếu không tìm thấy.
SQL Server:
SELECT CHARINDEX(' ', 'Nguyen Van An') AS co_khoang_trang, CHARINDEX('@', 'abc') AS khong_tim_thay;7 0MySQL:
SELECT INSTR('Nguyen Van An', ' ') AS co_khoang_trang, INSTR('abc', '@') AS khong_tim_thay;7 0SQL Server vs MySQL:
SUBSTRINGvới độ dài âm sẽ lỗi ở T-SQL nhưng trả chuỗi rỗng ở MySQL — nên kiểm tra= 0trước bằngCASE. Ngoài ra SQL Server bắt buộc có tham số độ dài trongSUBSTRING, MySQL thì không.
2.3. Ứng dụng: tách mã khách hàng, định dạng tên
Lấy phần số của mã (KH100007 → 100007) và 3 số cuối: RIGHT(customer_code, 3). Tách họ và phần tên ở khoảng trắng đầu tiên:
SQL Server:
SELECT customer_id,
SUBSTRING(full_name, 1, CHARINDEX(' ', full_name) - 1) AS ho,
SUBSTRING(full_name, CHARINDEX(' ', full_name) + 1, 200) AS phan_con_lai
FROM Customers WHERE customer_id IN (1, 2, 3) ORDER BY customer_id;1 Nguyen Van An
2 Tran Thi Binh
3 Le Van CuongMySQL:
SELECT customer_id,
SUBSTRING(full_name, 1, INSTR(full_name, ' ') - 1) AS ho,
SUBSTRING(full_name, INSTR(full_name, ' ') + 1) AS phan_con_lai
FROM Customers WHERE customer_id IN (1, 2, 3) ORDER BY customer_id;1 Nguyen Van An
2 Tran Thi Binh
3 Le Van CuongTên chỉ có một từ (không có khoảng trắng) làm INSTR = 0 → độ dài -1: hãy bọc bằng CASE WHEN ... = 0 THEN ... ELSE ... END.
Tách tên miền email (phần sau @), an toàn với NULL nhờ hàm của SQL đã trả NULL khi đầu vào NULL:
SQL Server:
SELECT customer_id, SUBSTRING(email, CHARINDEX('@', email) + 1, 100) AS mien
FROM Customers WHERE customer_id IN (5, 6) ORDER BY customer_id;5 example.com
6 NULLMySQL:
SELECT customer_id, SUBSTRING(email, INSTR(email, '@') + 1) AS mien
FROM Customers WHERE customer_id IN (5, 6) ORDER BY customer_id;5 example.com
6 NULLSai lầm thường gặp: đếm độ dài bằng LENGTH trong MySQL (đếm byte); nối chuỗi bằng + trong SQL Server khi có NULL (cả biểu thức thành NULL); quên rằng SUBSTRING đếm vị trí từ 1, không phải từ 0; so sánh chuỗi mà quên rằng hoa/thường thường không phân biệt với collation mặc định.
Thực hành ngay: bài 4.1
Vừa học xong RIGHT, UPPER, LEN/CHAR_LENGTH, TRIM và tách chuỗi bằng CHARINDEX/INSTR, hãy làm nhóm bài 4.1 để trích mã khách hàng, chuẩn hóa tên và ghép nhãn thư từ.
3. Hàm xử lý ngày giờ
3.1. Ngày giờ hiện tại — và vì sao khóa học không dùng
| Nhu cầu | SQL Server | MySQL |
|---|---|---|
| Ngày giờ hiện tại | GETDATE(), SYSDATETIME() |
NOW() |
| Chỉ ngày hiện tại | CAST(GETDATE() AS DATE) |
CURDATE() |
| Chỉ giờ hiện tại | CAST(GETDATE() AS TIME) |
CURTIME() |
Các hàm này trả về giá trị thay đổi mỗi lần chạy, nên chấm tự động không thể so kết quả cố định. Trong khóa học, hãy thay bằng ngày cố định '2026-09-25'. Trong hệ thống thật thì dùng hàm tương ứng ở bảng trên.
3.2. Trích thành phần của ngày
YEAR(d), MONTH(d), DAY(d) giống nhau ở cả hai hệ. Ngày trong tuần:
| SQL Server | MySQL | |
|---|---|---|
| Thứ (số) | DATEPART(weekday, d) |
DAYOFWEEK(d) |
| Tên thứ | DATENAME(weekday, d) |
DAYNAME(d) |
| Quý | DATEPART(quarter, d) |
QUARTER(d) |
SQL Server:
SELECT DATENAME(weekday, '2021-06-29') AS ten_thu,
DATEPART(weekday, '2021-06-29') AS so_thu,
DATEPART(quarter, '2026-09-25') AS quy,
EOMONTH('2026-09-25') AS cuoi_thang;Tuesday 3 3 2026-09-30MySQL:
SELECT DAYNAME('2021-06-29') AS ten_thu,
DAYOFWEEK('2021-06-29') AS so_thu,
QUARTER('2026-09-25') AS quy,
LAST_DAY('2026-09-25') AS cuoi_thang;Tuesday 3 3 2026-09-30Với cấu hình mặc định của khóa, Chủ nhật = 1, thứ Hai = 2, ...; '2021-06-29' là Tuesday (3).
Lưu ý: Ở SQL Server kết quả
DATEPART(weekday, ...)phụ thuộc thiết lậpDATEFIRSTcủa phiên — không nên dựa vào số thứ tự trong báo cáo quan trọng; dùngDATENAME/DAYNAMEhoặc đặt rõSET DATEFIRST.
3.3. Cộng/trừ ngày và chênh lệch ngày
| Nhu cầu | SQL Server | MySQL |
|---|---|---|
| Cộng ngày | DATEADD(day, 30, d) |
DATE_ADD(d, INTERVAL 30 DAY) |
| Trừ ngày | DATEADD(day, -30, d) |
DATE_SUB(d, INTERVAL 30 DAY) |
| Cộng tháng/năm | DATEADD(month, 1, d), DATEADD(year, 1, d) |
DATE_ADD(d, INTERVAL 1 MONTH) |
| Số ngày giữa hai ngày | DATEDIFF(day, ngày_đầu, ngày_cuối) |
DATEDIFF(ngày_cuối, ngày_đầu) |
| Chênh lệch theo tháng/năm | DATEDIFF(month, đầu, cuối) |
TIMESTAMPDIFF(MONTH, đầu, cuối) |
| Cuối tháng | EOMONTH(d) |
LAST_DAY(d) |
Cảnh báo: Thứ tự tham số
DATEDIFFcủa hai hệ ngược nhau, và MySQLDATEDIFFchỉ nhận 2 tham số (đơn vị là ngày). Ví dụ số ngày khách hàng đã tham gia:
SQL Server:
SELECT customer_id, created_date,
DATEDIFF(day, created_date, '2026-09-25') AS so_ngay,
DATEADD(day, 30, created_date) AS sau_30_ngay
FROM Customers WHERE customer_id IN (1, 5) ORDER BY customer_id;1 2021-06-29 1914 2021-07-29
5 2026-09-23 2 2026-10-23MySQL:
SELECT customer_id, created_date,
DATEDIFF('2026-09-25', created_date) AS so_ngay,
DATE_ADD(created_date, INTERVAL 30 DAY) AS sau_30_ngay
FROM Customers WHERE customer_id IN (1, 5) ORDER BY customer_id;1 2021-06-29 1914 2021-07-29
5 2026-09-23 2 2026-10-23Cộng tháng và lấy ngày cuối tháng cho ngày đăng ký (dùng khi tính kỳ tính phí):
SQL Server:
SELECT customer_id, DATEADD(month, 1, created_date) AS thang_sau,
EOMONTH(created_date) AS cuoi_thang
FROM Customers WHERE customer_id IN (1, 5) ORDER BY customer_id;1 2021-07-29 2021-06-30
5 2026-10-23 2026-09-30MySQL:
SELECT customer_id, DATE_ADD(created_date, INTERVAL 1 MONTH) AS thang_sau,
LAST_DAY(created_date) AS cuoi_thang
FROM Customers WHERE customer_id IN (1, 5) ORDER BY customer_id;1 2021-07-29 2021-06-30
5 2026-10-23 2026-09-30Bẫy kiểu dữ liệu trả về của DATEADD: khi đối số là chuỗi ('2026-09-25'), T-SQL DATEADD trả về DATETIME. Cột kiểu DATE thì trả về DATE. Muốn in đúng ngày, bọc CAST(... AS DATE):
SQL Server:
SELECT DATEADD(day, -30, '2026-09-25') AS tra_datetime,
CAST(DATEADD(day, -30, '2026-09-25') AS DATE) AS tra_date;2026-08-26 00:00:00.000 2026-08-26MySQL:
SELECT DATE_SUB('2026-09-25', INTERVAL 30 DAY) AS ba_muoi_ngay_truoc;2026-08-26Bẫy DATEDIFF(year, ...) của SQL Server: nó đếm số mốc 31/12–1/1 đã đi qua, không phải số năm tròn. Hai ngày chỉ cách nhau một ngày vẫn cho 1 năm chênh lệch:
SQL Server:
SELECT DATEDIFF(year, '2025-12-31', '2026-01-01') AS so_nam,
DATEDIFF(month, '2026-08-31', '2026-09-01') AS so_thang;1 1MySQL — TIMESTAMPDIFF chỉ đếm các đơn vị tròn:
SELECT TIMESTAMPDIFF(YEAR, '2025-12-31', '2026-01-01') AS so_nam,
TIMESTAMPDIFF(MONTH, '2026-08-31', '2026-09-01') AS so_thang;0 0Do đó, tính tuổi phải xét cả tháng-ngày. Cách sau chạy được ở cả hai hệ (tuổi tại ngày 2026-09-25):
SELECT customer_id, birth_date,
2026 - YEAR(birth_date)
- CASE WHEN MONTH(birth_date) * 100 + DAY(birth_date) > 925 THEN 1 ELSE 0 END AS tuoi
FROM Customers
WHERE customer_id IN (1, 2, 3, 4, 5, 6)
ORDER BY customer_id;1 1994-07-21 32
2 1997-12-24 28
3 1968-10-23 57
4 1994-10-14 31
5 1996-02-06 30
6 1969-07-22 57Trong đó MONTH * 100 + DAY biến ngày sinh thành số mmdd (ví dụ 21/07 → 721) để so sánh với 925 (25/09): nếu sinh nhật chưa tới thì trừ 1 tuổi.
3.4. Định dạng hiển thị ngày
| SQL Server | MySQL | |
|---|---|---|
| Định dạng | FORMAT(d, 'dd/MM/yyyy') |
DATE_FORMAT(d, '%d/%m/%Y') |
Tháng dạng yyyy-MM |
FORMAT(d, 'yyyy-MM') |
DATE_FORMAT(d, '%Y-%m') |
SQL Server:
SELECT FORMAT(CAST('2021-06-29' AS DATE), 'dd/MM/yyyy') AS ngay_vn,
FORMAT(CAST('2021-06-29' AS DATE), 'yyyy-MM') AS thang;29/06/2021 2021-06MySQL:
SELECT DATE_FORMAT('2021-06-29', '%d/%m/%Y') AS ngay_vn,
DATE_FORMAT('2021-06-29', '%Y-%m') AS thang;29/06/2021 2021-06Lưu ý: Mẫu định dạng của SQL Server phân biệt hoa/thường (
MM= tháng,mm= phút); của MySQL dùng%Y %m %d.FORMATtrả về chuỗi — dùng cho hiển thị, không dùng để so sánh ngày.
3.5. Ứng dụng: phân tích xu hướng theo mùa/kỳ
Gom nhóm theo YEAR/MONTH/quý để xem xu hướng, ví dụ số khách đăng ký theo năm (chạy giống nhau ở cả hai hệ):
SELECT YEAR(created_date) AS nam_dk, COUNT(*) AS so_khach
FROM Customers
GROUP BY YEAR(created_date)
ORDER BY nam_dk;2018 1
2019 3
2020 7
2021 9
2022 5
2023 5
2024 4
2025 2
2026 4Theo quý để tìm "mùa cao điểm" mở tài khoản:
SQL Server:
SELECT DATEPART(quarter, created_date) AS quy, COUNT(*) AS so_khach
FROM Customers
GROUP BY DATEPART(quarter, created_date)
ORDER BY quy;1 8
2 10
3 14
4 8MySQL:
SELECT QUARTER(created_date) AS quy, COUNT(*) AS so_khach
FROM Customers
GROUP BY QUARTER(created_date)
ORDER BY quy;1 8
2 10
3 14
4 8Ví dụ nghiệp vụ: Quý 3 có nhiều khách đăng ký nhất trong dữ liệu mẫu. Phòng marketing dùng số liệu theo quý/tháng như vậy để lập kế hoạch chiến dịch mở tài khoản.
Đăng ký "trong 30 ngày qua" (tính đến 25/09/2026): điều kiện created_date >= (ngày hôm nay − 30 ngày) AND created_date <= hôm nay, với DATEADD(day, -30, '2026-09-25') (T-SQL) / DATE_SUB('2026-09-25', INTERVAL 30 DAY) (MySQL). Luôn chốt rõ biên: 30 ngày trước là bao gồm hay không, và ngày sau "hôm nay" có bị loại không — đây chính là kiểu điều kiện biên mà bài chấm sẽ kiểm tra.
Sai lầm thường gặp: đảo thứ tự tham số DATEDIFF giữa hai hệ (ra số âm); dùng DATEDIFF(year, ...) để tính tuổi; so sánh kết quả FORMAT (chuỗi) với ngày; quên ép DATEADD với chuỗi về DATE.
Thực hành ngay: bài 4.2
Vừa học xong DATEDIFF, DATEADD/DATE_ADD, YEAR/MONTH, FORMAT/DATE_FORMAT và cách tính tuổi, hãy làm nhóm bài 4.2 để tính số ngày tham gia, phân loại theo năm đăng ký, tính tuổi, lọc khách mới trong 30 ngày và tính ngày thanh toán T+2.
4. Hàm toán học
| Hàm | Ý nghĩa | SQL Server | MySQL |
|---|---|---|---|
ABS(x) |
Giá trị tuyệt đối | có | có |
ROUND(x, n) |
Làm tròn n chữ số thập phân (n âm: làm tròn hàng chục/trăm) |
có (giữ nguyên số chữ số thập phân của kiểu) | có |
CEILING(x) |
Làm tròn lên | CEILING |
CEILING hoặc CEIL |
FLOOR(x) |
Làm tròn xuống | có | có |
POWER(x, y) |
Lũy thừa | có | có (POW) |
SQRT(x) |
Căn bậc hai | có | có |
| Chia lấy dư | a % b |
a % b hoặc MOD(a, b) |
Kết quả thô của các hàm này khác nhau ở số chữ số thập phân hiển thị:
SQL Server:
SELECT ABS(-810.5) AS a, ROUND(123.456, 2) AS r2, ROUND(1234.5, -2) AS rm2,
CEILING(3.2) AS c, FLOOR(-3.2) AS f, SQRT(144) AS s, 17 % 5 AS m;810.5 123.460 1200.0 4 -4 12 2MySQL:
SELECT ABS(-810.5) AS a, ROUND(123.456, 2) AS r2, ROUND(1234.5, -2) AS rm2,
CEILING(3.2) AS c, FLOOR(-3.2) AS f, SQRT(144) AS s, 17 % 5 AS m;810.5 123.46 1200 4 -4 12.0 2Các chữ số thập phân hiển thị khác nhau (123.460 so với 123.46, 12 so với 12.0); vì vậy khi cần khớp chính xác, luôn CAST(ROUND(x, 2) AS DECIMAL(18,2)):
SELECT CAST(ABS(-810.5) AS DECIMAL(18,2)) AS a,
CAST(ROUND(123.456, 2) AS DECIMAL(18,2)) AS r2,
CAST(CEILING(3.2) AS DECIMAL(18,0)) AS c,
CAST(FLOOR(-3.2) AS DECIMAL(18,0)) AS f,
CAST(SQRT(144) AS DECIMAL(18,2)) AS s,
17 % 5 AS m;810.50 123.46 4 -4 12.00 2Cắt bớt thay vì làm tròn. ROUND làm tròn theo quy tắc số học; muốn cắt bỏ phần lẻ thì T-SQL dùng tham số thứ ba ROUND(x, n, 1), MySQL dùng TRUNCATE(x, n):
SQL Server:
SELECT ROUND(123.456, 2) AS lam_tron, ROUND(123.456, 2, 1) AS cat_bo;123.460 123.450MySQL:
SELECT ROUND(123.456, 2) AS lam_tron, TRUNCATE(123.456, 2) AS cat_bo;123.46 123.45Làm tròn lên/xuống thế nào cho đúng nghiệp vụ. Giá trị giao dịch 718,3 tỷ quy ra "số tỷ làm tròn lên" là CEILING(718.3) = 719; FLOOR(718.9) = 718. Lô giao dịch chuẩn là 100 cổ phiếu: số lô = FLOOR(quantity / 100.0) và phần lẻ = quantity % 100:
SELECT order_id, quantity, FLOOR(quantity / 100.0) AS so_lo, quantity % 100 AS le
FROM Orders WHERE order_id <= 5 ORDER BY order_id;1 300 3 0
2 2000 20 0
3 2000 20 0
4 100 1 0
5 1000 10 0Bẫy POWER với kiểu DECIMAL của SQL Server. Hàm POWER trả về cùng kiểu với đối số thứ nhất — với DECIMAL(3,2) nghĩa là chỉ giữ 2 chữ số thập phân:
SQL Server:
SELECT POWER(1.06, 3) AS sai, POWER(1.06e0, 3) AS dung;1.19 1.191016MySQL:
SELECT POWER(1.06, 3) AS sai, POWER(1.06e0, 3) AS dung;1.191016 1.191016Cảnh báo: Ở SQL Server,
POWER(1.06, 3)bị cắt còn1.19(sai). Cách xử lý: ép đối số sang số thực bằng hằng1.06e0(hoặc nhân1.0e0), rồi làm tròn/ép kiểu ở bước cuối.
Ví dụ dự phóng số dư 8.000.000 đồng gửi 3 năm, lãi kép 6%/năm — cho cùng kết quả ở cả hai hệ:
SELECT CAST(ROUND(8000000 * POWER(1.06e0, 3), 2) AS DECIMAL(18,2)) AS so_du_sau_3_nam;9528128.00Ứng dụng: làm tròn lợi suất, tính phần trăm. Lợi suất trong ngày = 100.0 * (close_price - open_price) / open_price; nhớ 100.0 (thập phân) để tránh chia nguyên, rồi ROUND(..., 2):
SELECT symbol, open_price, close_price,
CAST(ROUND(100.0 * (close_price - open_price) / open_price, 2) AS DECIMAL(18,2)) AS loi_suat_pct
FROM MarketPrices
WHERE trade_date = '2026-09-25' AND symbol IN ('FPT', 'HPG', 'VCB')
ORDER BY symbol;FPT 118800.00 116400.00 -2.02
HPG 26460.00 27270.00 3.06
VCB 92920.00 95680.00 2.97Độ lệch chuẩn (đo rủi ro) = SQRT(AVG(POWER(x - trung_bình, 2))) — cần ép sang số thực như trên.
Sai lầm thường gặp: quên 100.0 khiến phần trăm bị chia nguyên; POWER trên DECIMAL ở SQL Server; hiển thị ROUND thô rồi so sánh chuỗi với đáp án (số chữ số khác nhau giữa hai hệ).
Thực hành ngay: bài 4.4
Vừa học xong ROUND, ABS, CEILING, POWER, SQRT, hãy làm nhóm bài 4.4 để tính lợi suất trong ngày, biên độ, giá trị làm tròn lên tỷ đồng và độ lệch chuẩn giá của từng mã.
5. Hàm điều kiện
5.1. CASE WHEN — chuẩn SQL, chạy trên cả hai hệ
CASE
WHEN điều_kiện_1 THEN giá_trị_1
WHEN điều_kiện_2 THEN giá_trị_2
ELSE giá_trị_mặc_định
ENDCác nhánh được xét từ trên xuống, nhánh đúng đầu tiên thắng; không có ELSE và không nhánh nào đúng thì kết quả là NULL. Ví dụ phân nhóm mức giá:
SELECT symbol, close_price,
CASE WHEN close_price > 100000 THEN 'Cao'
WHEN close_price > 30000 THEN 'Trung binh'
ELSE 'Thap' END AS nhom_gia
FROM MarketPrices
WHERE trade_date = '2026-09-25' AND symbol IN ('FPT', 'VNM', 'SHS')
ORDER BY symbol;FPT 116400.00 Cao
SHS 14720.00 Thap
VNM 65960.00 Trung binhCASE dùng được ở nhiều nơi: trong SELECT, ORDER BY, WHERE, và bên trong hàm tổng hợp để đếm có điều kiện:
SELECT SUM(CASE WHEN status = 'ACTIVE' THEN 1 ELSE 0 END) AS dang_hoat_dong,
SUM(CASE WHEN status <> 'ACTIVE' THEN 1 ELSE 0 END) AS khac
FROM Customers;31 9Khi GROUP BY một biểu thức CASE, phải lặp lại nguyên biểu thức (hoặc gom ở truy vấn con):
SELECT CASE WHEN side = 'BUY' THEN 'Mua'
WHEN side = 'SELL' THEN 'Ban'
ELSE 'Khac' END AS loai,
COUNT(*) AS so_lenh
FROM Orders
GROUP BY CASE WHEN side = 'BUY' THEN 'Mua'
WHEN side = 'SELL' THEN 'Ban'
ELSE 'Khac' END
ORDER BY loai;Ban 42
Mua 78Cảnh báo: Nhánh
WHENhẹp đặt sau nhánh rộng sẽ bị "nuốt":WHEN close_price > 30000đặt trướcWHEN close_price > 100000thì mọi giá trên 100.000 đều rơi vào nhánh đầu. Luôn xếp từ điều kiện hẹp/chặt nhất đến rộng nhất.
Hình dưới minh họa cách CASE WHEN xét điều kiện lần lượt từ trên xuống — cơ chế quyết định thứ tự viết các nhánh.
Vì dừng ở điều kiện đúng đầu tiên nên thứ tự các WHEN rất quan trọng: đặt điều kiện hẹp/lớn trước. Không có ELSE và không điều kiện nào đúng thì kết quả là NULL.
5.2. IF / IIF
Nhánh hai lựa chọn ngắn gọn:
| SQL Server | MySQL |
|---|---|
IIF(điều_kiện, giá_trị_đúng, giá_trị_sai) (từ 2012) |
IF(điều_kiện, giá_trị_đúng, giá_trị_sai) |
SQL Server:
SELECT symbol, open_price, close_price,
IIF(close_price >= open_price, 'Tang', 'Giam') AS chieu
FROM MarketPrices
WHERE trade_date = '2026-09-25' AND symbol IN ('FPT', 'HPG', 'VCB')
ORDER BY symbol;FPT 118800.00 116400.00 Giam
HPG 26460.00 27270.00 Tang
VCB 92920.00 95680.00 TangMySQL:
SELECT symbol, open_price, close_price,
IF(close_price >= open_price, 'Tang', 'Giam') AS chieu
FROM MarketPrices
WHERE trade_date = '2026-09-25' AND symbol IN ('FPT', 'HPG', 'VCB')
ORDER BY symbol;FPT 118800.00 116400.00 Giam
HPG 26460.00 27270.00 Tang
VCB 92920.00 95680.00 TangSQL Server vs MySQL:
IF(...)chỉ chạy trên MySQL;IIF(...)chỉ chạy trên SQL Server. Muốn một câu lệnh chạy cả hai hệ thì dùngCASE. (Trong stored procedure,IF ... THEN ... END IFcủa MySQL vàIF ... ELSEcủa T-SQL là câu lệnh điều khiển luồng, khác với hàmIF()ở đây — sẽ học ở Buổi 9.)
5.3. COALESCE, IFNULL/ISNULL, NULLIF
| Hàm | Ý nghĩa | Dùng ở |
|---|---|---|
COALESCE(a, b, c, ...) |
Trả về giá trị không NULL đầu tiên | Cả hai hệ (chuẩn SQL) |
IFNULL(a, b) |
Nếu a là NULL thì b |
MySQL |
ISNULL(a, b) |
Nếu a là NULL thì b |
SQL Server (MySQL cũng có hàm ISNULL(x) nhưng ý nghĩa khác: kiểm tra NULL, trả 0/1) |
NULLIF(a, b) |
Trả NULL nếu a = b, ngược lại trả a |
Cả hai — thường dùng để tránh chia cho 0: x / NULLIF(y, 0) |
SELECT customer_id, email, COALESCE(email, 'chua co') AS email_hien_thi
FROM Customers WHERE customer_id IN (5, 6) ORDER BY customer_id;5 [email protected] [email protected]
6 NULL chua coCùng kết quả bằng hàm riêng của từng hệ:
SQL Server:
SELECT customer_id, ISNULL(email, 'chua co') AS email_hien_thi
FROM Customers WHERE customer_id IN (5, 6) ORDER BY customer_id;5 [email protected]
6 chua coMySQL:
SELECT customer_id, IFNULL(email, 'chua co') AS email_hien_thi
FROM Customers WHERE customer_id IN (5, 6) ORDER BY customer_id;5 [email protected]
6 chua coĐếm dữ liệu thiếu bằng CASE (cách này không phát sinh cảnh báo "NULL bị loại" của SQL Server như COUNT(cột)):
SELECT COUNT(*) AS so_kh,
SUM(CASE WHEN email IS NULL THEN 1 ELSE 0 END) AS thieu_email
FROM Customers;40 5Ứng dụng quan trọng: tổng có thể là NULL. Khách chưa mở tài khoản nào thì SUM(balance) là NULL, và NULL > 1000000000 không đúng cũng không sai → khách rơi khỏi cả hai nhánh CASE. Bọc COALESCE(SUM(...), 0) để coi là 0.
5.4. Ba ứng dụng nghiệp vụ điển hình (nền tảng của bài 4.3)
Phân loại khách hàng VIP/Thường theo tổng số dư mọi tài khoản. Khi chưa học JOIN, ta lấy tổng bằng truy vấn con tương quan (mỗi dòng khách hàng "chạy" một truy vấn nhỏ lấy tổng số dư của chính nó):
SELECT c.customer_id,
COALESCE((SELECT SUM(a.balance) FROM Accounts a WHERE a.customer_id = c.customer_id), 0) AS tong_tai_san
FROM Customers c
WHERE c.customer_id <= 8
ORDER BY c.customer_id;1 67000000.00
2 8640000000.00
3 168000000.00
4 1480000000.00
5 3432000000.00
6 1600000000.00
7 1290000000.00
8 760000000.00rồi bọc lại thành truy vấn con và dùng CASE WHEN tong_tai_san > 1000000000 THEN 'VIP' ELSE 'Thuong' END.
Đổi nhãn Mua/Bán/Khác cho cột side (BUY/SELL/giá trị khác) — CASE WHEN side = 'BUY' THEN 'Mua' WHEN side = 'SELL' THEN 'Ban' ELSE 'Khac' END (như ví dụ GROUP BY ở mục 5.1).
Hoa hồng khác nhau cho khách mới/cũ: tỷ lệ = CASE WHEN created_date >= '2026-08-26' THEN 0.001 ELSE 0.0005 END, hoa hồng = tổng giá trị giao dịch × tỷ lệ, rồi ROUND + CAST DECIMAL. Nên ép kiểu tỷ lệ để hai hệ in giống nhau:
SELECT customer_id, created_date,
CAST(CASE WHEN created_date >= '2026-08-26' THEN 0.001 ELSE 0.0005 END AS DECIMAL(6,4)) AS ty_le
FROM Customers
WHERE customer_id IN (1, 5, 12)
ORDER BY customer_id;1 2021-06-29 0.0005
5 2026-09-23 0.0010
12 2026-09-19 0.0010Ví dụ nghiệp vụ: Bộ phận kinh doanh áp biểu phí 0,1% cho khách đăng ký trong 30 ngày gần nhất và 0,05% cho khách cũ. Thay vì hai truy vấn riêng, một biểu thức
CASEcho phép tính phí cho mọi khách trong một lần chạy.
Sai lầm thường gặp: đặt nhánh WHEN hẹp sau nhánh rộng (nhánh rộng "nuốt" trước); quên ELSE khiến ra NULL; so sánh với NULL bằng = (phải dùng IS NULL); trộn kiểu giá trị giữa các nhánh (một nhánh số, một nhánh chuỗi) gây lỗi chuyển kiểu.
Thực hành ngay: bài 4.3
Vừa học xong CASE, COALESCE và các ứng dụng phân loại, hãy làm nhóm bài 4.3 để gắn nhãn Mua/Bán/Khác cho lệnh, phân loại khách VIP/Thường theo tài sản và tính hoa hồng theo khách mới/cũ.
6. Tổng kết
| Nhóm | Hàm chính | Khác biệt SQL Server ↔ MySQL |
|---|---|---|
| Kiểu dữ liệu | INT, BIGINT, DECIMAL, VARCHAR, DATE, BIT/BOOLEAN |
/ số nguyên; CAST AS INT ↔ AS SIGNED (cắt ↔ làm tròn); VARCHAR ↔ CHAR khi ép sang chuỗi |
| Chuỗi | LEN/CHAR_LENGTH, SUBSTRING, RIGHT, UPPER, TRIM, REPLACE, CONCAT |
CHARINDEX ↔ INSTR (ngược tham số); CONCAT với NULL; LENGTH đếm byte |
| Ngày giờ | YEAR/MONTH/DAY, DATEDIFF, DATEADD, FORMAT |
DATEDIFF(day, a, b) ↔ DATEDIFF(b, a); DATEADD ↔ DATE_ADD/DATE_SUB; FORMAT ↔ DATE_FORMAT; DATEDIFF(year) sai tuổi |
| Toán học | ABS, ROUND, CEILING, FLOOR, POWER, SQRT, % |
POWER trên DECIMAL; số chữ số thập phân hiển thị; cắt bớt: ROUND(x,n,1) ↔ TRUNCATE |
| Điều kiện | CASE, COALESCE, NULLIF |
IIF ↔ IF; ISNULL ↔ IFNULL |
Nguyên tắc vàng khi viết SQL chạy được ở cả hai hệ: dùng CASE, COALESCE, CAST(... AS DECIMAL(18,2)), CONCAT (kèm COALESCE), YEAR/MONTH/DAY; khi buộc phải khác nhau (đếm độ dài, chênh lệch ngày, tìm vị trí, cộng ngày) thì tra bảng ở các mục trên.
Sai lầm thường gặp
| Sai lầm | Hậu quả | Cách đúng |
|---|---|---|
Chia hai số nguyên (a / b) |
SQL Server cắt phần thập phân, MySQL không | 100.0 * a / b rồi CAST(ROUND(...) AS DECIMAL(18,2)) |
Lưu tiền bằng FLOAT |
Sai lệch tích lũy, không khớp sổ sách | Dùng DECIMAL |
CAST(x AS INT) trên MySQL |
Lỗi cú pháp | CAST(x AS SIGNED); nhớ SQL Server cắt còn MySQL làm tròn |
| So sánh cột chuỗi với số | SQL Server báo lỗi; MySQL trả rỗng lặng lẽ | So sánh cùng kiểu: = 'KH100007' |
Đếm độ dài bằng LENGTH ở MySQL |
Đếm byte, sai với tiếng Việt có dấu | CHAR_LENGTH |
CONCAT với cột có NULL |
MySQL trả NULL cả chuỗi |
CONCAT(..., COALESCE(cột, ''), ...) |
DATEDIFF đảo tham số / DATEDIFF(year) tính tuổi |
Ra số âm hoặc tuổi sai | Đúng thứ tự của từng hệ; tuổi = năm trừ năm, trừ 1 nếu chưa tới sinh nhật |
POWER trên DECIMAL ở SQL Server |
Kết quả bị cắt chữ số thập phân | Dùng hằng 1.06e0 (số thực) |
CASE thiếu ELSE hoặc xếp nhánh sai thứ tự |
Ra NULL hoặc nhãn sai |
Thêm ELSE, xếp nhánh hẹp trước |
Bài tập thực hành của buổi
Các bài đều chấm tự động trên cả SQL Server và MySQL (mỗi bài có lời giải riêng cho từng hệ ở chỗ cú pháp khác nhau). Nếu bạn chưa làm hết các nhóm bài đã gắn trong bài giảng, đây là danh sách đầy đủ của buổi.
Gợi ý mở rộng
- Thử
REPLICATE(T-SQL) /REPEAT(MySQL) để lặp ký tự che số điện thoại thay cho'****'. - Tính "tuổi tài khoản" theo tháng bằng
DATEDIFF(month, ...)/TIMESTAMPDIFF(MONTH, ...)và so sánh kết quả với cách tính theo ngày chia 30. - Tính hoa hồng lũy tiến theo bậc (nhiều nhánh
CASE): dưới 100 triệu 0,15%, 100–500 triệu 0,1%, trên 500 triệu 0,05%. - Chuẩn bị Buổi 5 (CTE, bảng tạm, truy vấn con): các bài 4.3 đã dùng truy vấn con tương quan — buổi sau ta sẽ học đầy đủ và tách các bước bằng
WITH ... AS.