Data Analyst › SQL

จัดการวันที่ (Date functions)

ประมาณ 30 นาที

เป้าหมายของบท

  • ดึงส่วนของวันที่ (ปี/เดือน/วันในสัปดาห์) ด้วย EXTRACT
  • ตัดวันที่ให้เป็นหน่วยที่ใหญ่ขึ้น (ต้นเดือน) ด้วย DATE_TRUNC เพื่อเทียบข้ามปีได้ถูกต้อง
  • หาระยะห่างระหว่างวันที่ด้วย DATEDIFF

EXTRACT — ดึงส่วนของวันที่

SELECT EXTRACT(year FROM order_date) AS y,
       EXTRACT(month FROM order_date) AS m,
       EXTRACT(dow FROM order_date) AS dow
FROM orders

dow (day of week) ใน DuckDB ที่ใช้ในเว็บนี้ (และ PostgreSQL): 0 = อาทิตย์ … 6 = เสาร์ — แต่ระบบอื่นอาจนับต่างกัน (เช่น MySQL DAYOFWEEK เริ่มที่ 1 = อาทิตย์) ควรเช็คเอกสารของระบบที่ใช้จริง

DATE_TRUNC — ตัดวันที่ให้เหลือความละเอียดที่กำหนด

SELECT DATE_TRUNC('month', order_date) FROM orders
-- '2024-03-15' กลายเป็น '2024-03-01'

ต่างจาก EXTRACT ตรงที่ผลลัพธ์ยังเป็นวันที่ (ไม่ใช่แค่ตัวเลข) และยังเก็บข้อมูลปีไว้ครบ นี่คือเหตุผลที่ต้องใช้ DATE_TRUNC('month', ...) แทน EXTRACT(month FROM ...) เฉยๆ เวลาจะเปรียบเทียบ ยอดขายรายเดือนข้ามปี — ถ้าใช้ EXTRACT(month...) เดือนเดียวกันจากคนละปีจะถูกนับรวมกันโดยไม่ตั้งใจ เพราะมันดึงแค่ตัวเลขเดือนออกมา ไม่สนใจว่าเป็นปีไหน

DATEDIFF — ระยะห่างระหว่างวันที่

SELECT DATEDIFF('day', signup_date, order_date) AS days_between
FROM ...

นับจาก argument ตัวแรก (start) ไปตัวที่สอง (end) เสมอ — ถ้าสลับตำแหน่งกัน ผลลัพธ์จะได้ค่าเดิม แต่เครื่องหมายกลับกัน (บวกกลายเป็นลบ) ค่าติดลบที่ไม่คาดคิดมักเป็นสัญญาณว่าใส่ argument สลับกัน

เทียบกับ “วันนี้”

ข้อมูลจำลองในเว็บนี้ไม่มี “วันนี้” ที่แท้จริง (ไม่ใช่ข้อมูล real-time) จึงมักใช้วันที่ล่าสุดที่มีอยู่ในระบบ (MAX(order_date)) เป็นจุดอ้างอิงแทน CURRENT_DATE ในงานจริงที่มีข้อมูล real-time จะใช้ CURRENT_DATE หรือ NOW() ได้ตรงๆ

ตัวอย่างแก้โจทย์

กด "ขั้นต่อไป" ทีละขั้น ลองรันและแก้โค้ดในแต่ละขั้นได้

ยอดขายรายเดือน — ทำไมต้องใช้ DATE_TRUNC ไม่ใช่ EXTRACT(month) เฉยๆ

อยากดูแนวโน้มยอดขายรายเดือนของปี 2025 เพื่อเทียบกับปีก่อนหน้า

  1. ใช้ EXTRACT(month FROM ...) ดึงเลขเดือนออกมา กรองเฉพาะปี 2025 ด้วย EXTRACT(year FROM ...)

    SQL

ลูกค้าสมัครมานานกี่วันแล้ว (นับถึงวันล่าสุดที่มีข้อมูลในระบบ)

