Prela ย่อ query ที่เขียนด้วย SQL ยาวเกิน 20 บรรทัดให้เหลือบรรทัดเดียว ด้วยกฎข้อเดียวว่าทุกตารางมีแค่สองคอลัมน์
Prela เป็นภาษา query ใหม่จาก UCLA ที่ย่อ query SQL เกิน 20 บรรทัดให้เหลือบรรทัดเดียว เคล็ดคือมองทุกอย่างเป็นตารางสองคอลัมน์ที่ต่อกันได้เหมือนฟังก์ชัน

Prela เป็นภาษา query ตัวใหม่สำหรับเขียนคำสั่งดึงข้อมูล ซึ่งกำลังพัฒนาอยู่ในกลุ่มวิจัย RePL ของ UCLA ภาษานี้สามารถย่อ query ที่ถ้าเขียนด้วย SQL จะยาวเกิน 20 บรรทัดให้เหลือบรรทัดเดียว
ทั้งสองฝั่งมีโจทย์เดียวกันคือ หาหนังที่ผลิตโดยบริษัทในอเมริกา และมีชื่อตัวละครอยู่ในชื่อเรื่อง จากนั้นให้แสดงชื่อเรื่องพร้อมชื่อในวงการหรือ alias ของนักแสดงแต่ละคน ถ้าเขียนด้วย SQL ที่เราใช้ดึงข้อมูลกันทุกวัน จะได้ไฟล์ query ที่ยาวเกิน 20 บรรทัด แต่ถ้าเขียนด้วย Prela จะเหลือเพียงบรรทัดนี้:
movie.where(company.s(country).eq("[us]") & keyword.eq("character-name-in-title")).select(title & cast.s(person).s(alias).s(text))สิ่งที่น่าสนใจไม่ใช่แค่เรื่องความสั้น แต่เป็นวิธีคิดที่ทำให้มันสั้นได้ โค้ดบรรทัดนี้ประกอบขึ้นจากชิ้นส่วนเล็กๆ ที่นำมาต่อกันได้เรื่อยๆ เหมือนการเรียงคำในประโยค และชิ้นส่วนย่อยแต่ละชิ้นก็เป็น query ที่สมบูรณ์ในตัวเอง
ต้องบอกไว้ก่อนว่า Prela ยังเป็นงานวิจัยที่อยู่ระหว่างการพัฒนา และเอกสารฉบับเต็มของภาษาก็ยังเขียนไม่เสร็จ เราจึงยังไม่แนะนำให้ย้ายงานจริงมาใช้ Prela ในตอนนี้ แต่แนวคิดเบื้องหลังของมันมีประโยชน์มากสำหรับคนที่เขียน SQL อยู่ทุกวัน เพราะมันช่วยตอบคำถามที่เราเจอบ่อยๆ ว่า ทำไม query ถึงยาวขึ้นเรื่อยๆ จนอ่านไม่รู้เรื่อง
Query ยาวเพราะแบ่งเป็นชิ้นย่อยไม่ได้
Query ที่ยาวจนอ่านยาก ส่วนใหญ่มักไม่ได้ยาวเพราะโจทย์ซับซ้อน แต่ยาวเพราะเขียนรวมกันเป็นก้อนเดียวขนาดใหญ่
พอทุกอย่างรวมเป็นก้อนเดียว ปัญหาก็ตามมา 3 อย่าง คือเวลาจะแก้เงื่อนไขเดียวก็ต้องไล่อ่านทั้งก้อนเพื่อหาว่าอยู่ตรงไหน ถ้าอยากเอาโค้ดบางท่อนไปใช้ซ้ำก็ทำได้แค่ก๊อปปี้ไปวางแล้วไล่แก้ และพอมีคำสั่งเชื่อมตารางอย่าง join หรือ subquery ซ้อนกันหลายชั้น คนที่มาอ่านต่อก็ต้องจำโครงสร้างทั้งหมดไว้ในหัวพร้อมกัน ถึงจะเข้าใจว่า query นี้ทำงานอย่างไร
จริงๆ แล้ว SQL ก็มีเครื่องมือช่วยจัดการเรื่องนี้ เช่น CTE ที่ช่วยแยกท่อนย่อยออกมาตั้งชื่อใน query เดียวกัน หรือ View ที่ช่วยเก็บ query ไว้เป็นตารางเสมือนเพื่อใช้ซ้ำ แต่เราต้องเลือกใช้เครื่องมือเหล่านี้เอง ส่วนการเขียน SQL โดยพื้นฐานยังรวมทุกอย่างไว้ใน query ก้อนใหญ่อยู่ดี
Prela จึงเลือกวิธีที่ต่างออกไป โดยออกแบบให้ query แบ่งเป็นชิ้นย่อยได้ตั้งแต่แรก เพราะทุกชิ้นส่วนในภาษาเป็นข้อมูลประเภทเดียวกันทั้งหมด และนำมาเชื่อมต่อกันได้เสมอ
ทั้งภาษามีแค่ตารางสองคอลัมน์

