Tạo chuỗi thời gian giữa hai ngày trong PostgreSQL


92

Tôi có một truy vấn như thế này tạo một chuỗi ngày giữa 2 ngày nhất định một cách độc đáo:

select date '2004-03-07' + j - i as AllDate 
from generate_series(0, extract(doy from date '2004-03-07')::int - 1) as i,
     generate_series(0, extract(doy from date '2004-08-16')::int - 1) as j

Nó tạo ra 162 ngày giữa 2004-03-072004-08-16và đây là những gì tôi muốn. Vấn đề với mã này là nó sẽ không đưa ra câu trả lời đúng khi hai ngày là các năm khác nhau, ví dụ như khi tôi thử 2007-02-012008-04-01.

Có giải pháp nào tốt hơn không?


Câu trả lời:


174

Có thể được thực hiện mà không cần chuyển đổi thành / từ int (thay vào đó là / từ dấu thời gian)

SELECT date_trunc('day', dd):: date
FROM generate_series
        ( '2007-02-01'::timestamp 
        , '2008-04-01'::timestamp
        , '1 day'::interval) dd
        ;

3
tại sao date_trunccần thiết?
Idefixx

2
Nó chỉ là bản trình bày. Nó loại bỏ việc in phần thời gian của dấu thời gian là số không trong trường hợp này.
beemtee 23/07/18

73

Để tạo một chuỗi ngày, đây là cách tối ưu :

SELECT t.day::date 
FROM   generate_series(timestamp '2004-03-07'
                     , timestamp '2004-08-16'
                     , interval  '1 day') AS t(day);
  • Bổ sung date_trunc()là không cần thiết. Diễn viên đến date( day::date) thực hiện điều đó một cách ngầm định.

  • Nhưng cũng không có ích lợi gì trong việc truyền các ký tự ngày tháng thành datetham số đầu vào. Au contraire, timestamplà sự lựa chọn tốt nhất . Lợi thế về hiệu suất tuy nhỏ, nhưng không có lý do gì để không nắm lấy nó. Và bạn không cần liên quan đến các quy tắc DST (thời gian tiết kiệm ánh sáng ban ngày) cùng với việc chuyển đổi từ datesang timestamp with time zonevà ngược lại. Xem bên dưới.

Cú pháp ngắn tương đương, ít rõ ràng hơn:

SELECT day::date 
FROM   generate_series(timestamp '2004-03-07', '2004-08-16', '1 day') day;

Hoặc với chức năng set-return trong SELECTdanh sách:

SELECT generate_series(timestamp '2004-03-07', '2004-08-16', '1 day')::date AS day;

Các AStừ khóa được yêu cầu trong các biến thể cuối cùng, Postgres sẽ hiểu sai bí danh cột daykhác. Và tôi sẽ không khuyên biến thể đó trước Postgres 10 - ít nhất là không có nhiều hơn một hàm set-return trong cùng một SELECTdanh sách:

(Ngoài ra, biến thể cuối cùng thường nhanh nhất với một biên độ nhỏ.)

Tại sao timestamp [without time zone]?

Có một số biến thể quá tải của generate_series(). Hiện tại (Postgres 11):

SELECT oid::regprocedure   AS function_signature
     , prorettype::regtype AS return_type
FROM   pg_proc
where  proname = 'generate_series';
function_signature | return_type                
: ------------------------------------------------- ------------------------------- | : --------------------------
create_series (số nguyên, số nguyên, số nguyên) | số nguyên                    
create_series (số nguyên, số nguyên) | số nguyên                    
create_series (bigint, bigint, bigint) | bigint                     
create_series (bigint, bigint) | bigint                     
create_series (số, số, số) | số                    
create_series (số, số) | số                    
create_series (dấu thời gian không có múi giờ, dấu thời gian không có múi giờ, khoảng thời gian) | dấu thời gian không có múi giờ
create_series (dấu thời gian với múi giờ, dấu thời gian với múi giờ, khoảng thời gian) | dấu thời gian với múi giờ

