Kết quả truy vấn nhóm theo tháng và năm trong postgresql


156

Tôi có bảng cơ sở dữ liệu sau trên máy chủ Postgres:

id      date          Product Sales
1245    01/04/2013    Toys    1000     
1245    01/04/2013    Toys    2000
1231    01/02/2013    Bicycle 50000
456461  01/01/2014    Bananas 4546

Tôi muốn tạo ra một truy vấn mà đưa ra SUMcác Salescột và các nhóm kết quả theo tháng, năm như sau:

Apr    2013    3000     Toys
Feb    2013    50000    Bicycle
Jan    2014    4546     Bananas

Có một cách đơn giản để làm điều đó?

Câu trả lời:


216
select to_char(date,'Mon') as mon,
       extract(year from date) as yyyy,
       sum("Sales") as "Sales"
from yourtable
group by 1,2

Theo yêu cầu của Radu, tôi sẽ giải thích truy vấn đó:

to_char(date,'Mon') as mon, : chuyển đổi thuộc tính "ngày" thành định dạng xác định của dạng tháng ngắn.

extract(year from date) as yyyy : Hàm "trích xuất" của Postgresql được sử dụng để trích xuất năm YYYY từ thuộc tính "ngày".

sum("Sales") as "Sales" : Hàm SUM () cộng tất cả các giá trị "Bán hàng" và cung cấp bí danh phân biệt chữ hoa chữ thường, với độ nhạy trường hợp được duy trì bằng cách sử dụng dấu ngoặc kép.

group by 1,2: Hàm GROUP BY phải chứa tất cả các cột trong danh sách CHỌN không phải là một phần của tổng hợp (hay còn gọi là tất cả các cột không nằm trong các hàm SUM / AVG / MIN / MAX, v.v.). Điều này cho biết truy vấn rằng SUM () nên được áp dụng cho mỗi tổ hợp cột duy nhất, trong trường hợp này là cột tháng và năm. Phần "1,2" là một tốc ký thay vì sử dụng các bí danh cột, mặc dù có lẽ tốt nhất là sử dụng các biểu thức "to_char (...)" và "trích xuất (...)" để dễ đọc.


5
Tôi không nghĩ đưa ra câu trả lời mà không có lời giải thích là một ý tưởng rất tốt, đặc biệt là cho người mới bắt đầu. Bạn nên giải thích logic đằng sau câu trả lời của bạn, có thể ít nhất là một chút (mặc dù nó có vẻ đơn giản và dễ hiểu đối với phần còn lại của chúng tôi).
Radu Gheorghiu

1
@BurakArslan Kết quả có giống như những gì OP yêu cầu cụ thể không?
bma

2
@rogerdpack, đầu ra của date_trunckhông chính xác là những gì người hỏi muốn: select date_trunc('month', timestamp '2001-02-16 20:38:40')::date=>2001-02-01
pisaruk

2
Tôi thích ý tưởng sử dụng date_trunctrong group bymệnh đề.
pisaruk

1
Các vấn đề "trường phải có thể nằm trong nhóm theo mệnh đề" ... Tốt hơn là sử dụng QUÁ (PHẦN THAM GIA B) NG).
Zon

316

Tôi không thể tin rằng câu trả lời được chấp nhận có quá nhiều sự ủng hộ - đó là một phương pháp khủng khiếp.

Đây là cách chính xác để làm điều đó, với date_trunc :

   SELECT date_trunc('month', txn_date) AS txn_month, sum(amount) as monthly_sum
     FROM yourtable
 GROUP BY txn_month

Đó là thực hành xấu nhưng bạn có thể được tha thứ nếu bạn sử dụng

 GROUP BY 1

trong một truy vấn rất đơn giản.

Bạn cũng có thể dùng

 GROUP BY date_trunc('month', txn_date)

nếu bạn không muốn chọn ngày.


6
Thật không may, đầu ra của date_trunckhông phải là những gì người hỏi mong đợi: select date_trunc('month', timestamp '2001-02-16 20:38:40')=> 2001-02-01 00:00:00.
pisaruk

