EN813304 · Midterm
บทที่ 3 / 12 · intro2rel-model.pptx (ครึ่งหลัง) · สัปดาห์ที่ 3

Relational Algebra

6 operator พื้นฐาน σ Π ∪ − × ρ · การประกอบ operator เข้าด้วยกัน · equivalent queries และ query optimization

ออกสอบเขียนนิพจน์
หัวข้อในบทนี้

วิดีโอที่เกี่ยวข้อง:

Advanced Relational Concepts

3.1ทำไมต้องเรียน Relational Algebra

ประโยคที่อาจารย์เน้นในสไลด์ SQL บอกฐานข้อมูลว่า “อยากได้อะไร” (WHAT)
Relational algebra คือวิธีที่ฐานข้อมูล “คิด” ว่าจะไปเอามายังไง (HOW)
ทุก SQL query จะถูกแปลเป็น relational algebra ก่อนถูกรัน — เรียนพีชคณิตนี้ = เรียนวิธีอ่านใจฐานข้อมูล
ชั้นบทบาท
Relational Modelนิยามว่าข้อมูลหน้าตาเป็นยังไง — relation, tuple, attribute, key (คำนาม)
Relational Algebraนิยามว่าทำอะไรกับข้อมูลได้บ้าง — σ, Π, ∪, −, ×, ρ (คำกริยา)
SQLประกาศว่าเราต้องการอะไร — ภาษาที่มนุษย์อ่านง่ายเพื่อสั่งให้ทำ operation เหล่านั้น

เกิดอะไรขึ้นเมื่อเรารัน SQL

SELECT name FROM instructor WHERE salary > 90000;

↓ Parser แปลเป็น relational algebra

Π name ( σ salary > 90000 ( instructor ) )

↓ Optimizer เลือกแผนที่ถูกที่สุดจากนิพจน์ที่สมมูลกัน

↓ Execution engine คืนผลลัพธ์

Unambiguous

พีชคณิตคือ operator tree ที่รันได้จริง ส่วนตัวหนังสือ SQL เป็นเพียงคำบรรยาย

Provable rewriting

มีกฎความสมมูลที่ทำให้ optimizerแปลง query ได้โดยรับประกันว่าผลลัพธ์เหมือนเดิม

Composable

closure property — ทุก operator รับ relation เข้า คืน relation ออก จึงต่อกันเป็น plan tree ได้

3.26 Basic Operators — ลองกดเล่นได้

1 · Select — σ (sigma)

เป็น operation ที่ใช้บ่อยที่สุด: เลือก tuple จาก relation เดียวที่ทำให้ predicate เป็นจริง

σ p (r)

σ dept_name='Physics' (instructor)
σ dept_name='Physics' ∧ salary > 90000 (instructor)

2 · Project — Π (pi)

unary operation (= operation ที่รับ relation เข้าไปตัวเดียว ตรงข้ามกับ binary ที่รับสองตัวอย่าง ∪ หรือ ×) ซึ่งคืน relation เดิมโดยตัด attribute บางตัวออก

Π A₁, A₂, …, Ak (r)

จุดที่มักตอบผิด Π ลบแถวที่ซ้ำกันออกโดยอัตโนมัติ เพราะ relation คือ เซต

เช่น Π course_id (σ year=2026 (Enrollment)) — ถึงแม้ EN813304 จะถูกลงทะเบียนโดยนักศึกษา 3 คนในปี 2026 ผลลัพธ์ก็มี EN813304 แค่ แถวเดียว

ต่างจาก SQL: SELECT course_id FROM … จะคืน 3 แถว ต้องเขียน SELECT DISTINCT เองถึงจะเหมือน Π

3–5 · Set operations — ∪, −, (∩)

เงื่อนไขที่ต้องเป็นจริงก่อนใช้ set operation:

  1. r และ s ต้องมี arity เท่ากัน (จำนวน attribute เท่ากัน)
  2. domain ของ attribute ต้องเข้ากันได้ — คอลัมน์ที่ 2 ของ r ต้องเป็นข้อมูลชนิดเดียวกับคอลัมน์ที่ 2 ของ s
ตาราง section ที่ตัวอย่างข้างล่างใช้ สไลด์ใช้ตาราง section = “วิชาที่เปิดสอนจริงในเทอมไหน กลุ่มไหน” (คนละอย่างกับ course ที่เป็นรายวิชาในหลักสูตร)
section(course_id, sec_id, semester, year, building, room_number) — 4 ตัวแรกรวมกันเป็น primary key
course_idsec_idsemesteryear
CS-1011Fall2017
CS-3471Fall2017
PHY-1011Fall2017
CS-1011Spring2018
CS-3151Spring2018

