จัดกลุ่มผลลัพธ์การสืบค้นตามเดือนและปีเป็น postgresql


156

ฉันมีตารางฐานข้อมูลต่อไปนี้บนเซิร์ฟเวอร์ 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

ผมอยากจะสร้างแบบสอบถามที่จะช่วยให้การSUMของSalesคอลัมน์และกลุ่มผลการค้นหาตามเดือนและปีดังต่อไปนี้:

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

มีวิธีง่าย ๆ ในการทำเช่นนั้น?

คำตอบ:


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

ตามคำร้องขอของ Radu ฉันจะอธิบายแบบสอบถามดังกล่าว:

to_char(date,'Mon') as mon, : แปลงแอตทริบิวต์ "date" เป็นรูปแบบที่กำหนดของรูปแบบย่อของเดือน

extract(year from date) as yyyy : ฟังก์ชั่น "แยก" ของ Postgresql ใช้เพื่อแยกปี YYYY จากแอตทริบิวต์ "วันที่"

sum("Sales") as "Sales" : ฟังก์ชัน SUM () จะรวมค่า "ยอดขาย" ทั้งหมดและส่งนามแฝงที่เป็นตัวพิมพ์เล็กและตัวพิมพ์ใหญ่โดยคำนึงถึงตัวพิมพ์เล็กและตัวพิมพ์ใหญ่โดยใช้เครื่องหมายคำพูด

group by 1,2: ฟังก์ชัน GROUP BY จะต้องมีคอลัมน์ทั้งหมดจากรายการ SELECT ที่ไม่ได้เป็นส่วนหนึ่งของการรวม (aka คอลัมน์ทั้งหมดที่ไม่ได้อยู่ในฟังก์ชั่น SUM / AVG / MIN / MAX ฯลฯ ) สิ่งนี้จะบอกถึงแบบสอบถามว่าควรใช้ SUM () กับชุดค่าผสมแต่ละคอลัมน์ที่ไม่ซ้ำกันซึ่งในกรณีนี้คือคอลัมน์เดือนและปี ส่วน "1,2" เป็นชวเลขแทนที่จะใช้นามแฝงของคอลัมน์แม้ว่าการใช้นิพจน์ "to_char (... )" และ "แยก (... )" ที่ดีที่สุดสำหรับการอ่าน


5
ฉันไม่คิดว่าจะให้คำตอบโดยไม่มีคำอธิบายเป็นความคิดที่ดีโดยเฉพาะอย่างยิ่งสำหรับผู้เริ่มต้น คุณควรอธิบายเหตุผลหลังคำตอบของคุณอย่างน้อยก็สักนิด (แม้ว่ามันอาจจะดูง่ายและตรงไปตรงมาสำหรับพวกเราที่เหลือ)
Radu Gheorghiu

1
@BurakArslan ผลลัพธ์ดูเหมือนว่า OP ขอมาโดยเฉพาะหรือไม่?
bma

2
@rogerdpack ผลลัพธ์date_truncไม่ใช่สิ่งที่ผู้ถามต้องการ: select date_trunc('month', timestamp '2001-02-16 20:38:40')::date=>2001-02-01
pisaruk

2
ฉันชอบความคิดที่จะใช้date_truncในgroup byข้อ
pisaruk

1
ฟิลด์ "ที่เป็นไปได้ต้องอยู่ในกลุ่มตามข้อ" ... ดีกว่าที่จะใช้ OVER (PARTITION BY)
Zon

317

ฉันไม่อยากเชื่อเลยว่าคำตอบที่ยอมรับมี upvotes มากมาย - เป็นวิธีที่น่ากลัว

นี่คือวิธีที่ถูกต้องที่จะทำกับdate_trunc :

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

เป็นการปฏิบัติที่ไม่ดี แต่คุณอาจได้รับการอภัยหากคุณใช้

 GROUP BY 1

ในแบบสอบถามที่ง่ายมาก

คุณยังสามารถใช้

 GROUP BY date_trunc('month', txn_date)

หากคุณไม่ต้องการเลือกวันที่


6
น่าเสียดายที่การส่งออกของdate_truncไม่ได้เป็นสิ่งที่คาดว่าจะถาม: =>select date_trunc('month', timestamp '2001-02-16 20:38:40') 2001-02-01 00:00:00
pisaruk

4
ฉันยอมรับว่าวิธีนี้ดีกว่า ฉันไม่แน่ใจ แต่ฉันคิดว่ามันมีประสิทธิภาพมากกว่าด้วยเนื่องจากมีการจัดกลุ่มเพียงกลุ่มเดียวแทนที่จะเป็นสองกลุ่ม หากคุณต้องการจัดรูปแบบวันที่ใหม่คุณสามารถทำได้หลังจากนั้นโดยใช้วิธีการที่อธิบายไว้ในคำตอบอื่น ๆ :to_char(date_trunc('month', txn_date), 'YY-Mon')
PawełSokołowski

1
ใช่จำนวนโหวตสำหรับคำตอบที่ยอมรับคือความคิดที่จะเชื่อได้ date_truncถูกสร้างขึ้นเพื่อวัตถุประสงค์ที่แน่นอนนี้ ไม่มีเหตุผลในการสร้างสองคอลัมน์
allenwlee

2
ดีมาก! นี่คือคำตอบที่เหนือกว่าโดยเฉพาะอย่างยิ่งเมื่อคุณสามารถสั่งซื้อได้เช่นกัน upvoted!
bobmarksie

1
อีกตัวอย่างหนึ่งที่คำตอบที่ upvoted ที่สุดควรปรากฏก่อนคำตอบที่ยอมรับ
Brian Risk