3
Tôi đồng ý rằng phương pháp này là tốt hơn. Tôi không chắc nhưng tôi nghĩ nó cũng hiệu quả hơn, vì chỉ có một nhóm thay vì hai. Nếu bạn cần định dạng lại ngày, bạn có thể thực hiện sau đó bằng các phương pháp được mô tả trong các câu trả lời khác:to_char(date_trunc('month', txn_date), 'YY-Mon')
Paweł Sokołowski

1
vâng, số phiếu bầu cho câu trả lời được chấp nhận là không ổn định. date_truncđược tạo ra cho mục đích chính xác này. không có lý do để tạo hai cột
allenwlee

2
Rất đẹp! Đây là một câu trả lời vượt trội, đặc biệt là vì bạn có thể đặt hàng là tốt. Nâng cao!
bobmarkie

1
Một ví dụ khác mà câu trả lời được đánh giá cao nhất sẽ xuất hiện trước câu trả lời được chấp nhận
Brian Risk

33

to_char thực sự cho phép bạn rút ra Năm và tháng trong một cú trượt!

select to_char(date('2014-05-10'),'Mon-YY') as year_month; --'May-14'
select to_char(date('2014-05-10'),'YYYY-MM') as year_month; --'2014-05'

hoặc trong trường hợp ví dụ của người dùng ở trên:

select to_char(date,'YY-Mon') as year_month
       sum("Sales") as "Sales"
from some_table
group by 1;

6
Tôi sẽ khuyên bạn không nên làm điều này nếu bạn có một lượng dữ liệu kha khá trong bảng của mình. Này thực hiện nhiều tồi tệ hơn so với date_truncphương pháp khi thực hiện nhóm bằng. Thử nghiệm trên DB tôi có ích, trên một bảng có 270 nghìn hàng, phương thức date_trunc có tốc độ gấp đôi tốc độ của TO_CHAR
Chris Clark

@ChrisClark nếu hiệu suất là một mối quan tâm, tôi đồng ý rằng việc sử dụng date_trunc có thể có ý nghĩa, nhưng trong một số trường hợp có chuỗi ngày được định dạng là thích hợp hơn và nếu bạn đang sử dụng kho dữ liệu biểu diễn, tính toán bổ sung có thể không phải là công cụ giải quyết . Ví dụ: nếu bạn đang chạy báo cáo phân tích nhanh bằng cách sử dụng dịch chuyển đỏ và thường mất 3 giây, truy vấn 6 giây có thể ổn (mặc dù, nếu bạn đang chạy báo cáo, tính toán bổ sung có thể làm mọi thứ chậm lại theo tỷ lệ nhỏ hơn, bởi vì có một chi phí tính toán lớn hơn)
mgoldwasser

1
bạn vẫn có thể làm điều đó - chỉ cần thực hiện định dạng dưới dạng một bước riêng biệt bằng cách 'bao bọc' nhóm theo truy vấn. Ví dụ: CHỌN to_char (d, 'YYYY-DD') TỪ (CHỌN date_trunc ('tháng', d) NHƯ "d" TỪ tbl) NHƯ foo. Tốt nhất của cả hai thế giới!
Chris Clark

1
Giải pháp này đơn giản và thanh lịch. Tôi thích nó và trong trường hợp của tôi nó là đủ nhanh. Cảm ơn bạn cho câu trả lời này!
guettli

5

Có một cách khác để đạt được kết quả bằng cách sử dụng hàm date_part () trong postgres.

 SELECT date_part('month', txn_date) AS txn_month, date_part('year', txn_date) AS txn_year, sum(amount) as monthly_sum
     FROM yourtable
 GROUP BY date_part('month', txn_date)

Cảm ơn


1

bma trả lời là tuyệt vời! Tôi đã sử dụng nó với ActiveRecords, đây là nếu có ai cần nó trong Rails:

Model.find_by_sql(
  "SELECT TO_CHAR(created_at, 'Mon') AS month,
   EXTRACT(year from created_at) as year,
   SUM(desired_value) as desired_value
   FROM desired_table
   GROUP BY 1,2
   ORDER BY 1,2"
)

