ข้ามไปยังเนื้อหา

การ select และการกรอง

การอ่านเป็นสิ่งที่คุณทำกับฐานข้อมูลบ่อยที่สุด และ SELECT คือเครื่องมือสำหรับงานนั้น SELECT หนึ่งคำสั่งอธิบายว่าคุณต้องการ column ไหน, row ไหนที่เข้าเกณฑ์, เรียงลำดับอย่างไร และเอาเท่าไหร่ server จะส่งคืนชุดผลลัพธ์ (result set) — ศูนย์หรือมากกว่าหนึ่ง row — โดยไม่เปลี่ยนแปลงอะไรบนดิสก์

ตัวอย่างเหล่านี้สมมติว่า table authors ถูกเติมด้วย row จำนวนหนึ่งจากบทเรียน insert

แสดงรายการ column ที่คุณต้องการหลัง SELECT ใช้ * เฉพาะตอนสำรวจอย่างรวดเร็วเท่านั้น ในโค้ดจริง ให้ระบุชื่อ column เพื่อให้ผลลัพธ์คงที่ แม้ในภายหลังจะมีคนเพิ่ม column เข้ามา

SELECT id, name, born FROM authors;
id | name | born
----+-----------------+------
1 | Mira Castellan | 1971
2 | Devon Reyes | 1985
3 | Priya Anand | 1990
4 | Tomas Holt | 1962
(4 rows)

WHERE เก็บไว้เฉพาะ row ที่เงื่อนไขเป็นจริงเท่านั้น คุณสามารถเปรียบเทียบด้วย =, <, >, <=, >= และรวมเงื่อนไขด้วย AND และ OR

SELECT name, born
FROM authors
WHERE born >= 1980 AND born < 1995;
name | born
-------------+------
Devon Reyes | 1985
Priya Anand | 1990
(2 rows)

IN ตรวจสอบการเป็นสมาชิกในรายการ, BETWEEN เป็นช่วงแบบรวมปลายทั้งสองข้าง และ LIKE จับคู่ pattern ของข้อความ LIKE สนใจตัวพิมพ์เล็กพิมพ์ใหญ่ PostgreSQL ยังมี ILIKE สำหรับการจับคู่แบบไม่สนใจตัวพิมพ์เล็กพิมพ์ใหญ่ด้วย ใน pattern นั้น % จับคู่กับอักขระต่อเนื่องชุดใดก็ได้ และ _ จับคู่กับหนึ่งอักขระพอดี

SELECT name, born
FROM authors
WHERE born BETWEEN 1960 AND 1980
AND name ILIKE '%a%';
name | born
----------------+------
Mira Castellan | 1971
Tomas Holt | 1962
(2 rows)

NULL หมายถึง “ไม่ทราบ” จึงไม่ทำตัวเหมือนค่าธรรมดาทั่วไป คุณทดสอบด้วย = ไม่ได้ ให้ใช้ IS NULL และ IS NOT NULL แทน สมมติว่า author คนหนึ่งไม่มีปีเกิดที่บันทึกไว้

SELECT name FROM authors WHERE born IS NULL;

ORDER BY เรียงผลลัพธ์ เติม DESC เพื่อเรียงจากมากไปน้อย LIMIT จำกัดจำนวน row และ OFFSET ข้าม row จากด้านบน — เมื่อใช้ร่วมกันจะช่วยให้คุณแบ่งหน้าผ่าน table ขนาดใหญ่ได้

SELECT name, born
FROM authors
ORDER BY born DESC
LIMIT 2 OFFSET 1;
name | born
-------------+------
Devon Reyes | 1985
Mira Castellan | 1971
(2 rows)