Fall 2017 เปิด {CS-101, CS-347, PHY-101} · Spring 2018 เปิด {CS-101, CS-315} → ∪ = {CS-101, CS-347, PHY-101, CS-315} · ∩ = {CS-101} · − (Fall ลบ Spring) = {CS-347, PHY-101}

-- วิชาที่เปิดสอน Fall 2017 หรือ Spring 2018 หรือทั้งสอง
Π course_id (σ semester='Fall' ∧ year=2017 (section))
  ∪ Π course_id (σ semester='Spring' ∧ year=2018 (section))

-- วิชาที่เปิดสอนทั้งสองภาค
… ∩ …

-- วิชาที่เปิด Fall 2017 แต่ไม่เปิด Spring 2018
… − …
ต้องจำ ∩ ไม่ใช่ 1 ใน 6 basic operators — มันเป็น derived operator เพราะเขียนแทนได้ว่า
r ∩ s = r − ( r − s )
6 ตัวจริง ๆ คือ σ, Π, ∪, −, ×, ρ · natural join (⋈) ก็เป็น derived เช่นกัน

6 · Cartesian product — ×

รวมข้อมูลจาก relation สองตัว โดยสร้าง tuple ผลลัพธ์จากทุกคู่ที่เป็นไปได้ ผลลัพธ์มีคอลัมน์ของทั้งสองตารางต่อกัน และมีจำนวนแถว = (แถวของ r) × (แถวของ s)

ข้อมูลตัวอย่างที่ใช้ทั้งหัวข้อนี้ (จากสไลด์ university database):

instructor — อาจารย์
IDnamedept_namesalary
10101SrinivasanComp. Sci.65000
12121WuFinance90000
15151MozartMusic40000
teaches — ใครสอนวิชาอะไร
IDcourse_idsec_idsemesteryear
10101CS-1011Fall2017
10101CS-3471Fall2017
12121FIN-2011Spring2018

สังเกตว่า teaches.ID คือ FK ที่ชี้ไปหา instructor.ID — Mozart (15151) ไม่มีแถวใน teaches เลย แปลว่าเทอมนี้เขาไม่ได้สอน

ปัญหาของ × instructor × teaches จับคู่ ทุก อาจารย์กับ ทุก แถวการสอน → 3 × 3 = 9 แถว แต่มีแค่ 3 แถว เท่านั้นที่ instructor.ID ตรงกับ teaches.ID จริง
อีก 6 แถวคือ “Mozart สอน CS-101”, “Wu สอน CS-347” … ซึ่งไม่เป็นความจริงเลย → ไร้ความหมาย

เนื่องจาก ID เป็นชื่อคอลัมน์ที่ปรากฏในทั้งสอง relation พอเอามาต่อกันแล้วจะมีคอลัมน์ชื่อ ID สองอัน เราจึงต้องแยกความต่างด้วยการเติมชื่อ relation นำหน้า: instructor.ID (รหัสอาจารย์จากตารางซ้าย) และ teaches.ID (รหัสอาจารย์จากตารางขวา)

instructor.IDnameteaches.IDcourse_idแถวนี้จริงไหม
10101Srinivasan10101CS-101จริง — ID ตรงกัน
10101Srinivasan10101CS-347จริง — ID ตรงกัน
10101Srinivasan12121FIN-201ขยะ — Srinivasan ไม่ได้สอน FIN-201
12121Wu10101CS-101ขยะ
12121Wu10101CS-347ขยะ
12121Wu12121FIN-201จริง — ID ตรงกัน
15151Mozart10101CS-101ขยะ
15151Mozart10101CS-347ขยะ
15151Mozart12121FIN-201ขยะ

สรุป: × ให้ “ทุกความเป็นไปได้” ซึ่งรวมทั้งที่จริงและที่มั่ว — งานของเราคือกรองเฉพาะที่จริงออกมา ซึ่งก็คือ join ในหัวข้อถัดไป

Join — ⋈ (derived)

เพื่อเก็บเฉพาะ tuple ที่อาจารย์กับวิชาที่เขาสอนตรงกันจริง เราใช้ σ กรองผลของ ×