จุดที่แปลกที่สุดของ Prela คือ ทั้งภาษามีโครงสร้างข้อมูลเพียงแบบเดียว นั่นคือ ตารางที่มีแค่สองคอลัมน์ หรือที่ในทางทฤษฎีเรียกว่า binary relation
ฟังดูครั้งแรกอาจเหมือนข้อจำกัดที่ไม่น่าใช้งานได้จริง เพราะในการทำงานจริงตารางของเรามักมีเป็นสิบคอลัมน์ แต่ไม่ว่าตารางจะกว้างแค่ไหน ก็แตกย่อยออกมาเป็นตารางสองคอลัมน์หลายๆ ตัวได้เสมอ วิธีการคือแยกแต่ละคอลัมน์ออกมาเป็นตารางของตัวเอง โดยจับคู่เลขแถวกับค่าในคอลัมน์นั้น
สมมติว่าเรามีตาราง movies ที่มี 3 คอลัมน์ 3 แถว ดังนี้:
| ID | title | year |
|---|---|---|
| 646 | The Godfather | 1972 |
| 478 | Seven Samurai | 1954 |
| 583 | Casablanca | 1942 |
เราสามารถแตกตารางนี้ออกเป็นตารางสองคอลัมน์ได้ 3 ตาราง:
movie = Rel([(646, 0), (478, 1), (583, 2)])
title = Rel([(0, "The Godfather"), (1, "Seven Samurai"), (2, "Casablanca")])
year = Rel([(0, 1972), (1, 1954), (2, 1942)])หลักการจับคู่คือ title จับคู่เลขแถวกับชื่อเรื่อง year จับคู่เลขแถวกับปีที่ฉาย และ movie จับคู่ ID หนังกับเลขแถว
จุดที่ควรสังเกตคือตำแหน่งของเลขแถวในแต่ละตารางไม่เหมือนกัน ใน title กับ year เลขแถวจะอยู่คอลัมน์หน้า แต่ใน movie เลขแถวจะไปอยู่คอลัมน์หลัง และคอลัมน์หน้าก็เก็บ ID ของหนังแทน บทเรียนบอกว่าเราจะเห็นเหตุผลชัดเจนขึ้นเมื่อเริ่มนำตารางมาต่อกันในหัวข้อถัดไป
อันที่จริง วิธีแตกตารางแบบนี้ไม่ใช่เรื่องใหม่ แต่เป็นหลักการ 6NF (Sixth Normal Form) ที่มีอยู่ในตำราฐานข้อมูลอยู่แล้ว สิ่งที่ Prela ทำต่างออกไป คือการนำหลักการนี้มาใช้เป็นกฎพื้นฐานเพียงข้อเดียวของทั้งภาษา
ทำไมต้องเป็นตารางสองคอลัมน์ เพราะมันทำหน้าที่เหมือนฟังก์ชัน