33

to_char ที่จริงแล้วคุณสามารถดึงปีและเดือนออกในคราวเดียว!

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'

หรือในกรณีตัวอย่างของผู้ใช้ด้านบน:

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

6
ฉันจะแนะนำอย่างยิ่งต่อการทำเช่นนี้หากคุณมีข้อมูลจำนวนพอสมควรในตารางของคุณ สิ่งนี้ทำงานได้แย่กว่าdate_truncวิธีเมื่อทำการกลุ่มโดย การทดลองใน DB ฉันมีประโยชน์บนโต๊ะกับ 270k แถววิธี date_trunc ที่มีมากกว่าสองเท่าของความเร็วของ TO_CHAR
คริสคลาร์ก

@ChrisClark หากประสิทธิภาพเป็นสิ่งที่น่ากังวลฉันยอมรับว่าอาจเหมาะสมที่จะใช้ date_trunc แต่ในบางกรณีการมีสตริงวันที่จัดรูปแบบจะดีกว่าและถ้าคุณใช้คลังข้อมูลนักแสดงการคำนวณเพิ่มเติมอาจไม่ใช่ตัวจัดการข้อตกลง . ตัวอย่างเช่นหากคุณกำลังเรียกใช้รายงานการวิเคราะห์อย่างรวดเร็วโดยใช้ redshift และโดยปกติจะใช้เวลา 3 วินาทีการค้นหา 6 วินาทีอาจไม่เป็นไร (แม้ว่าถ้าคุณกำลังเรียกใช้รายงานการคำนวณเพิ่มเติมอาจทำให้สิ่งต่าง ๆ ลดลงด้วยเปอร์เซ็นต์ที่น้อยลง มีค่าใช้จ่ายในการคำนวณที่ใหญ่กว่า)
mgoldwasser

1
คุณยังสามารถทำได้ - เพียงแค่ทำการจัดรูปแบบเป็นขั้นตอนแยกโดย 'ตัด' กลุ่มด้วยข้อความค้นหา เช่น SELECT to_char (d, 'YYYY-DD') จาก (SELECT date_trunc ('เดือน', d) AS "d" จาก tbl) AS foo สุดยอดของทั้งสองโลก!
Chris Clark

1
วิธีนี้ง่ายและสง่างาม ฉันชอบมันและในกรณีของฉันมันเร็วพอ ขอบคุณสำหรับคำตอบนี้!
guettli

5

มีวิธีอื่นเพื่อให้ได้ผลลัพธ์โดยใช้ฟังก์ชัน date_part () ใน 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)

ขอบคุณ


1

คำตอบbmaดีมาก! ฉันได้ใช้กับ ActiveRecords นี่คือถ้าใครต้องการมันใน 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
หรือคุณสามารถทำได้yourscopeorclass.group("extract(year from tablename.colname)")และคุณสามารถโยงมันเข้าด้วยกัน 3 ครั้งเพื่อรับปี, เดือน, วัน
nruth

1

ดูตัวอย่าง E ของบทช่วยสอนนี้ -> https://www.postgresqltutorial.com/postgresql-group-by/

คุณต้องเรียกใช้ฟังก์ชั่นใน GROUP BY ของคุณแทนที่จะเรียกชื่อของคุณสมบัติเสมือนที่คุณสร้างขึ้นเมื่อเลือก ฉันทำสิ่งที่คำตอบทั้งหมดข้างต้นแนะนำและฉันได้รับcolumn 'year_month' does not existข้อผิดพลาด

สิ่งที่ทำงานให้ฉันคือ:

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

0

Postgres มีการประทับเวลาบางประเภท:

timestamp ที่ไม่มีเขตเวลา - (ดีกว่าเพื่อเก็บ UTC timestamps) คุณพบมันในที่จัดเก็บฐานข้อมูลข้ามชาติ ลูกค้าในกรณีนี้จะดูแลเขตเวลาชดเชยสำหรับแต่ละประเทศ

การประทับเวลาพร้อมเขตเวลา - การชดเชยเวลารวมอยู่ในการประทับเวลาแล้ว

ในบางกรณีฐานข้อมูลของคุณไม่ได้ใช้เขตเวลา แต่คุณยังต้องจัดกลุ่มระเบียนตามเขตเวลาท้องถิ่นและเวลาออมแสง (เช่นhttps://www.timeanddate.com/time/zone/romania/bucharest )

ในการเพิ่มเขตเวลาคุณสามารถใช้ตัวอย่างนี้และแทนที่เขตเวลาชดเชยด้วยของคุณ

"your_date_column" at time zone '+03'

ในการเพิ่มการชดเชยเวลาฤดูร้อน +1 ให้กับ DST คุณต้องตรวจสอบว่าการประทับเวลาของคุณตกอยู่ในฤดูร้อน DST หรือไม่ เนื่องจากช่วงเวลาเหล่านั้นแตกต่างกันไปด้วย 1 หรือ 2 วันฉันจะใช้การประมาณที่ไม่มีผลต่อการบันทึกเดือนสิ้นดังนั้นในกรณีนี้ฉันสามารถละเว้นช่วงเวลาที่แน่นอนของแต่ละปีได้

หากต้องสร้างคิวรีที่แม่นยำยิ่งขึ้นคุณต้องเพิ่มเงื่อนไขเพื่อสร้างเคสเพิ่มเติม แต่โดยประมาณจะทำงานได้ดีในการแยกข้อมูลต่อเดือนตามเขตเวลาและ SummerTimeเมื่อคุณค้นหาการประทับเวลาที่ไม่มีเขตเวลาในฐานข้อมูลของคุณ:

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)
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.