σ instructor.ID = teaches.ID ( instructor × teaches )

นิพจน์นี้เรียกว่า equi-join — ยังไม่ใช่ instructor ⋈ teaches เป๊ะ ๆ เพราะผลลัพธ์ยังมีคอลัมน์ ID อยู่ สองอัน · ต้อง project ทิ้งอันหนึ่งก่อนถึงจะเท่ากัน:
instructor ⋈ teaches = ΠID, name, dept_name, salary, course_id, sec_id, semester, year ( σinstructor.ID = teaches.ID ( instructor × teaches ) )

σ ตัวนี้ทำหน้าที่ “เก็บเฉพาะ 3 แถวสีเขียวในตารางข้างบน ทิ้ง 6 แถวขยะ” ผลลัพธ์คือ:

instructor.IDnamedept_nameteaches.IDcourse_id
10101SrinivasanComp. Sci.10101CS-101
10101SrinivasanComp. Sci.10101CS-347
12121WuFinance12121FIN-201

Mozart หายไป เพราะไม่มีแถวใน teaches ที่ ID = 15151 → เขาไม่มีคู่ให้ join ด้วย (นี่คือพฤติกรรมของ inner join)

Natural join (⋈) จับคู่ tuple ที่มีค่าเท่ากันบน attribute ร่วมทุกตัว (ในที่นี้คือ ID ตัวเดียว) และเก็บคอลัมน์ร่วมไว้ชุดเดียว — ต่างจากบรรทัดบนที่มี ID โผล่มา 2 คอลัมน์ instructor ⋈ teaches จะเหลือ ID แค่คอลัมน์เดียว

คำศัพท์ที่ต้องแยกให้ออก Theta join (θ-join) = join ที่เราเขียนเงื่อนไขเอง เช่น ON a.x > b.y หรือ ON a.id = b.id (θ คือชื่อเรียกรวม ๆ ของ predicate อะไรก็ได้) · รูปพีชคณิตคือ σθ(a × b)

Equi-join = theta join กรณีพิเศษที่ θ ใช้แต่เครื่องหมาย =

Natural join (⋈) = equi-join ที่ไม่ต้องเขียนเงื่อนไข เพราะระบบจับคู่ attribute ที่ชื่อตรงกันให้เอง แล้วยุบคอลัมน์ซ้ำทิ้ง

Rename — ρ (rho) และ Assignment (←)

ผลลัพธ์ของนิพจน์พีชคณิตไม่มีชื่อให้เราอ้างถึง จึงมี rename operator

ρ x (E)                    -- ตั้งชื่อผลของ E ว่า x
ρ x(A₁, A₂, …, Aₙ) (E)       -- ตั้งชื่อทั้ง relation และ attribute
ρ ชื่อใหม่ ← ชื่อเดิม (E)       -- เปลี่ยนชื่อ "แค่คอลัมน์เดียว"
อ่านลูกศรให้ถูก — ← มีสองความหมายในบทนี้
เขียนแบบอ่านว่า
ρ ใหม่ ← เดิม (R)อยู่ข้างในตัวห้อยของ ρ → “เปลี่ยนชื่อคอลัมน์ เดิม ให้เป็น ใหม่” ตารางยังเป็นตารางเดิม แค่หัวคอลัมน์เปลี่ยน
ชื่อ ← นิพจน์ยืนเดี่ยวเป็นบรรทัดของตัวเอง → assignment “คำนวณนิพจน์ฝั่งขวา แล้วเก็บผลไว้ในตัวแปรชื่อฝั่งซ้าย” เหมือน x = … ในภาษาโปรแกรม
ทั้งสองแบบชื่อใหม่อยู่ซ้าย ชื่อเดิม/ค่าอยู่ขวาเสมอ — ต่างกันแค่ว่าอยู่ใต้ ρ หรือยืนเดี่ยว
-- ตัวอย่าง ρ: Inventory(ISBN, StockQuantity) → Inventory(ISBN, AvailableStock)
ρ AvailableStock ← StockQuantity (Inventory)

Assignment (←) ช่วยให้เขียน query ยาว ๆ เป็นลำดับขั้นเหมือนโปรแกรม

Physics ← σ dept_name='Physics' (instructor)
Music   ← σ dept_name='Music'   (instructor)
Physics ∪ Music

3.3การประกอบ operator เข้าด้วยกัน