3
hoặc bạn có thể làm yourscopeorclass.group("extract(year from tablename.colname)")và bạn có thể xâu chuỗi lại với nhau 3 lần để có được năm, tháng, ngày
nruth

1

Hãy xem ví dụ E của hướng dẫn này -> https://www.postgresqltutorial.com/postgresql-group-by/

Bạn cần gọi hàm trên GROUP BY thay vì gọi tên của thuộc tính ảo mà bạn đã tạo trên select. Tôi đã làm những gì tất cả các câu trả lời ở trên đề nghị và tôi đã nhận được một column 'year_month' does not existlỗi.

Những gì làm việc cho tôi là:

SELECT 
    date_trunc('month', created_at), 'MM/YYYY' AS month
FROM 
    "orders"  
GROUP BY 
    date_trunc('month', created_at)

0

Postgres có một số loại dấu thời gian:

dấu thời gian không có múi giờ - (Thích lưu trữ dấu thời gian UTC) Bạn tìm thấy nó trong bộ lưu trữ cơ sở dữ liệu đa quốc gia. Khách hàng trong trường hợp này sẽ chăm sóc phần bù múi giờ cho mỗi quốc gia.

dấu thời gian với múi giờ - Phần bù múi giờ đã được bao gồm trong dấu thời gian.

Trong một số trường hợp, cơ sở dữ liệu của bạn không sử dụng múi giờ nhưng bạn vẫn cần nhóm các bản ghi liên quan đến múi giờ địa phương và Giờ tiết kiệm ánh sáng ban ngày (ví dụ: https://www.timeanddate.com/time/zone/romania/buchomon )

Để thêm múi giờ, bạn có thể sử dụng ví dụ này và thay thế bù múi giờ bằng múi giờ của bạn.

"your_date_column" at time zone '+03'

Để thêm bù thời gian mùa hè +1 cụ thể cho DST, bạn cần kiểm tra xem dấu thời gian của bạn có rơi vào DST mùa hè hay không. Vì các khoảng thời gian đó thay đổi trong 1 hoặc 2 ngày, tôi sẽ sử dụng phép tính gần đúng mà không ảnh hưởng đến các bản ghi cuối tháng, vì vậy trong trường hợp này tôi có thể bỏ qua khoảng thời gian chính xác mỗi năm.

Nếu phải xây dựng truy vấn chính xác hơn, thì bạn phải thêm điều kiện để tạo thêm trường hợp. Nhưng đại khái, điều này sẽ hoạt động tốt trong việc chia dữ liệu mỗi tháng liên quan đến múi giờ và SummerTime khi bạn tìm thấy dấu thời gian không có múi giờ trong cơ sở dữ liệu của mình:

SELECT 
    "id", "Product", "Sale",
    date_trunc('month', 
        CASE WHEN 
            Extract(month from t."date") > 03 AND
            Extract(day from t."date") > 26 AND
            Extract(hour from t."date") > 3 AND
            Extract(month from t."date") < 10 AND
            Extract(day from t."date") < 29 AND
            Extract(hour from t."date") < 4
        THEN 
            t."date" at time zone '+03' -- Romania TimeZone offset + DST
        ELSE
            t."date" at time zone '+02' -- Romania TimeZone offset 
        END) as "date"
FROM 
    public."Table" AS t
WHERE 1=1
    AND t."date" >= '01/07/2015 00:00:00'::TIMESTAMP WITHOUT TIME ZONE
    AND t."date" < '01/07/2017 00:00:00'::TIMESTAMP WITHOUT TIME ZONE
GROUP BY date_trunc('month', 
    CASE WHEN 
        Extract(month from t."date") > 03 AND
        Extract(day from t."date") > 26 AND
        Extract(hour from t."date") > 3 AND
        Extract(month from t."date") < 10 AND
        Extract(day from t."date") < 29 AND
        Extract(hour from t."date") < 4
    THEN 
        t."date" at time zone '+03' -- Romania TimeZone offset + DST
    ELSE
        t."date" at time zone '+02' -- Romania TimeZone offset 
    END)
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.