ใน pgAdmin: รันอันใดก็ได้เหล่านี้ใน Query Tool ผลลัพธ์จะปรากฏใน table Data Output ด้านล่าง และคุณยังเรียง column ได้ด้วยการคลิกที่หัว column

  • NULL ใช้ตรรกะสามค่า (three-valued logic): การเปรียบเทียบกับ NULL ไม่ได้เป็นจริงหรือเท็จ แต่เป็นไม่ทราบ (unknown) ดังนั้น row เหล่านั้นจึงถูกตัดออกโดย WHERE จงใช้ IS NULL แทน = NULL เสมอ
  • LIMIT ที่ไม่มี ORDER BY จะคืนชุดย่อยแบบสุ่ม — server อาจคืน row ในลำดับใดก็ได้ และลำดับนั้นอาจเปลี่ยนไประหว่างการรันแต่ละครั้ง จงจับคู่ LIMIT กับ ORDER BY ที่มีผลแน่นอน (deterministic) เสมอ
  • ILIKE สะดวกแต่ไม่สามารถใช้ index ธรรมดาได้อย่างมีประสิทธิภาพ สำหรับการค้นหาข้อความหนัก ๆ ให้ศึกษา trigram index หรือ full-text search ในภายหลังของคอร์ส
  • การ select เฉพาะ column ที่คุณต้องการช่วยให้ result set เล็กและโค้ดของคุณทนทานต่อการเปลี่ยนแปลง schema
ตัวเลือกBenefitCost
ระบุ column list ชัดเจนผลลัพธ์คงที่ (stable API), ส่งข้อมูลผ่าน wire น้อยลง, ไม่ leak column ที่อ่อนไหวต้องแก้ query เมื่อ column ที่ต้องการเปลี่ยน
SELECT *เขียนเร็ว สะดวกตอน prototype หรือสำรวจข้อมูลพังเงียบ ๆ เมื่อมีคนเพิ่ม/ลบ/เปลี่ยนชื่อ column ทีหลัง และอาจ leak column ที่ไม่ควรออก
กรองด้วย WHERE ใน SQLถูก push ไปให้ database ทำ ใช้ index ได้ ส่งเฉพาะ row ที่ต้องการผ่าน wireต้องเข้าใจ SQL และวางแผน index ให้ตรงกับ pattern การกรอง
ดึงข้อมูลทั้งหมดมากรองใน application codeเขียน logic ฝั่งแอปได้ง่าย ไม่ต้องคิด SQL ซับซ้อนเปลือง bandwidth, พลาดโอกาสใช้ index, ช้าลงเมื่อ table โต
  • ใช้ SELECT * ใน production code — เมื่อมีคนเพิ่ม column ใหม่ทีหลัง ผลลัพธ์ที่ได้เปลี่ยนไปแบบไม่ตั้งใจ และอาจ leak column ที่อ่อนไหวออกไปด้วย ให้ระบุ column ที่ต้องการเสมอ
  • ห่อ column ที่มี index ด้วย function ใน WHERE เช่น WHERE lower(email) = ... — ทำให้ index ธรรมดาใช้งานไม่ได้ ต้องใช้ expression index หรือปรับ query
  • คิดว่า NULL จับคู่กับ = ได้WHERE born = NULL ไม่คืน row ใด ๆ เลยเพราะ NULL เทียบด้วยตรรกะสามค่า ต้องใช้ IS NULL แทน

💡 ตัวอย่างจากของจริง

GitHub API / GitLab API — บังคับให้ client ระบุ field ที่ต้องการอย่างชัดเจนและมี pagination เสมอ เพื่อป้องกัน SELECT * ที่แอบเปลี่ยน contract ของ response และรักษา query plan ให้คาดเดาได้

ระบบ dashboard แบบ real-time — กรองข้อมูลด้วย WHERE และ index ที่วางแผนไว้ล่วงหน้าแทนการดึงทั้ง table มากรองในโค้ด เพื่อให้ query ตอบสนองไวแม้ table มีหลายสิบล้าน row

จะหา row ที่ column born ไม่มีค่า (unknown) ได้อย่างถูกต้องอย่างไร?
ข้อไหนถูกต้องเกี่ยวกับ LIMIT ที่ไม่มี ORDER BY?
operator ตัวไหนทำ pattern match แบบไม่สนตัวพิมพ์เล็กใหญ่ (case-insensitive) ใน PostgreSQL?
OFFSET 1 ทำอะไรใน SELECT?