ผลลัพธ์ของทุก operation คือ relation ดังนั้นเราเอาผลลัพธ์ไปเป็น input ของ operation ถัดไปได้ แทนที่จะใส่ชื่อ relation เป็นอาร์กิวเมนต์ของ Π เราใส่นิพจน์ที่ให้ผลเป็น relation แทนได้เลย

ตัวอย่างจากสไลด์ต่อจากนี้อ้างภาค Physics ซึ่งไม่มีในตาราง 3 แถวข้างบน — ให้คิดว่าตารางเต็มของตำรามีแถวนี้อยู่ด้วย: (22222, Einstein, Physics, 95000)

-- ชื่อของอาจารย์ทุกคนในภาควิชา Physics
Π name ( σ dept_name='Physics' (instructor) )
Closure property input เป็นตาราง · output ก็เป็นตาราง · และข้อมูลใน output มาจาก input เท่านั้น
คุณสมบัตินี้ทำให้ต่อ operation กันได้ไม่จำกัด และรับประกันว่าผลลัพธ์ของ query มาจากข้อมูลที่มีอยู่จริงเสมอ

3.4Equivalent Queries และ Query Optimization

query หนึ่งเขียนได้หลายแบบ — ตัวอย่างจากสไลด์: หาข้อมูลอาจารย์ในภาค Physics ที่เงินเดือนเกิน 90,000

Query 1

σ dept_name='Physics' ∧ salary>90000 (instructor)

Query 2

σ dept_name='Physics' ( σ salary>90000 (instructor) )

สองนิพจน์นี้ ไม่เหมือนกัน (not identical) แต่ สมมูลกัน (equivalent) — คือให้ผลลัพธ์เดียวกันบนฐานข้อมูลทุกชุดที่เป็นไปได้

นี่คือสิ่งที่ optimizer ใช้ประโยชน์ ช้า — σdept_name='Physics' ∧ salary>90000 ( instructor ⋈ teaches )
join ก่อนแล้วค่อยกรอง → ต้องประมวลผลข้อมูลจำนวนมากโดยไม่จำเป็น

เร็ว — ( σdept_name='Physics' ∧ salary>90000 (instructor) ) ⋈ teaches
กรองก่อนแล้วค่อย join → join กับข้อมูลน้อยลงมาก

กฎนี้เรียกว่า “push σ down” — ดัน selection ลงไปให้ใกล้ข้อมูลดิบที่สุด

คู่ที่ “ไม่” สมมูลกัน — โจทย์ Part C จากแบบฝึกหัด

ข้อพวกนี้ใช้ schema จากใบงานสัปดาห์ที่ 3: Student(student_id, name, major, gpa) — ข้อมูลจริง 5 แถวอยู่ใน §3.5 ด้านล่าง

C1. σmajor='CoE' ∧ gpa>3.0(Student) vs σmajor='CoE'(σgpa>3.0(Student))

สมมูลกัน — การกรองด้วย ∧ ในครั้งเดียว ให้ผลเหมือนการกรองสองรอบซ้อนกัน เพราะเงื่อนไขทั้งสองต้องเป็นจริงพร้อมกันอยู่ดี และลำดับการกรองไม่ส่งผลต่อเซตผลลัพธ์

หมายเหตุ: ต่างกันแค่ประสิทธิภาพ ไม่ต่างที่ผลลัพธ์

C2. Πname(σgpa>3.0(Student)) vs σgpa>3.0(Πname(Student))

ไม่สมมูลกัน

นิพจน์ที่สอง ทำ Πname ก่อน ซึ่งตัดคอลัมน์ gpa ทิ้งไปแล้ว จากนั้นจึงพยายามกรองด้วยเงื่อนไข gpa > 3.0 — แต่ relation ที่ได้ไม่มี attribute ชื่อ gpa เหลืออยู่เลย นิพจน์นี้จึงไม่ถูกต้อง (invalid) เขียนแบบนี้ไม่ได้

บทเรียน: จะ push σ ลงไปใต้ Π ได้ ก็ต่อเมื่อ attribute ที่ σ ใช้ ยังอยู่ หลังการ project

3.5ฝึกจากโจทย์จริงในคาบ

ใช้ schema นี้ (จากใบงาน Relational Algebra สัปดาห์ที่ 3):

