Kiểu dữ liệu và ép kiểu
Bạn sẽ học được
- Vì sao cột lưu sai kiểu gây lỗi âm thầm.
- Ép kiểu an toàn từ text sang số và ngày.
- Chọn
numerichayreal/double precisioncho tiền. - Tránh lỗi chia số nguyên và làm tròn sai.
- Kiểm tra dữ liệu trước khi ép kiểu hàng loạt.
Khái niệm cốt lõi
Kiểu dữ liệu quyết định phép toán nào hợp lệ và kết quả ra sao. Dữ liệu thật thường về dưới dạng text, kể cả số và ngày. Ép kiểu sai gây lỗi chạy, hoặc tệ hơn: chạy mà cho kết quả sai.
Ba nguyên tắc:
- Lưu đúng kiểu ngay từ đầu — ngày là
date, tiền lànumeric, số đếm làinteger. - Kiểm tra trước khi ép — tìm giá trị không ép được, đừng để cả lô thất bại.
- Ép ở một chỗ — lớp staging, không rải
::intkhắp báo cáo.
Bảng kiểu thường dùng
| Dữ liệu | Kiểu đúng | Kiểu sai thường gặp |
|---|---|---|
| Ngày | date | text '05/01/2026' |
| Thời điểm | timestamptz | timestamp không múi giờ |
| Tiền | numeric(14,2) | real, double precision |
| Số đếm | integer, bigint | text |
| Cờ đúng/sai | boolean | text 'Y'/'N' |
| Mã phân loại | text + bảng tham chiếu | integer tuỳ tiện |
Tiền dùng numeric vì real/double là số thực nhị phân, không biểu diễn chính xác 0,1. Cộng nhiều dòng tiền bằng double có thể lệch vài đồng — nhỏ nhưng không chấp nhận được trong kế toán.
Ép kiểu an toàn
PostgreSQL không có TRY_CAST; ép kiểu sai sẽ ném lỗi và huỷ cả truy vấn. Cách an toàn: kiểm tra bằng biểu thức chính quy trước.
-- Dòng nào KHÔNG ép được sang số
SELECT id, price_raw
FROM staging_products
WHERE price_raw !~ '^-?[0-9]+(\.[0-9]+)?$';Chỉ ép những dòng khớp:
SELECT
id,
price_raw::numeric(14,2) AS price
FROM staging_products
WHERE price_raw ~ '^-?[0-9]+(\.[0-9]+)?$';Với ngày, kiểm tra định dạng rồi dùng to_date với format tường minh:
SELECT
id,
to_date(order_date_raw, 'DD/MM/YYYY') AS order_date
FROM staging_orders
WHERE order_date_raw ~ '^\d{2}/\d{2}/\d{4}$';to_date vẫn có thể nhận giá trị vô lý như 31/02/2026 và tự cuộn sang tháng 3. Với dữ liệu quan trọng, kiểm tra thêm bằng cách định dạng ngược lại và so sánh.
Lỗi chia số nguyên
Trong PostgreSQL, integer / integer cho kết quả integer:
SELECT 5 / 2; -- 2, không phải 2.5
SELECT 5 / 2.0; -- 2.5
SELECT 5::numeric / 2; -- 2.5Đây là lỗi âm thầm phổ biến khi tính tỷ lệ:
-- Sai: luôn bằng 0 hoặc 1
SELECT COUNT(*) FILTER (WHERE status = 'COMPLETED') / COUNT(*) AS ty_le
FROM orders;
-- Đúng
SELECT
COUNT(*) FILTER (WHERE status = 'COMPLETED')::numeric
/ NULLIF(COUNT(*), 0) AS ty_le
FROM orders;Ép một phía sang numeric trước khi chia. Luôn kèm NULLIF cho mẫu số.
Làm tròn
ROUND trên numeric làm tròn nửa ra xa 0: ROUND(2.5) = 3, ROUND(-2.5) = -3. Trên double precision kết quả phụ thuộc biểu diễn nhị phân (có thể là 2). Đây là lý do khác để dùng numeric cho tiền.
Quy tắc:
- Làm tròn ở bước hiển thị, giữ độ chính xác đầy đủ khi tính.
- Không làm tròn từng dòng rồi cộng — tổng sẽ lệch.
- Ghi rõ đơn vị: đồng, nghìn đồng, triệu đồng.
SELECT
SUM(quantity * unit_price) AS tong_chinh_xac,
ROUND(SUM(quantity * unit_price), 0) AS tong_hien_thi
FROM order_items;Ép kiểu và hiệu năng
Ép kiểu trên cột trong WHERE có thể làm mất chỉ mục:
-- Chậm: ép kiểu cột, chỉ mục trên order_date không dùng được
WHERE order_date::date = CURRENT_DATE;
-- Nhanh: giữ cột nguyên, so với khoảng
WHERE order_date >= CURRENT_DATE
AND order_date < CURRENT_DATE + INTERVAL '1 day';Tương tự, WHERE price::text LIKE '1%' không dùng được chỉ mục số. Viết điều kiện trên kiểu gốc khi có thể.
Chuẩn hoá boolean
Dữ liệu nguồn hay dùng nhiều cách biểu diễn:
| Nguồn | Cách parse |
|---|---|
'Y'/'N' | CASE WHEN upper(x) = 'Y' THEN true ELSE false END |
'1'/'0' | x::int::boolean (sau khi kiểm tra) |
'true'/'false' | x::boolean |
| Có/không NULL | CASE WHEN x IS NULL THEN NULL ... |
Dùng CASE tường minh thay vì ::boolean trần để kiểm soát giá trị lạ. Giá trị không khớp nên được gắn cờ, không tự động thành false.
Quy trình staging
1. Nạp nguyên văn vào bảng staging (mọi cột text)
2. Đếm dòng lỗi theo từng cột bằng regex
3. Ép kiểu các dòng hợp lệ sang bảng clean
4. Giữ dòng lỗi ở bảng quarantine kèm lý do
5. Đối soát số dòng: staging = clean + quarantineBước 5 là kiểm tra bắt buộc: nếu tổng không khớp, có dòng bị mất hoặc bị đếm hai lần.
Thực hành
- Tìm các dòng
price_rawkhông ép được sang số. - Viết truy vấn parse ngày
DD/MM/YYYYcó kiểm tra regex. - Sửa một phép chia tỷ lệ đang trả 0.
- So sánh kết quả
ROUNDtrênnumericvàdouble. - Viết lại một
WHEREcó ép kiểu cột thành dạng dùng được chỉ mục.
Lỗi thường gặp
| Triệu chứng | Nguyên nhân | Cách sửa |
|---|---|---|
| Cả lô import thất bại | Một dòng sai kiểu | Lọc bằng regex trước khi ép |
| Tỷ lệ luôn 0 hoặc 1 | Chia số nguyên | Ép sang numeric |
| Tổng tiền lệch vài đồng | Dùng double | Dùng numeric |
| Tổng khác tổng từng dòng | Làm tròn sớm | Làm tròn ở cuối |
| Truy vấn chậm | Ép kiểu cột trong WHERE | So sánh theo khoảng |
Giá trị lạ thành false | ::boolean trần | Dùng CASE tường minh |
Tóm tắt
- Lưu đúng kiểu; text chỉ dùng ở tầng staging.
- Kiểm tra bằng regex trước khi ép kiểu hàng loạt.
- Tiền dùng
numeric; chia tỷ lệ cần ép sangnumeric. - Làm tròn ở bước hiển thị, không làm tròn từng dòng.
- Ép kiểu cột trong
WHEREcó thể mất chỉ mục. - Đối soát
staging = clean + quarantine.
Use ← → to move between lessons