ทำไมต้องเป็นตารางสองคอลัมน์ ทำไมไม่ใช่สามหรือสี่คอลัมน์
คำตอบคือ ตารางสองคอลัมน์ทำงานเหมือน ฟังก์ชัน โดยคอลัมน์ซ้ายทำหน้าที่เป็นอินพุตหรือค่าที่ใส่เข้าไป และคอลัมน์ขวาทำหน้าที่เป็นเอาต์พุตหรือค่าที่ส่งกลับออกมา เช่น ตาราง title ก็เปรียบเหมือนฟังก์ชันที่รับเลขแถวเข้ามา แล้วคืนค่าเป็นชื่อเรื่องกลับไป
จุดเด่นที่ทำให้ฟังก์ชันทรงพลังคือ คุณสมบัติการนำผลลัพธ์มาต่อกันได้ หรือ Composability เราสามารถเอาผลลัพธ์จากฟังก์ชันหนึ่งส่งต่อไปเป็นอินพุตของอีกฟังก์ชันหนึ่งได้ทันที เหมือนกับการเขียนโปรแกรมที่ประกอบขึ้นจากฟังก์ชันย่อยๆ พอทุกอย่างใน query มีโครงสร้างเดียวกันแบบนี้ เราจึงประกอบ query ที่ซับซ้อนขึ้นมาจากชิ้นส่วนเล็กๆ ได้อย่างอิสระ
ข้อแตกต่างมีเพียงจุดเดียวคือ ฟังก์ชันทั่วไปเมื่อใส่อินพุต 1 ค่าจะได้เอาต์พุต 1 ค่าเสมอ แต่ในตารางสองคอลัมน์ อินพุต 1 ค่าอาจให้เอาต์พุตได้หลายค่า เพราะมีหลายแถวที่ใช้อินพุตตัวเดียวกัน บทเรียนเรียกสิ่งนี้ว่า ฟังก์ชันแบบผลลัพธ์ไม่ตายตัว หรือ non-deterministic function และคุณสมบัตินี้เองที่ช่วยตอบโจทย์งานฐานข้อมูลได้ เพราะในชีวิตจริง ข้อมูล 1 รายการอาจมีข้อมูลที่เกี่ยวข้องได้หลายตัว เช่น หนัง 1 เรื่องย่อมมีนักแสดงได้หลายคน
ตัวดำเนินการพื้นฐาน 4 ตัวที่ต้องรู้
Prela น่าสนใจตรงที่บทเรียนนี้สอนตัวดำเนินการพื้นฐานเพียง 4 ตัว และใช้หัวข้อเดียวก็เข้าใจได้ทั้งหมด:
1. การเชื่อมต่อ เขียนด้วย .select (ย่อได้เป็น .s) ทำหน้าที่นำผลลัพธ์จากตารางแรกไปค้นหาต่อในตารางที่สอง เช่น ถ้า company จับคู่หนังกับบริษัท และ country จับคู่บริษัทกับประเทศ เมื่อเขียนต่อกันเป็น:
company.s(country)ก็จะได้ประเทศของบริษัทที่ผลิตหนังเรื่องนั้น และเราสามารถเชื่อมต่อให้ยาวขึ้นได้เรื่อยๆ เช่น ไล่จากข้อมูลนักแสดงในเรื่องไปจนถึงชื่อในวงการของแต่ละคน:
cast.s(person).s(alias).s(text)การเชื่อมต่อแต่ละครั้งต้องแปลง ID ให้ตรงกับเลขแถวด้วย แต่ Prela จะจัดการเบื้องหลังให้เอง เราจึงเขียน .s() ต่อกันไปได้ตรงๆ โดยไม่ต้องเขียนขั้นตอนแปลงค่าด้วยตัวเอง
2. & ใช้สำหรับดึงข้อมูลหลายคอลัมน์พร้อมกัน ทำหน้าที่จับตารางสองตัวมาเชื่อมกันด้วยคอลัมน์แรก แล้วรวมค่าในคอลัมน์ขวาของทั้งคู่เข้าด้วยกัน เช่น title & year จะได้ทั้งชื่อเรื่องและปีที่ฉายของหนังในแถวเดียวกัน จุดสำคัญคือ ผลลัพธ์ที่ได้ก็ยังคงเป็นตารางสองคอลัมน์ ทำให้เรานำไปต่อกับคำสั่งอื่นได้เรื่อยๆ โดยไม่สะดุด
3. .eq(v) ใช้สำหรับกรองข้อมูล โดยเก็บไว้เฉพาะแถวที่มีค่าในคอลัมน์ขวาตรงกับค่าที่กำหนด เช่น company.s(country).eq("[us]") จะกรองเหลือเฉพาะหนังที่บริษัทผู้ผลิตอยู่ในประเทศ [us]
4. .where ใช้สำหรับนำเงื่อนไขไปกรองอีกตารางหนึ่ง รับตารางเงื่อนไขที่ผ่านการกรองด้วย .eq มาแล้ว ไปใช้คัดกรองข้อมูลในอีกตารางหนึ่ง เช่น:
movie.where(company.s(country).eq("[us]"))โค้ดนี้อ่านเข้าใจได้ตรงตัวทันทีว่า เลือกเฉพาะหนังที่บริษัทผู้ผลิตอยู่ในประเทศอเมริกา
เมื่อย้อนกลับไปดู query บรรทัดเดียวตอนเปิดเรื่อง เราจะเข้าใจการทำงานของแต่ละส่วนได้ทันที โดย .where ใช้คัดกรองหนังตาม 2 เงื่อนไข ส่วน .select ใช้เลือกว่าจะแสดงผลข้อมูลอะไรบ้าง และชื่อในวงการของนักแสดงที่ดึงออกมา ก็คือปลายทางของ .s ที่ต่อกันสามครั้ง
สองจุดที่เชื่อมโยงกับแนวคิด SQL ที่เราคุ้นเคย
แนวคิดของ Prela เชื่อมโยงกับสิ่งที่เราคุ้นเคยใน SQL อยู่ 2 จุดหลักๆ:
จุดแรกคือ การแยกตัวแปร เมื่อ query เริ่มยาวหรือซับซ้อนขึ้น เราสามารถแยกท่อนที่ต้องใช้ซ้ำออกมาตั้งชื่อเป็นตัวแปร Python ทั่วไปได้ทันที ซึ่งตัวแปรนี้จะทำหน้าที่เหมือน CTE ใน SQL ไปโดยปริยาย
เมื่อกี้เราเพิ่งสร้าง CTE ด้วยตัวแปร Python ธรรมดาเฉยเลยเหรอ ใช่แล้ว
ทำแบบนี้ได้เพราะทุกชิ้นส่วนย่อยใน Prela เป็น query ที่สมบูรณ์ในตัวเองอยู่แล้ว ภาษานี้จึงไม่ต้องมีไวยากรณ์พิเศษสำหรับตั้งชื่อท่อนย่อย แต่ใช้วิธีประกาศตัวแปรตามปกติแทนได้เลย
จุดที่สองคือ เครื่องหมาย & ที่เราใช้ดึงหลายคอลัมน์ เมื่อนำไปวางไว้ข้างใน .where จะกลายเป็นเงื่อนไข AND ทางตรรกศาสตร์โดยอัตโนมัติ ผู้เขียนบทเรียนมองว่าเป็น "ความบังเอิญที่ลงตัวพอดี" เพราะกลไกเดิมของ & คือการนำตารางสองตัวมาเชื่อมกันอยู่แล้ว พอมาอยู่ในตำแหน่งของเงื่อนไข การเชื่อมกันนี้จึงหมายความว่าข้อมูลต้องตรงตามเงื่อนไขของทั้งสองตารางพร้อมกันพอดี
ผลคือภาษานี้ไม่ต้องเพิ่มไวยากรณ์สำหรับตรวจหลายเงื่อนไข ในโค้ดเปิดเรื่องที่เขียนว่า company.s(country).eq("[us]") & keyword.eq("character-name-in-title") เครื่องหมาย & จึงทำหน้าที่เป็น AND แบบที่เราคุ้นเคย
เขียนตัวดำเนินการทั้ง 4 ตัวได้ด้วยโค้ดเพียง 11 บรรทัด
สิ่งที่ยืนยันว่าเรื่องนี้ไม่ใช่แค่แนวคิดบนกระดาษ คือเราสามารถเข้าไปดูโค้ดจริงได้เลย ในบทเรียนมีไฟล์ tutorial/prela.py ที่เขียนด้วย Python ล้วนและนำไปรันได้จริง โดยมีฟังก์ชันการทำงานหลัก 4 ตัวดังนี้:
def select(r, s):
d = dict(s)
return [ (x, d[y]) for x, y in r if y in d ]
def and_(r, s):
d = dict(s)
return [ (x, (y, d[x])) for x, y in r if x in d ]
def eq(r, v):
return [ (x, y) for x, y in r if y == v ]
def where(r, s):
d = dict(s)
return [ (x, y) for x, y in r if y in d ]ฟังก์ชันทั้ง 4 ตัวนี้ใช้โค้ดจริงเพียง 11 บรรทัดเท่านั้น (หรือ 14 บรรทัดถ้ารวมบรรทัดว่าง) แม้ในไฟล์จะมีคลาส Rel ครอบอยู่อีกชั้นเพื่อให้เรียกใช้แบบ .s, .where, .eq และ & ได้สะดวกขึ้น แต่กลไกการทำงานหลักทั้งหมดก็คือ 4 ฟังก์ชันด้านบนนี้เอง
เมื่อลองอ่านการทำงานทีละตัว จะเห็นว่าตรงไปตรงมามาก โดย select แปลงตารางที่สองให้เป็นตารางค้นหาอย่าง dict แล้วนำค่าฝั่งขวาของตารางแรกไปค้นหาใน dict นั้น ด้าน and_ ทำงานคล้ายกันแต่ค้นหาด้วยค่าฝั่งซ้าย แล้วนำค่าฝั่งขวาของทั้งสองตารางมารวมเป็นคู่ ส่วน eq คัดกรองเฉพาะแถวที่ค่าฝั่งขวาตรงกับค่าที่กำหนด และ where เก็บเฉพาะแถวที่ค่าฝั่งขวาปรากฏอยู่ในตารางเงื่อนไข
ในไฟล์เดียวกันยังมีข้อมูลตัวอย่างพร้อม query ให้ทดลองรันดูผลลัพธ์ได้จริง:
q = movie.where(company.s(country).eq("[us]")).select(title & year.eq(1942))แต่ไฟล์นี้เป็นเพียงเวอร์ชันตัวอย่างที่เขียนขึ้นเพื่อให้เข้าใจหลักการ ไม่ใช่เอนจินจริงของภาษา เพราะเอนจินที่ใช้จริงจะต้องจัดการทั้งเรื่องประสิทธิภาพและรายละเอียดอื่นๆ อีกมากที่โค้ดสั้นๆ ชุดนี้ยังไม่ได้ครอบคลุม
ยังเป็นงานวิจัย เอกสารยังเขียนไม่จบ
ในเอกสารของ Prela ระบุไว้ตรงๆ ว่า เอกสารฉบับเต็มของภาษายังทำไม่เสร็จ ระหว่างนี้สามารถศึกษาเพิ่มเติมได้จากเปเปอร์แทน
นอกจากนี้ ตัวภาษายังมีฟีเจอร์อื่นๆ เช่น การจัดกลุ่มข้อมูล การคำนวณสรุปผล รวมถึงตัวดำเนินการอื่นๆ ที่ยังไม่ได้เล่าในบทเรียนฉบับนี้ แปลว่าตัวดำเนินการ 4 ตัวที่เราเพิ่งดูไปคือแกนหลัก ไม่ใช่ฟีเจอร์ทั้งหมดของภาษา
อีกจุดหนึ่งที่ยังไม่มีข้อมูลจากทั้งสองแหล่ง คือการทดสอบวัดความเร็วเทียบกับ SQL ใครที่กำลังสงสัยว่าทำงานเร็วกว่าไหม คำตอบในตอนนี้คือยังไม่มีผลการทดสอบให้ดู สิ่งที่ Prela นำเสนอจึงเน้นไปที่รูปแบบภาษาและวิธีคิดในการเขียน query เป็นหลัก ไม่ใช่เรื่องความเร็ว ทั้งตัวโครงการที่ยังอยู่ระหว่างพัฒนา เอกสารที่ยังไม่เสร็จ และโค้ดที่เผยแพร่ซึ่งเป็นเพียงเวอร์ชันจำลอง ทั้งหมดนี้คือเหตุผลที่เราแนะนำให้อ่าน Prela ในฐานะแนวคิด ไม่ใช่ตัวเลือกสำหรับงานจริงในตอนนี้
นำแนวคิดไปปรับใช้ได้ทันที แม้ไม่ได้แตะ Prela เลย
สิ่งที่นักพัฒนาสามารถนำมาปรับใช้ได้ในวันนี้ ไม่ใช่ตัวไวยากรณ์ภาษา แต่เป็นวิธีมอง query ของตัวเอง
ลองเปิด query ที่ยาวที่สุดในโปรเจกต์ของคุณขึ้นมาดู แล้วถามตัวเองว่า ท่อนย่อยๆ ข้างในนั้น แยกออกมาตั้งชื่อให้ชัดเจนได้ไหม ถ้าตั้งได้ เครื่องมือที่มีอยู่แล้วอย่าง CTE, View หรือ Query Builder (เช่น Drizzle) ก็ช่วยให้โค้ดสะอาดและจัดการง่ายขึ้นมาก แต่ถ้าแยกไม่ได้เพราะแต่ละท่อนไม่สมบูรณ์ในตัวเอง นั่นคือสาเหตุที่ทำให้ query บวมและยาวเกินไป
Query ที่ยาวจนอ่านไม่รู้เรื่อง มักไม่ได้แปลว่าโจทย์ยากเกินไป แต่อาจแปลว่าเรายังไม่ได้ซอยออกมาเป็นชิ้นส่วนย่อยๆ ที่เข้าใจง่ายและตั้งชื่อได้ต่างหาก
ที่มา:
- บทความ Prela Tutorial จาก Prela (prela-lang.org)
- ไฟล์ prela/tutorial/prela.py at main · remysucre/prela จาก remysucre/prela (GitHub)
ชอบเรื่องแนวนี้ มีอีบุ๊คฟรีให้อ่านต่อ
NotebookLM ฉบับเข้าใจง่าย โยนเอกสารให้ AI อ่าน แล้วได้สรุป พอดแคสต์ และคลังความรู้ส่วนตัว
กดสมัครแล้วเราจะส่งเทคนิค AI และของแจกใหม่ๆ ให้ทางอีเมล เลิกรับได้ตลอด
Claude Cowork · The Business Playbook

ฉบับภาษาไทย 15 บท เรียนรู้ผ่านโปรเจกต์จำลองต่อเนื่องทั้งเล่ม ตั้งแต่ตั้งค่า Workspace จัดการไฟล์ เชื่อมแอป ตั้งระบบอัตโนมัติ จนถึงสร้าง Plugin