Student(student_id, name, major, gpa)
Course(course_id, title, credits, dept_name)
Enrollment(student_id, course_id, semester, year, grade)
Student
student_idnamemajorgpa
6613001AnanCoE3.25
6613002BeamCoE2.80
6613003ChadaEE3.60
6613004DaoCoE3.60
6613005EarnME2.40
Enrollment
student_idcourse_idsemesteryeargrade
6613001EN81330412026A
6613001EN81100122025B+
6613002EN81330412026B
6613002EN81100122025C+
6613003EN81220112026A
6613004EN81330412026B+

Part A — อ่านนิพจน์ แล้วเขียนตารางผลลัพธ์

A1. σ major='CoE' (Student)

กรองแถวที่ major = 'CoE' · คอลัมน์ครบทุกตัว เพราะ σ ไม่ตัดคอลัมน์

student_idnamemajorgpa
6613001AnanCoE3.25
6613002BeamCoE2.80
6613004DaoCoE3.60
A2. Π name, gpa ( σ gpa ≥ 3.50 (Student) )

กรอง gpa ≥ 3.50 ได้ Chada (3.60) และ Dao (3.60) → แล้ว project เหลือ name, gpa

namegpa
Chada3.60
Dao3.60

ระวัง: ทั้งคู่ gpa = 3.60 เท่ากัน แต่ ชื่อต่างกัน จึงยังเป็นคนละ tuple ไม่ถูกยุบ · ถ้าโจทย์ให้ Π gpa เฉย ๆ จะเหลือแค่ 3.60 แถวเดียว

A3. Π course_id ( σ year=2026 (Enrollment) ) — ข้อกับดัก

แถวปี 2026 มี 4 แถว: EN813304 (×3) และ EN812201 (×1)

แต่ relation คือเซต → Π ลบแถวซ้ำ เหลือ 2 แถว

course_id
EN813304
EN812201

ถ้าตอบ 4 แถวคือผิด — นี่คือ hint ที่โจทย์ใส่มาให้ว่า “relation เป็นเซต ลองคิดเรื่องแถวที่ซ้ำกัน”

Part B — เขียนนิพจน์เอง

B1. หารายวิชาทั้งหมดของภาค CoE ที่มี 3 หน่วยกิต
σ dept_name='CoE' ∧ credits=3 (Course)

หรือเขียนซ้อนกันก็ได้ (สมมูลกัน): σ dept_name='CoE' ( σ credits=3 (Course) )

B2. แสดงเฉพาะชื่อและสาขาของนักศึกษาที่ gpa มากกว่า 2.X (X = เลขท้ายรหัสตัวเอง)
Π name, major ( σ gpa > 2.X (Student) )

ตัวอย่างถ้า X = 5 → gpa > 2.5 ได้ Anan (3.25), Beam (2.80), Chada (3.60), Dao (3.60) — Earn (2.40) ตกไป

namemajor
AnanCoE
BeamCoE
ChadaEE
DaoCoE
B3. หา course_id ของวิชาที่มีการลงทะเบียนในปี 2026 แต่ไม่มีในปี 2025
Π course_id ( σ year=2026 (Enrollment) )
   −
Π course_id ( σ year=2025 (Enrollment) )

2026 = {EN813304, EN812201} · 2025 = {EN811001} · ผลต่าง = {EN813304, EN812201}

ทำไมต้อง Π ก่อนแล้วค่อย −
เพราะ − เทียบ ทั้งแถว ไม่ใช่เทียบแค่ course_id · ถ้าเอา Enrollment เต็ม ๆ มาลบกัน แถวปี 2026 กับแถวปี 2025 มีคอลัมน์ year ต่างกันอยู่แล้ว จึงไม่มีทางหักล้างกันได้เลย — ผลลัพธ์จะเป็นแถวปี 2026 ทั้งหมด ซึ่งผิด
แม้แต่ถ้าตัด year ออกไป การเทียบทั้งแถวก็ยังพา student_id กับ grade ไปด้วย — วิชาที่มีคนลงทั้งสองปีแต่คนละคน ก็จะรอดมาเป็นคำตอบทั้งที่ไม่ควร
ต้อง Π เหลือ course_id ก่อน ถึงจะเทียบ “วิชา” กับ “วิชา” จริง ๆ

B4. หาชื่อนักศึกษาที่ลงทะเบียนวิชา 'EN813304'
Π name ( Student ⋈ σ course_id='EN813304' (Enrollment) )

หรือเขียนแบบใช้ operator พื้นฐานล้วน:

Π name ( σ Student.student_id = Enrollment.student_id
       ( Student × σ course_id='EN813304' (Enrollment) ) )

