SQLite không thật sự có kiểu ngày
Điều này làm khó mọi người chuyển từ Postgres hay MySQL sang. SQLite có năm lớp lưu trữ là NULL, INTEGER, REAL, TEXT, BLOB, và chỉ vậy thôi. Không có DATE, không có DATETIME, không có TIMESTAMP. Bạn có thể viết created_at DATETIME trong CREATE TABLE, và SQLite sẽ chấp nhận, nhưng nó lưu giá trị dưới dạng chuỗi thường hoặc số.
Thứ SQLite thực sự cung cấp là một bộ hàm hiểu được ba định dạng quy ước:
- Chuỗi ISO 8601:
'2026-04-23','2026-04-23 10:15:00','2026-04-23T10:15:00.123Z'. - Unix timestamp: số giây tính từ 1970-01-01 UTC, lưu dưới dạng số nguyên.
- Số ngày Julian: số ngày (có phần lẻ) tính từ năm 4714 trước Công nguyên, lưu dưới dạng số thực.
Chọn một và dùng nhất quán. Chuỗi ISO 8601 dễ đọc nhất và sắp xếp đúng khi so sánh như chuỗi, vì thế nó là định dạng mặc định.
Bốn cách hỏi "ngay bây giờ": ngày dạng chuỗi, ngày giờ dạng chuỗi, số giây Unix, số ngày Julian. Tất cả đều mô tả cùng một thời điểm.
Năm hàm ngày giờ
SQLite có sẵn năm hàm (function) đáp ứng gần như mọi nhu cầu:
date(time, ...): trả vềYYYY-MM-DD.time(time, ...): trả vềHH:MM:SS.datetime(time, ...): trả vềYYYY-MM-DD HH:MM:SS.julianday(time, ...): trả về một số thực (rất tiện để tính khoảng cách).strftime(format, time, ...): trả về một chuỗi theo định dạng tùy chỉnh.
Mỗi hàm nhận một giá trị thời gian làm đối số đầu tiên, sau đó là bao nhiêu chuỗi modifier tùy ý.
Để ý 'unixepoch': đó là cách bạn báo cho các hàm ngày giờ biết số nguyên đầu vào là Unix timestamp chứ không phải số ngày Julian. Không có nó, SQLite coi con số là ngày Julian.
strftime: định dạng ngày tùy chỉnh
strftime là hàm làm việc chăm chỉ nhất. Vẫn là các mã % quen thuộc mà bạn biết từ C hay Python:
Những mã bạn sẽ dùng nhiều nhất:
%Y: năm bốn chữ số.%m: tháng (01 đến 12).%d: ngày trong tháng (01 đến 31).%H,%M,%S: giờ, phút, giây.%w: thứ trong tuần (0 là Chủ Nhật).%j: ngày trong năm (001 đến 366).%s: Unix timestamp.%f: giây có phần lẻ (SS.SSS).
strftime cũng là cách để tách từng phần của một ngày, vì không có hàm EXTRACT hay YEAR() riêng. Bạn chỉ cần định dạng nó xuống đúng phần mình cần, rồi ép kiểu nếu cần một con số:
strftime luôn trả về chuỗi, vì vậy hãy bọc nó trong CAST(... AS INTEGER) khi bạn muốn tính toán hoặc so sánh theo giá trị số.
Modifier: tính toán ngày tháng mà không cần toán tử
Đây là tính năng khiến việc xử lý ngày giờ trong SQLite trở nên dễ chịu. Sau đối số thời gian, bạn có thể truyền bao nhiêu chuỗi modifier tùy ý, và chúng được áp dụng lần lượt:
Những modifier bạn sẽ dùng liên tục:
'+N days','-N days', và tương tự chohours,minutes,seconds,months,years.'start of day','start of month','start of year': cắt về mốc đầu ngày, đầu tháng, đầu năm.'weekday N': tiến tới thứ được chỉ định tiếp theo trong tuần (0 là Chủ Nhật).'localtime'và'utc': chuyển đổi giữa các múi giờ.
Mẹo "ngày cuối tháng", tức đầu tháng, cộng một tháng, trừ một ngày, rất đáng ghi nhớ. SQLite không có hàm LAST_DAY, nhưng nối các modifier cho bạn kết quả tương tự.
Giờ UTC và giờ địa phương
'now' luôn trả về giờ UTC. Nếu muốn giờ địa phương, bạn phải yêu cầu rõ:
Modifier 'localtime' chuyển một giá trị UTC sang múi giờ địa phương của hệ thống. Modifier 'utc' làm ngược lại: coi đầu vào là giờ địa phương và chuyển sang UTC.
Thói quen an toàn: lưu mọi thứ theo UTC, chỉ chuyển sang giờ địa phương khi hiển thị. Trộn nhiều múi giờ khi lưu sẽ dẫn đến những bug xuất hiện hai lần mỗi năm khi chuyển giờ mùa hè.
So sánh ngày và lọc theo khoảng
Nếu bạn lưu ngày dưới dạng chuỗi ISO 8601, phép so sánh và BETWEEN tự khắc chạy đúng: ISO 8601 khi sắp theo thứ tự chữ cái cũng chính là thứ tự thời gian. Đó là toàn bộ lý do nó là định dạng mặc định.
Khoảng nửa mở (>= start, < end) là một thói quen hữu ích: nó loại bỏ hẳn câu hỏi "nửa đêm ngày 30 có được tính vào hay không?".
Với "7 ngày gần nhất", hãy để SQLite tự tính mốc:
Khoảng cách giữa hai ngày
SQLite không có DATEDIFF. Hai mẫu sau đáp ứng mọi trường hợp:
Hiệu của julianday() tính bằng ngày (có độ chính xác phần lẻ), nên nhân với 24 cho ra giờ, nhân với 1440 cho ra phút. Hiệu của strftime('%s', ...) tính bằng giây, tiện khi bạn muốn một số nguyên.
CAST(... AS INTEGER) cắt bỏ phần lẻ của ngày nếu bạn muốn đếm số ngày tròn:
Lưu ngày tháng: chọn một định dạng và giữ nguyên
Ba lựa chọn hợp lý, xếp theo mức độ bạn nên chọn:
- Chuỗi ISO 8601 (
TEXT). Dễ đọc khi xuất dữ liệu, sắp xếp đúng, hợp với mọi hàm ngày giờ. Lựa chọn mặc định. - Số giây Unix (
INTEGER). Gọn, so sánh nhanh, không mơ hồ về múi giờ. Phù hợp khi bạn có hàng triệu hàng. Bạn sẽ cầndatetime(col, 'unixepoch')để đọc lại. - Số ngày Julian (
REAL). Hiếm khi đáng dùng, trừ khi bạn tính toán ngày tháng nhiều và muốn độ chính xác dưới một giây trong một cột.
Điều bạn không nên làm là trộn nhiều định dạng trong cùng một cột. Các hàm ngày giờ sẽ lặng lẽ chấp nhận cả hai, nhưng chỉ mục, sắp xếp và phép so sánh sẽ cho ra kết quả vô nghĩa.
DEFAULT (datetime('now')) là cách viết tương đương DEFAULT CURRENT_TIMESTAMP trong SQLite: nó đóng dấu giờ UTC hiện tại lên mọi hàng mới mà không cần code ứng dụng can thiệp.
Nhóm theo khoảng thời gian
strftime phát huy sức mạnh khi bạn muốn gom các hàng theo tháng, tuần hoặc giờ:
Cùng ý tưởng cho "đơn hàng theo giờ trong ngày", "lượt đăng ký theo thứ trong tuần" hay "sự kiện theo phút": chọn một chuỗi định dạng chỉ giữ lại đúng mức chi tiết bạn cần, nhóm theo nó và tổng hợp.
Tiếp theo: hàm tổng hợp
Nhân nói về nhóm dữ liệu, COUNT(*) kia là hàm tổng hợp đơn giản nhất của SQLite. Tiếp theo chúng ta sẽ xem đủ bộ: SUM, AVG, MIN, MAX, và cách chúng gộp nhiều hàng thành một giá trị tóm tắt.
Câu hỏi thường gặp
SQLite có kiểu dữ liệu DATE hoặc DATETIME không?
Không, SQLite không có kiểu ngày riêng. Bạn lưu ngày dưới dạng TEXT theo định dạng ISO 8601 ('2026-04-23 10:15:00'), dạng INTEGER là Unix timestamp, hoặc dạng REAL là số ngày Julian. Các hàm ngày giờ có sẵn nhận cả ba định dạng và mặc định trả về chuỗi ISO 8601.
Làm sao lấy ngày giờ hiện tại trong SQLite?
Dùng date('now') để lấy ngày hiện tại, time('now') để lấy giờ hiện tại, datetime('now') cho cả hai, và strftime('%s', 'now') để lấy Unix timestamp. Mặc định các hàm này trả về giờ UTC; truyền 'localtime' làm modifier để chuyển đổi: datetime('now', 'localtime').
Làm sao cộng thêm ngày hoặc tháng vào một ngày trong SQLite?
Truyền một chuỗi modifier vào bất kỳ hàm ngày giờ nào: date('2026-04-23', '+7 days'), date('now', '-1 month'), datetime('now', '+2 hours', '+30 minutes'). Các modifier được áp dụng lần lượt theo thứ tự, và đơn vị gồm days, hours, minutes, seconds, months và years.
Làm sao tính khoảng cách giữa hai ngày?
Để tính theo ngày, dùng julianday(end) - julianday(start): số ngày Julian là số thực, nên kết quả có cả phần lẻ của ngày. Để tính theo giây, lấy hiệu hai Unix timestamp: strftime('%s', end) - strftime('%s', start). SQLite không có hàm DATEDIFF; hai mẫu này đáp ứng gần như mọi trường hợp.