DHDataHọc
Lý thuyếtBài 4/85 phút đọc+10 XP

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 numeric hay real/double precision cho 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:

  1. Lưu đúng kiểu ngay từ đầu — ngày là date, tiền là numeric, số đếm là integer.
  2. Kiểm tra trước khi ép — tìm giá trị không ép được, đừng để cả lô thất bại.
  3. Ép ở một chỗ — lớp staging, không rải ::int khắp báo cáo.

Bảng kiểu thường dùng

Dữ liệuKiểu đúngKiểu sai thường gặp
Ngàydatetext '05/01/2026'
Thời điểmtimestamptztimestamp không múi giờ
Tiềnnumeric(14,2)real, double precision
Số đếminteger, biginttext
Cờ đúng/saibooleantext 'Y'/'N'
Mã phân loạitext + bảng tham chiếuinteger 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.

code
-- 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:

code
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:

code
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:

code
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ệ:

code
-- 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.
code
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:

code
-- 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ồnCá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 NULLCASE 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

code
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 + quarantine

Bướ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

  1. Tìm các dòng price_raw không ép được sang số.
  2. Viết truy vấn parse ngày DD/MM/YYYY có kiểm tra regex.
  3. Sửa một phép chia tỷ lệ đang trả 0.
  4. So sánh kết quả ROUND trên numeric và double.
  5. Viết lại một WHERE có ép kiểu cột thành dạng dùng được chỉ mục.

Lỗi thường gặp

Triệu chứngNguyên nhânCách sửa
Cả lô import thất bạiMột dòng sai kiểuLọc bằng regex trước khi ép
Tỷ lệ luôn 0 hoặc 1Chia số nguyênÉp sang numeric
Tổng tiền lệch vài đồngDùng doubleDùng numeric
Tổng khác tổng từng dòngLàm tròn sớmLàm tròn ở cuối
Truy vấn chậmÉp kiểu cột trong WHERESo sánh theo khoảng
Giá trị lạ thành false::boolean trầnDù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 sang numeric.
  • 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 WHERE có thể mất chỉ mục.
  • Đối soát staging = clean + quarantine.

Dùng ← → để chuyển bài