ผลลัพธ์: Anan, Beam, Dao

เทคนิคทำข้อสอบ: กรอง (σ) ให้เล็กที่สุดก่อน แล้วค่อย join แล้วค่อย project เป็นขั้นสุดท้าย — ทั้งอ่านง่ายและตรงกับที่ optimizer ทำจริง

3.6ตัวอย่างจากสไลด์เพิ่มเติม

ร้านหนังสือออนไลน์

Books(ISBN, Title, Author, Price) · Inventory(ISBN, StockQuantity)

a) หนังสือราคาเกิน $20        σ Price > 20 (Books)
b) เฉพาะชื่อเรื่องกับผู้แต่ง   Π Title, Author (Books)
c) ISBN ที่อยู่ใน Books หรือ Inventory
   Π ISBN (Books) ∪ Π ISBN (Inventory)
d) ISBN ที่อยู่ใน Books แต่ไม่อยู่ใน Inventory
   Π ISBN (Books) − Π ISBN (Inventory)
e) เปลี่ยนชื่อ StockQuantity เป็น AvailableStock
   ρ AvailableStock ← StockQuantity (Inventory)
ระบบสั่งซื้อ e-commerce

Customers(CustomerID, Name, Email, Address) · Orders(OrderID, CustomerID, OrderDate, TotalAmount)

ข้อ a ยืม LIKE กับ % ของ SQL มาใช้ (พีชคณิตแท้ ๆ ไม่มี operator นี้ แต่สไลด์เขียนแบบนี้)
% = ตัวอักษรอะไรก็ได้ กี่ตัวก็ได้ (รวม 0 ตัว) · '%New York%' จึงแปลว่า “มีคำว่า New York อยู่ตรงไหนก็ได้ ในข้อความ” เช่น “221 Main St, New York, NY” ก็เข้าเงื่อนไข
(รายละเอียด LIKE เต็ม ๆ อยู่ในบทที่ 6)

a) ออเดอร์เกิน $100 ของลูกค้าใน New York
   σ TotalAmount>100 (Orders)
     ⋈ σ Address LIKE '%New York%' (Customers)
b) ชื่อลูกค้ากับวันที่สั่งซื้อ
   Π Name, OrderDate (Customers ⋈ Orders)
c) รวม CustomerID จากทั้งสองตาราง
   Π CustomerID (Customers) ∪ Π CustomerID (Orders)
d) ลูกค้าที่ไม่เคยสั่งซื้อเลย
   Π CustomerID (Customers) − Π CustomerID (Orders)
e) ทุกคู่ที่เป็นไปได้        Customers × Orders
f) เปลี่ยนชื่อ TotalAmount   ρ OrderValue ← TotalAmount (Orders)

3.7ตารางแปลง SQL ↔ Relational Algebra

SQLRelational Algebraหมายเหตุ
SELECT colsΠΠ ตัดแถวซ้ำ · SELECT ไม่ตัด (ต้องใส่ DISTINCT)
WHERE pσpตรงกันเป๊ะ
FROM a, ba × bCartesian product
a JOIN b ON …σθ(a × b)theta join — join ที่เขียนเงื่อนไข (θ) เอง
a NATURAL JOIN ba ⋈ bจับคู่ทุก attribute ร่วม
UNION∪ทั้งคู่ตัดแถวซ้ำ
INTERSECT∩derived operator
EXCEPT / MINUS−
ASρrename
Π major (Student) จากตาราง Student 5 แถว (CoE, CoE, EE, CoE, ME) จะได้ผลลัพธ์กี่แถว
Π ลบแถวซ้ำเสมอ เหลือ {CoE, EE, ME} = 3 แถว · ถ้าเป็น SQL SELECT major FROM Student จะได้ 5 แถว ต้องใส่ DISTINCT ถึงจะได้ 3
ข้อใดคือเหตุผลที่ optimizer ชอบ “push σ down” (กรองก่อน join)
กฎความสมมูลรับประกันว่าผลลัพธ์เหมือนเดิมบนฐานข้อมูลทุกชุด จึงแปลงได้อย่างปลอดภัย · ประโยชน์คือ ลดจำนวนแถวที่ต้อง join ซึ่งเป็น operation ที่แพงที่สุด
ระบบจำไว้ในเบราว์เซอร์นี้ จะได้รู้ว่าอ่านถึงไหนแล้ว