PostgreSQL đi kèm với một loạt các kiểu dữ liệu liên quan đến ngày và giờ được tích hợp sẵn. Tại sao bạn nên sử dụng chúng trên chuỗi hoặc số nguyên? Bạn nên chú ý điều gì khi sử dụng chúng? Đọc để tìm hiểu thêm về cách làm việc hiệu quả với các kiểu dữ liệu này trong Postgres.
Rất nhiều loại
Tiêu chuẩn SQL, tiêu chuẩn ISO 8601, danh mục tích hợp sẵn của PostgreSQL và khả năng tương thích ngược lại cùng nhau xác định rất nhiều kiểu dữ liệu và quy ước liên quan đến chồng chéo, có thể tùy chỉnh / thời gian gây nhầm lẫn tốt nhất. Sự kết hợp này thường tràn vào mã trình điều khiển cơ sở dữ liệu, mã ứng dụng, quy trình SQL và dẫn đến các lỗi nhỏ rất khó gỡ lỗi.
Mặt khác, việc sử dụng các kiểu gốc tích hợp sẵn sẽ đơn giản hóa các câu lệnh SQL và làm cho chúng dễ đọc và viết hơn nhiều và do đó, ít mắc lỗi hơn. và mã ứng dụng khác.
Lợi ích của các kiểu gốc làm cho việc xác định một tập hợp các quy tắc không gây đau đớn và thực thi chúng trong toàn bộ ứng dụng và cơ sở mã hoạt động là điều đáng giá. Đây là một tập hợp như vậy, sẽ cung cấp các mặc định hợp lý và một điểm khởi đầu phù hợp để tùy chỉnh thêm nếu cần.
Loại
Chỉ sử dụng 3 loại sau (mặc dù có nhiều loại):
- ngày - một ngày cụ thể, không tính thời gian
- timestamptz - một ngày và giờ cụ thể với độ phân giải micro giây
- khoảng thời gian - khoảng thời gian với độ phân giải micro giây
Ba loại này kết hợp với nhau sẽ hỗ trợ hầu hết các trường hợp sử dụng ứng dụng. Nếu bạn không có nhu cầu cụ thể (như bảo tồn bộ nhớ), bạn chỉ nên sử dụng những loại này.
Ngày tháng đại diện cho một ngày không có thời gian và khá hữu ích trong thực tế (xem ví dụ bên dưới). Loại dấu thời gian là biến thể bao gồm thông tin múi giờ - không có thông tin múi giờ, chỉ có các biến toomany có thể ảnh hưởng đến việc diễn giải và trích xuất giá trị. Cuối cùng là khoảng thời gian đại diện cho các khoảng thời gian thấp nhất là một micro giây cho đến hàng triệu năm.
Chuỗi Literal
Chỉ sử dụng các biểu diễn theo nghĩa đen sau đây và sử dụng toán tử ép kiểu để giảm độ chi tiết mà không làm mất khả năng đọc:
-
'2012-12-25'::date
- ISO 8601 -
'2012-12-25 13:04:05.123-08:00'::timestamptz
- ISO 8601 -
'1 month 3 days'::interval
- Định dạng truyền thống Postgres cho đầu vào ngắt quãng
Việc bỏ qua múi giờ khiến bạn tùy thuộc vào cài đặt múi giờ của máy chủ Postgres, cấu hình TimeZone có thể được đặt ở cấp cơ sở dữ liệu, cấp phiên, cấp vai trò hoặc trong chuỗi kết nối, cài đặt múi giờ của máy khách và nhiều yếu tố như vậy.
Trong khi truy vấn từ mã ứng dụng, hãy chuyển đổi các loại khoảng thời gian thành một đơn vị thích hợp (như ngày hoặc giây) bằng cách sử dụng extract
hàm và đọc giá trị dưới dạng số nguyên hoặc giá trị thực.
Cấu hình và các cài đặt khác
- Không thay đổi cài đặt mặc định cho cấu hình GUC
DateStyle
,TimeZone
vàlc_time
. - Không đặt hoặc sử dụng các biến môi trường
PGDATESTYLE
vàPGTZ
. - Không sử dụng
SET [SESSION|LOCAL] TIME ZONE ...
. - Nếu bạn có thể, hãy đặt múi giờ hệ thống thành UTC trên máy chạy máy chủ Postgres, cũng như tất cả các máy chạy mã ứng dụng kết nối với nó.
- Xác thực rằng trình điều khiển cơ sở dữ liệu của bạn (như trình kết nối JDBC hoặc trình điều khiển Godatabase / sql) hoạt động hợp lý trong khi máy khách đang chạy trên một vùng thời gian và máy chủ trên một vùng khác. Đảm bảo nó hoạt động chính xác khi một
TimeZone
không-UTC hợp lệ tham số được bao gồm trong chuỗi kết nối.
Cuối cùng, lưu ý rằng tất cả những điều này chỉ là hướng dẫn và có thể được điều chỉnh theo nhu cầu của bạn - nhưng hãy đảm bảo rằng bạn điều tra tác động của việc làm đó trước.
Loại Gốc và Toán tử
Vậy chính xác thì việc sử dụng các kiểu gốc giúp đơn giản hóa mã SQL như thế nào? Dưới đây là một vài ví dụ.
Loại Ngày
Giá trị của ngày loại có thể được trừ để cung cấp cho khoảng thời gian giữa họ. Bạn cũng có thể thêm một số nguyên ngày vào một ngày cụ thể, chọn một khoảng thời gian cho một ngày để đưa ra dấu thời gian :
-- 10 days from now (outputs 2020-07-26)
SELECT now()::date + 10;
-- 10 days from now (outputs 2020-07-26 04:44:30.568847+00)
SELECT now() + '10 days'::interval;
-- days till christmas (outputs 161 days 14:06:26.759466)
SELECT '2020-12-25'::date - now();
-- the 10 longest courses
SELECT name, end_date - start_date AS duration
FROM courses
ORDER BY end_date - start_date DESC
LIMIT 10;
Giá trị của các loại này có thể so sánh được, đó là lý do tại sao bạn có thể đặt hàng truy vấn cuối cùng trước end_date - start_date
, có một loại khoảng thời gian . Đây là ví dụ điển hình:
-- certificates expiring within the next 7 days
SELECT name
FROM certificates
WHERE expiry_date BETWEEN now() AND now() + '7 days'::interval;
Loại dấu thời gian
Giá trị của loại timestamptz cũng có thể được trừ (để cung cấp một khoảng thời gian ), đã thêm (vào một khoảng thời gian để cung cấp một timestamptz khác ) và được so sánh.
-- difference of timestamps gives an interval
SELECT password_last_modified - created_at AS password_age
FROM users;
-- can also use the age() function
SELECT age(password_last_modified, created_at) AS password_age
FROM users;
Trong khi ở trên chủ đề, hãy lưu ý rằng có 3 chức năng tích hợp khác nhau trả về các giá trị “dấu thời gian hiện tại” khác nhau. Họ thực sự trả về những thứ khác nhau:
-- transaction_timestamp() returns the timestampsz of the start of current transaction
-- outputs 2020-07-16 05:09:32.677409+00
SELECT transaction_timestamp();
-- statement_timestamp() returns the timestamptz of the start of the current statement
SELECT statement_timestamp();
-- clock_timestamp() returns the timestamptz of the system clock
SELECT clock_timestamp();
Ngoài ra còn có các bí danh cho các chức năng này:
-- now() actually returns the start of the current transaction, which means it
-- does not change during the transaction
SELECT now(), transaction_timestamp();
-- transaction timestamp is also returned by these keyword-style constructs
SELECT CURRENT_DATE, CURRENT_TIMESTAMP, transaction_timestamp();
Loại khoảng thời gian
Các giá trị được nhập khoảng thời gian có thể được sử dụng làm kiểu dữ liệu cột, có thể được so sánh với bất kỳ loại dữ liệu nào khác và có thể được thêm vào (và trừ đi) dấu thời gian và ngày tháng. Dưới đây là một vài ví dụ:
-- interval-typed values can be stored and compared
SELECT num
FROM passports
WHERE valid_for > '10 years'::interval
ORDER BY valid_for DESC;
-- you can multiply them by numbers (outputs 4 years)
SELECT 4 * '1 year'::interval;
-- you can divide them by numbers (outputs 3 mons)
SELECT '1 year'::interval / 4;
-- you can add and subtract them (outputs 1 year 1 mon 6 days)
SELECT '1 year'::interval + '1.2 months'::interval;
Các hàm và cấu trúc khác
PostgreSQL cũng đi kèm với một số hàm và cấu trúc hữu ích có thể được sử dụng để thao tác các giá trị của các loại này.
Trích xuất
Hàm trích xuất có thể được sử dụng để truy xuất một phần cụ thể từ giá trị đã cho, chẳng hạn như tháng từ một ngày. Danh sách đầy đủ các phần có thể được trích xuất được tài liệu tại đây. Dưới đây là một số ví dụ hữu ích và không rõ ràng:
-- years from an interval (outputs 2)
SELECT extract(YEARS FROM '1.5 years 6 months'::interval);
-- day of the week (0=Sun .. 6=Sat) from timestamp (outputs 4)
SELECT extract(DOW FROM now());
-- day of the week (1=Mon .. 7=Sun) from timestamp (outputs 4)
SELECT extract(ISODOW FROM now());
-- convert interval to seconds (outputs 86400)
SELECT extract(EPOCH FROM '1 day'::interval);
Ví dụ cuối cùng đặc biệt hữu ích trong các truy vấn do ứng dụng chạy, vì ứng dụng có thể dễ dàng xử lý khoảng thời gian dưới dạng giá trị dấu phẩy động của số giây / phút / ngày / v.v.
Chuyển đổi Múi giờ
Ngoài ra còn có một chức năng tiện dụng để thể hiện dấu thời gian trong một múi giờ khác. Thông thường, điều này sẽ được thực hiện trong mã ứng dụng - việc kiểm tra theo cách đó sẽ dễ dàng hơn và giảm sự phụ thuộc vào cơ sở dữ liệu múi giờ mà Postgresserver sẽ tham chiếu đến. Tuy nhiên, đôi khi nó có thể hữu ích:
-- convert timestamps to a different time zone
SELECT timezone('Europe/Helsinki', now());
-- same as before, but this one is a SQL standard
SELECT now() AT TIME ZONE 'Europe/Helsinki';
Chuyển đổi sang và từ văn bản
Hàm to_char
(docs) có thể chuyển đổi ngày, dấu thời gian và khoảng thời gian thành văn bản dựa trên chuỗi định dạng – Postgres tương đương với hàm C cổ điển strftime
.
-- outputs Thu, 16th July
SELECT to_char(now(), 'Dy, DDth Month');
-- outputs 01 06 00 12 00 00
SELECT to_char('1.5 years'::interval, 'YY MM DD HH MI SS');
Để chuyển đổi từ văn bản sang ngày tháng, hãy sử dụng to_date
và để chuyển đổi văn bản thành dấu tối ưu, hãy sử dụng to_timestamp
. Lưu ý rằng nếu bạn sử dụng các biểu mẫu được liệt kê ở đầu bài đăng này, bạn chỉ có thể sử dụng toán tử truyền thay thế.
-- outputs 2000-12-25 15:42:50+00
SELECT to_timestamp('2000.12.25.15.42.50', 'YYYY.MM.DD.HH24.MI.SS');
-- outputs 2000-12-25
SELECT to_date('2000.12.25.15.42.50', 'YYYY.MM.DD');
Xem tài liệu để biết danh sách đầy đủ các mẫu chuỗi định dạng.
Tốt nhất là sử dụng các chức năng này cho các trường hợp đơn giản. Đối với việc phân tích hoặc định dạng phức tạp hơn, tốt hơn nên dựa vào mã ứng dụng, mã này có thể (được cho là) được kiểm tra đơn vị tốt hơn.
Giao diện với mã ứng dụng
Đôi khi không thuận tiện để chuyển các giá trị date / timestamptz / khoảng thời gian sang và từ mã ứng dụng, đặc biệt khi các tham số ràng buộc được sử dụng. Ví dụ, sẽ thuận tiện hơn khi chuyển một khoảng thời gian dưới dạng số nguyên ngày (hoặc giờ hoặc phút) hơn là ở định dạng chuỗi. Cũng dễ đọc hơn trong một khoảng thời gian dưới dạng số nguyên / dấu phẩy động, ngày (hoặc giờ hoặc phút, v.v.).
make_interval
hàm có thể được sử dụng để tạo một giá trị khoảng từ một số tích phân của các giá trị thành phần (xem tài liệu tại đây). to_timestamp
hàm mà chúng ta đã thấy trước đó có một dạng khác có thể tạo giá trị atimestamptz từ thời gian kỷ nguyên Unix.
-- pass the interval as number of days from the application code
SELECT name FROM courses WHERE duration <= make_interval(days => $1);
-- pass timestamptz as unix epoch (number of seconds from 1-Jan-1970)
SELECT id FROM events WHERE logged_at >= to_timestamp($1);
-- return interval as number of days (with a fractional part)
SELECT extract(EPOCH FROM duration) / 60 / 60 / 24;