( numericcác biến thể đã được thêm vào với Postgres 9.5.) Các biến thể liên quan là hai biến thể cuối cùng được in đậm và quay lại timestamp/ timestamptz.

Không có biến thể lấy hoặc trảdate . Một diễn viên rõ ràng là cần thiết để trở lại date. Lệnh gọi với các timestampđối số sẽ giải quyết trực tiếp đến biến thể tốt nhất mà không cần chuyển sang các quy tắc phân giải kiểu hàm và không cần ép kiểu bổ sung cho đầu vào.

timestamp '2004-03-07'là hoàn toàn hợp lệ, btw. Phần thời gian bị bỏ qua được mặc định thành 00:00với định dạng ISO.

Nhờ độ phân giải kiểu hàm mà chúng ta vẫn có thể vượt qua date. Nhưng điều đó đòi hỏi nhiều công việc hơn từ Postgres. Có một dàn diễn viên ngầm từ dateđến timestampcũng như một từ dateđến timestamptz. Sẽ không rõ ràng, nhưng timestamptzđược "ưu tiên" trong số "các loại ngày / giờ". Vì vậy trận đấu được quyết định ở bước 4d. :

Chạy qua tất cả các ứng cử viên và giữ những ứng cử viên chấp nhận các kiểu ưu tiên (thuộc loại kiểu của dữ liệu đầu vào) ở hầu hết các vị trí mà chuyển đổi kiểu sẽ được yêu cầu. Giữ tất cả các ứng cử viên nếu không ai chấp nhận các loại ưu tiên. Nếu chỉ còn lại một ứng cử viên, hãy sử dụng nó; khác tiếp tục bước tiếp theo.

Ngoài công việc bổ sung trong độ phân giải kiểu hàm, điều này còn bổ sung thêm một diễn viên timestamptz- điều này không chỉ làm tăng thêm chi phí mà còn có thể gây ra các vấn đề với DST dẫn đến kết quả không mong muốn trong một số trường hợp hiếm hoi. (DST là một khái niệm mỉa mai, btw, không thể nhấn mạnh điều này đủ.)

Tôi đã thêm các bản trình diễn vào fiddle hiển thị kế hoạch truy vấn đắt tiền hơn:

db <> fiddle here

Có liên quan:


7
Thậm chí nhiều hơn phiên bản ngắn hơn:SELECT generate_series(timestamp '2004-03-07', '2004-08-16', '1 day') :: DATE AS day;
Václav Kužel

Cú pháp t (ngày) biểu thị điều gì?
rendang

@rendang: AS t(day)trong SELECT * FROM func() AS t(day)là bí danh bảng và cột. Các AStừ khóa là tùy chọn tiếng ồn trong bối cảnh này. Xem: stackoverflow.com/a/20230716/939860
Erwin Brandstetter

35

Bạn có thể tạo chuỗi trực tiếp với ngày tháng. Không cần sử dụng int hoặc dấu thời gian:

select date::date 
from generate_series(
  '2004-03-07'::date,
  '2004-08-16'::date,
  '1 day'::interval
) date;

Tùy thuộc vào múi giờ của bạn, điều này có thể trả lại một kết quả không mong muốn. Tôi đã có vấn đề này. Sử dụng dấu thời gian để thay thế. ĐẶT múi giờ của phiên 'America / Sao_Paulo' SELECT d :: date FROM generate_series ('2019-11-01' :: date, '2019-11-03' :: date, '1 day') d SELECT d :: date FROM create_series ('2019-11-01' :: date, '2019-11-04' :: date, '1 day') d
palhares

1

Bạn cũng có thể sử dụng cái này.

select generate_series  ( '2012-12-31'::timestamp , '2018-10-31'::timestamp , '1 day'::interval) :: date 
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.