ข้อมูลจำลองนี้ไม่มี "วันนี้" จริง จึงใช้วันที่ล่าสุดที่มีออเดอร์ในระบบแทน แล้วคำนวณระยะห่างจากวันสมัคร

  1. หา "วันที่ล่าสุด" ในระบบก่อน จะใช้เป็นจุดอ้างอิง

    SQL

เติมช่องว่าง

แทน ___ ด้วยโค้ดที่ถูกต้อง แล้วกดตรวจคำตอบ

ยังไม่ได้ทำ

หาจำนวนออเดอร์ในแต่ละวันของสัปดาห์ (0=อาทิตย์ ... 6=เสาร์) เติม ___ ให้ครบ

แบบฝึกท้ายบท

บทนี้จะนับว่าเสร็จเมื่อผ่านข้อพื้นฐานและประยุกต์ครบ ข้อท้าทายได้ดาวเพิ่ม

🟢 พื้นฐาน

🟢 พื้นฐานยังไม่ได้ทำ

นับจำนวนออเดอร์ในแต่ละปี

🟢 พื้นฐานยังไม่ได้ทำ

นับจำนวนออเดอร์ทั้งหมดที่เกิดในเดือนธันวาคม (ทุกปีรวมกัน)

🟢 พื้นฐานยังไม่ได้ทำ

หาวันที่ล่าสุดที่มีการสั่งซื้อในระบบ

🟢 พื้นฐานยังไม่ได้ทำ

หาจำนวนวันระหว่างออเดอร์แรกสุดกับออเดอร์ล่าสุดในระบบทั้งหมด

🟡 ประยุกต์

🟡 ประยุกต์ยังไม่ได้ทำ

ยอดขายรายเดือน (จำนวนออเดอร์) ของปี 2024 เท่านั้น

🟡 ประยุกต์ยังไม่ได้ทำ

จำนวนลูกค้าที่สมัครมาแล้วเกิน 500 วัน (นับถึงวันล่าสุดในระบบ)

🟡 ประยุกต์ยังไม่ได้ทำ

จำนวนออเดอร์ที่เกิดขึ้นในวันเสาร์หรืออาทิตย์ (weekend)

🔴 ท้าทาย

🔴 ท้าทายยังไม่ได้ทำ

เดือนไหน (ปีไหน) ที่จำนวนออเดอร์เพิ่มขึ้นจากเดือนก่อนหน้ามากที่สุด (month-over-month, ใช้ LAG)

Quiz

1. EXTRACT กับ DATE_TRUNC ต่างกันยังไง
2. DATEDIFF('day', a, b) กับ DATEDIFF('day', b, a) ต่างกันยังไง
3. ทำไมเทียบยอดขาย "เดือนต่อเดือน" ด้วย EXTRACT(month...) เฉยๆ ข้ามปีถึงผิด

สรุป

  • EXTRACT ดึงส่วนของวันที่ออกมาเป็นตัวเลข DATE_TRUNC ตัดวันที่ให้เหลือความละเอียดที่กำหนด
  • DATEDIFF(หน่วย, start, end) นับจาก start ไป end เสมอ สลับ argument แล้วเครื่องหมายจะกลับกัน
  • เทียบยอดขายรายเดือนข้ามปีต้องใช้ DATE_TRUNC หรือกรองปีด้วย ไม่งั้นเดือนของคนละปีจะถูกรวมกัน

คำถามสัมภาษณ์

จะเปรียบเทียบยอดขายแบบเดือนต่อเดือน (MoM) ข้ามปีให้ถูกต้องต้องทำยังไง

ต้องแยกปีออกจากกันเสมอ ไม่ใช่ GROUP BY แค่เลขเดือนจาก EXTRACT(month...) เพราะจะรวมเดือนเดียวกัน จากคนละปีเข้าด้วยกันโดยไม่ตั้งใจ วิธีที่ถูกต้องมีสองแบบ: (1) ใช้ DATE_TRUNC('month', date_col) ซึ่งได้ค่าที่เก็บทั้งปีและเดือนไว้ในตัว (เช่น '2024-03-01') หรือ (2) GROUP BY ทั้ง year และ month แยกกัน ทั้งสองวิธีทำให้เดือนของแต่ละปีถูกนับแยกกันอย่างถูกต้อง