ฐานข้อมูลคืออะไร ทำไมต้องใช้ DBMS
ชุดข้อมูลที่เกี่ยวข้องกัน
ข้อเท็จจริงที่บันทึกได้และมีความหมายโดยนัย
ส่วนของโลกจริงที่เราเก็บลงฐานข้อมูล เช่น เกรดและ transcript ของนักศึกษา
ซอฟต์แวร์ที่ช่วย สร้าง และ บำรุงรักษา ฐานข้อมูล
DBMS + Database (+ Applications)
Database System Environment
Software to Process Queries/Programs
↓
Software to Access Stored Data
(Meta-Data / catalog)
(ข้อมูลจริง)
ผู้ใช้ไม่แตะข้อมูลตรง ๆ ทุกอย่างวิ่งผ่าน DBMS
4 ลักษณะเด่นของ Database Technology จุดสอบ
- Self-describing catalog เก็บ meta-data (คำอธิบายฐานข้อมูล) ทำให้ DBMS ใช้กับหลายฐานข้อมูลได้
- Program-data independence แก้โครงสร้างการเก็บได้โดยไม่ต้องแก้โปรแกรม
- Data abstraction ใช้ data model ซ่อนรายละเอียดการเก็บ ให้ผู้ใช้เห็นภาพเชิงแนวคิด
- Multiple views ผู้ใช้แต่ละกลุ่มเห็นมุมมองต่างกัน เช่น Transcript view กับ Prerequisite view ที่ได้จากตารางชุดเดียวกัน
ประโยชน์เพิ่มเติม 12 ข้อ
Redundancy แบบควบคุม vs ไม่ควบคุม
GRADE_REPORT ที่ใส่ StudentName ซ้ำไว้ (ข้อมูลจริง: เลข 17 = Smith)
(a) Controlled
| No | Name | Sec |
|---|---|---|
| 17 | Smith | 112 |
| 8 | Brown | 85 |
(b) Uncontrolled
| No | Name | Sec |
|---|---|---|
| 17 | Brown | 112 |
| 8 | Brown | 85 |
Actors on the scene
- DBA ดูแลระบบ ติดตั้ง ปรับแต่งประสิทธิภาพ backup ความปลอดภัย
- Database designer วิเคราะห์ความต้องการข้อมูล ออกแบบตาราง ความสัมพันธ์ และกฎ
- End users แบ่งเป็น Casual, Naïve/parametric และ Sophisticated
- System analysts & application programmers
เมื่อไหร่ ไม่ ควรใช้ DBMS
- ต้นทุนเริ่มต้นสูง และมี overhead จาก security, concurrency, recovery, integrity
- ระบบเรียบง่าย นิยามชัด และไม่น่าจะเปลี่ยน
- มีข้อกำหนด real-time เข้มงวดจน overhead ของ DBMS รับไม่ได้
- ไม่มีผู้ใช้หลายคนเข้าถึงพร้อมกัน
- DBMS ไม่พอ เมื่อข้อมูลซับซ้อนเกินกว่าที่ model รองรับ หรือต้องการ operation พิเศษ
Data Models
Data model คือชุดแนวคิดที่ใช้อธิบาย โครงสร้าง และ constraints ของฐานข้อมูล ส่วน operations คือการ retrieve และ update
| ประเภท | ระดับ | ความหมาย |
|---|---|---|
| Conceptual | สูง (semantic) | ใกล้กับที่ผู้ใช้มองเห็น เรียกอีกชื่อว่า entity-based หรือ object-based |
| Practical (Implementation) | กลาง | แบบ record-oriented อยู่ระหว่างสองระดับ |
| Physical | ต่ำ (internal) | อธิบายวิธีเก็บในคอมพิวเตอร์ |
| Model | จุดจำ |
|---|---|
| Relational | E.F. Codd 1970, ขายเชิงพาณิชย์ครั้งแรก 1981–82 (Oracle, Sybase, Informix, Ingres) |
| Network | implement เป็นตัวแรก โดย Honeywell IDS ปี 1964–65 ได้รับการผลักดันจาก CODASYL (IDMS, IMAGE) |
| Hierarchical | IBM + North American Rockwell ราวปี 1965 กลายเป็นตระกูล IMS |
| Object-Oriented | มี class, method และ IS_A (inheritance) เช่น GEMSTONE, O2, ORION |
| Object-Relational | ตารางเก็บ object ได้ เช่น ILLUSTRA |
| Semi-structured | entity กลุ่มเดียวกันอาจมี attribute ไม่เท่ากัน ไม่ต้องมีครบ และ type ต่างกันได้ เช่น XML |
Hierarchical = ต้นไม้
Pavement Improvement ├ Reconstruction ├ Maintenance │ ├ Routine │ ├ Corrective │ └ Preventive └ Rehabilitationลูกแต่ละตัวมี parent ได้ ตัวเดียว
Network = กราฟ
Joint Seal ─┐
├→ Asphalt
Patching ───┘ Sealantnode มี parent ได้ หลายตัว จึงทำ M:N ได้ข้อสอบ "ข้อใดเป็นประโยชน์"
ควบคุมความซ้ำซ้อน · แชร์ข้อมูล · กันสิทธิ์ · หลาย interface · ความสัมพันธ์ซับซ้อน · integrity · backup/recovery · มาตรฐาน · ยืดหยุ่น · พัฒนาเร็ว · ข้อมูลปัจจุบัน · ประหยัดเมื่อใหญ่
มักเป็นตัวลวงในข้อ "ข้อใดไม่ใช่ประโยชน์"
ลงทุนเริ่มต้นสูง · อาจต้องซื้อฮาร์ดแวร์เพิ่ม · overhead จาก security, concurrency control, recovery, integrity · ช้าเกินไปสำหรับ real-time เข้มงวด
Schema, 3 ระดับ, ภาษา และขั้นการออกแบบ
Schema vs Instance จุดสอบ
| Schema | Instance (State) | |
|---|---|---|
| คือ | โครงสร้างเชิงตรรกะ + constraints | ข้อมูลจริง ณ เวลาหนึ่ง |
| เปรียบ | "ชนิดของตัวแปร" | "ค่าของตัวแปร" |
| เปลี่ยน | ไม่บ่อย | บ่อย |
Schema diagram แสดงแค่ชื่อตารางกับชื่อคอลัมน์ ในตารางจริง ชื่อตาราง = Entity, หัวคอลัมน์ = Attribute, แถว = Tuple (instance)
Three-Schema Architecture จุดสอบ
External views หลายมุมมอง
โครงสร้าง + constraints ทั้งหมด
physical storage, access paths
มีขึ้นเพื่อรองรับ program-data independence และ multiple views
โปรแกรมอ้างถึง External schema แล้ว DBMS map ลงไปถึง Internal เพื่อ execute
Data Independence
Logical เปลี่ยน Conceptual schema (เพิ่ม/ลบตาราง คอลัมน์ ความสัมพันธ์) โดยที่ External view และโปรแกรมไม่ต้องแก้
Physical เปลี่ยน Internal schema หรือฮาร์ดแวร์ โดยที่ Conceptual และ External ไม่กระทบ
เปลี่ยนแค่ mapping ระหว่างชั้น ส่วนชั้นบนไม่ต้องแก้
DBMS Languages
| ภาษา | ใช้ทำอะไร |
|---|---|
| DDL | DBA และ designer ใช้นิยาม conceptual schema ผ่าน DDL compiler แล้วผลไปเก็บใน Data Dictionary |
| SDL | Storage Definition กำหนด internal schema |
| VDL | View Definition กำหนด external schema |
| DML | retrieve และ update แบ่งเป็น Procedural (บอกว่าเอาอะไรและได้มาอย่างไร) กับ Declarative (บอกแค่ว่าเอาอะไร) ฝังใน host language อย่าง C หรือ COBOL ได้ หรือใช้เดี่ยวเป็น query language |
Data Dictionary เก็บ metadata ได้แก่ schema, storage definition, integrity constraints (domain, referential integrity, assertions) และ authorization
ตัวอย่าง Data Dictionary
| Data Item | Type | Format | Bytes | Example |
|---|---|---|---|---|
| Customer ID | String | XNNNNNN | 7 | M123456 |
| First Name | String | – | 25 | สมชาย |
| Date of Birth | Date | DD/MM/YYYY | 8 | 24/05/1998 |
| Platinum Member | Boolean | X | 1 | 1 |
| Spending | Floating | $XX.XX | 4 | 145,000 |
Utilities และการจำแนก DBMS
Utilities ได้แก่ loading, backup, reorganize, report generation, performance monitoring, sorting และ compression
การจำแนก DBMS: ตาม model (Traditional = Relational/Network/Hierarchical, Emerging = OO/OR), single-user หรือ multi-user, centralized หรือ distributed
Main Phases of Database Design จุดสอบ
ER Diagram→ 2 Logical design
แปลง ER เป็นตาราง→ 3 Physical design
การเก็บระดับฮาร์ดแวร์
ขาข้างหนึ่งทำ Functional requirements ไปเป็น Transaction spec แล้วต่อเป็น Application program design ขั้น Conceptual ยัง DBMS-independent ส่วนตั้งแต่ Logical ลงไปเป็น DBMS-specific
เขียน ER Diagram ให้ถูกสัญลักษณ์
สัญลักษณ์ ER (Chen notation) จุดสอบ
ประเภท Attribute
| ประเภท | ความหมาย | ตัวอย่าง |
|---|---|---|
| Key | ค่าไม่ซ้ำในแต่ละ entity | SSN, Roll_No |
| Simple | ค่าเดียวแบบ atomic | Sex |
| Composite | แตกย่อยได้ และซ้อนเป็นลำดับชั้นได้ | Address → Street, City, State, Zip |
| Multivalued | มีได้หลายค่า เขียนเป็น { } | {Color}, {Phone} |
| Derived | คำนวณได้จาก attribute อื่น | Age จาก BirthDate |
Key อาจเป็น composite เช่น VehicleTag = (Number, State) และ entity หนึ่งมีได้หลาย key เช่น CAR มีทั้ง VIN และ VehicleTag ส่วน {PreviousDegrees(College, Year, Degree)} คือ composite ที่เป็น multivalued ด้วย
Entity type = กลุ่ม entity ที่มี attribute ชุดเดียวกัน ส่วน Entity set = entity ทั้งหมดที่มีอยู่จริง ณ ตอนนั้น
Degree ของ Relationship
| Degree | จำนวน entity | ตัวอย่าง |
|---|---|---|
| Unary / Recursive | 1 | Person — Married to, EMPLOYEE — Supervision |
| Binary | 2 | Student — Enrolled in — Course |
| Ternary | 3 | STUDENT, COURSE, INSTRUCTOR |
| N-ary | n | DOCTOR, PATIENT, PRESCRIPTION, HOSPITAL |
Roles คือป้ายบนเส้นที่บอกบทบาท เช่น supervisor/supervisee หรือ manager/worker จะใส่หรือไม่ใส่ก็ได้ แต่จำเป็นมากใน recursive
Relationship มี attribute ได้ เช่น depositor(access_date), Works_on(Hours), ร้านค้า–ใช้–บัตรเครดิต(จำนวนเงิน, วันที่)
Cardinality ratio ดูจาก mapping จริง จุดสอบ
Cardinality = จำนวน สูงสุด ที่ entity หนึ่งเข้าร่วม relationship ได้ ส่วน Participation = จำนวน ต่ำสุด ถ้า "ทุกตัวต้องมี" เป็น total (เส้นคู่ หรือเรียกว่า existence dependency) ถ้า "บางตัว" เป็น partial
| Notation อื่น | อ่านว่า |
|---|---|
| ลูกศร → ชี้ไปฝั่ง "one" | customer ← borrower — loan หมายถึงลูกค้า 1 คนมีหลาย loan |
| min..max บนเส้น | customer 0..* borrower 1..1 loan หมายถึงลูกค้ากู้ได้ 0 ถึงหลาย loan และ loan แต่ละอันมีลูกค้า 1 คนพอดี (loan เป็น total) |
Weak Entity
- ไม่มี key เป็นของตัวเอง
- ระบุตัวตนด้วย partial key + PK ของ owner
- เชื่อมกับ owner ผ่าน identifying relationship (double diamond) แบบ 1:N และฝั่ง weak เป็น total
- เช่น DEPENDENT ของ EMPLOYEE, BANK-BRANCH ของ BANK
- ใน ER จะไม่เขียน PK ของ owner ลงใน weak entity เพราะมันอยู่ใน relationship อยู่แล้ว
วาด ER 6 ขั้น จุดสอบ
ตัวอย่างการอ่านจากสไลด์: "อย่างน้อย ทุก Project ต้องถูกควบคุมได้ มากสุดเพียง 1 Department" ได้ PROJECT เป็น total และ cardinality N:1
COMPANY ER Diagram (ตัวอย่างหลักที่ใช้ทั้งบท 3 และบท 4)
Requirement: แผนกมีผู้จัดการ 1 คน (เก็บ start date) และมีได้หลาย location แผนกควบคุมหลายโปรเจกต์ พนักงานสังกัด 1 แผนกแต่ทำได้หลายโปรเจกต์ (เก็บชั่วโมง) มี supervisor และอาจมี dependents หลายคน
| Relationship | Cardinality | Participation | Attribute |
|---|---|---|---|
| WORKS_FOR | EMPLOYEE N : 1 DEPARTMENT | total ทั้งสองฝั่ง | – |
| MANAGES | 1 : 1 | EMPLOYEE partial, DEPARTMENT total | Start_date |
| CONTROLS | DEPARTMENT 1 : N PROJECT | DEPT partial, PROJECT total | – |
| WORKS_ON | EMPLOYEE M : N PROJECT | total ทั้งสองฝั่ง | Hours |
| SUPERVISION | recursive 1 : N | partial | – |
| DEPENDENTS_OF | identifying 1 : N | DEPENDENT total | – |
ER อื่น ๆ ในสไลด์: BANK, customer, ตัวอย่างภาษาไทย
| BANK database | ||
|---|---|---|
| BRANCHES (identifying) | BANK 1 : N BANK-BRANCH (weak, partial key BranchNo) | total |
| ACCTS / LOANS | BANK-BRANCH 1 : N ACCOUNT / LOAN | ACCOUNT, LOAN total |
| A-C / L-C | ACCOUNT, LOAN M : N CUSTOMER | ACCOUNT, LOAN total |
| ตัวอย่างไทย (มหาวิทยาลัย) | ||
|---|---|---|
| ทำงาน | พนักงาน N : 1 คณะ | พนักงาน total |
| เป็นคณบดี | พนักงาน 1 : 1 คณะ | คณะ total |
| เป็นนักศึกษา | คณะ 1 : N นักศึกษา | นักศึกษา total |
แบบฝึกเขียน ER: ระบบจองห้องประชุม และแพลตฟอร์มดูแลผู้สูงอายุ
จองห้องประชุมออนไลน์
- ผู้ใช้ (รหัส, ชื่อ, อีเมล, โทร, ตำแหน่ง) ผู้ใช้ N : 1 หน่วยงาน
- ห้องประชุม (รหัส, ชื่อ, อาคาร, ชั้น, ความจุ, อุปกรณ์, สถานะ)
- การจอง (วันที่, เริ่ม, สิ้นสุด, หัวข้อ, จำนวนคน, สถานะ) การจอง N : 1 ห้อง และต้องบันทึกผู้จอง
- ผู้ดูแลระบบ อนุมัติหรือปฏิเสธการจอง
ดูแลผู้สูงอายุอัจฉริยะ
- ผู้สูงอายุ M : N ผู้ดูแล (มี attribute ความสัมพันธ์ เช่น บุตร หลาน)
- การตรวจสุขภาพ บันทึกโดยเจ้าหน้าที่หรืออาสาสมัคร
- ผู้สูงอายุ M : N กิจกรรม (มีสถานะการเข้าร่วม)
- การนัดหมาย กับบุคลากรการแพทย์
- ผู้สูงอายุ 1 : N อุปกรณ์ IoT, อุปกรณ์ 1 : N ข้อมูลที่วัดได้, ข้อมูลผิดปกติทำให้เกิดการแจ้งเตือน
แปลง ER เป็นตาราง 7 ขั้น
Strong entity
สร้างตารางที่มี simple attribute ทั้งหมด ถ้าเป็น composite ให้เก็บเฉพาะส่วนย่อย ไม่เอา multivalued และ derived
PROJECT(PName, PNumber, PLocation)
Weak entity
PK = PK ของ owner + partial key และ PK ของ owner เป็น FK ไปด้วย
Binary 1:1
เลือกตาราง S (ควรเป็นฝั่งที่ total) แล้วใส่ PK ของอีกฝั่งเป็น FK ใน S ย้าย attribute ของ relationship มาด้วย ถ้า total ทั้งคู่จะรวมเป็นตารางเดียวก็ได้
Binary 1:N
เอา PK ฝั่ง "1" ไปใส่ในตารางฝั่ง "N" เป็น FK ใช้กับ recursive ได้ด้วย
PROJECT(…, Dno)
Binary M:N
สร้าง ตารางใหม่ โดย PK = PK ของทั้งสองฝั่งรวมกัน (และแต่ละตัวเป็น FK) แล้วเก็บ attribute ของ relationship ที่นี่
Multivalued attribute
สร้างตารางใหม่เก็บค่านั้นกับ PK ของเจ้าของ ให้ PK = ทั้งสองคอลัมน์
N-ary (n > 2)
สร้างตารางใหม่ใส่ PK ของทุก entity ที่เข้าร่วมเป็น FK ตัวอย่างในสไลด์คือ Managed ที่เชื่อม Store, Inventory, Market, Sales
ผลลัพธ์สุดท้ายของ COMPANY จุดสอบ
| Relation | Attributes | FK อ้างถึง | มาจากขั้น |
|---|---|---|---|
| EMPLOYEE | Ssn, Fname, Minit, Lname, Address, Bdate, Salary, Sex, Dno, superSsn | Dno→DEPARTMENT, superSsn→EMPLOYEE | 1, 4 |
| DEPARTMENT | DName, DNumber, Mssn, Start_Date | Mssn→EMPLOYEE | 1, 3 |
| DEPT_LOCATION | DNumber, DLocation | DNumber→DEPARTMENT | 6 |
| PROJECT | PName, PNumber, Plocation, Dno | Dno→DEPARTMENT | 1, 4 |
| WORKS_ON | Ssn, Pno, Hours | Ssn→EMPLOYEE, Pno→PROJECT | 5 |
| DEPENDENT | Ssn, Dep_Name, Dep_sex, Birth_date, Relationship | Ssn→EMPLOYEE | 2 |
Number_of_employees เป็น derived จึงไม่มีในตารางใดเลย เพราะคำนวณด้วย COUNT ได้
แบบฝึกท้ายบท: ร้านค้าออนไลน์ (CustomerInfo / OrderCartInfo / InventoryItem / ItemType / Category) พร้อมแนวคำตอบ
ER: CustomerInfo 1:N OrderCartInfo (has), OrderCartInfo M:N InventoryItem (lineItems: orderPrice, qtyOrdered), ItemType 1:N InventoryItem (hasInStock, identifying และ InventoryItem เป็น weak), Category 1:N ItemType (belongsTo) ส่วน totalPrice เป็น derived และ date เป็น composite (oYear, oMonth, oDay)
Category(categoryId, description)
ItemType(itemNum, name, picture, price, categoryId)
OrderCartInfo(orderCartId, type, oYear, oMonth, oDay, loginName)
InventoryItem(itemNum, code, qtyInStock, itemColor, itemSize)
LineItems(orderCartId, itemNum, code, orderPrice, qtyOrdered)
แนวคำตอบนี้คิดเพิ่มเอง ไม่ได้มาจากสไลด์ และในรูปไม่ชัดว่า loginName เป็น key จริงหรือไม่
FD, Normalization และ Lossless Join
Guidelines 4 ข้อของตารางที่ดี
- ความหมายชัด หนึ่งแถวแทน 1 entity หรือ 1 เหตุการณ์ ไม่เอาหลาย entity มาปนในตารางเดียว และอ้างถึงกันด้วย FK เท่านั้น
- ไม่มี anomaly ทั้ง insert, delete และ update
- NULL น้อยที่สุด NULL เกิดได้ 3 แบบคือ not applicable, unknown และ unavailable ถ้าคอลัมน์ไหน NULL เยอะให้แยกออกเป็นตารางใหม่
- ไม่มี spurious tuples แยกตารางแล้วต้อง join กลับได้ข้อมูลเดิม (lossless) ห้ามเสียคุณสมบัตินี้ ส่วน dependency preservation ยอมเสียได้บางกรณี
ตัวอย่างตารางที่ไม่ดี: ใส่ Phone No หลายเบอร์ในช่องเดียว และเอาชื่อแผนกมาปนกับข้อมูลพนักงาน
Anomalies จุดสอบ
EMP_PROJ(Ssn, Pnumber, Hours, Ename, Pname, Plocation)
| ชนิด | เกิดอะไรขึ้น |
|---|---|
| Update | เปลี่ยนชื่อโปรเจกต์ 2 ต้องแก้ทุกแถว ถ้าแก้ไม่ครบข้อมูลจะขัดกัน |
| Insertion | เพิ่มโปรเจกต์ใหม่ไม่ได้ ถ้ายังไม่มีพนักงานทำโปรเจกต์นั้น |
| Deletion | ลบพนักงานคนสุดท้ายของโปรเจกต์ ข้อมูลโปรเจกต์หายไปด้วย |
Anomaly ไม่ได้เกิดจาก SQL แต่เกิดจากการออกแบบตาราง
| Ssn | Pno | Ename | Pname | Ploc |
|---|---|---|---|---|
| 123456789 | 1 | Smith | ProductX | Bellaire |
| 123456789 | 2 | Smith | ProductY | Sugarland |
| 453453453 | 1 | English | ProductX | Bellaire |
Functional Dependency
X → Y อ่านว่า "X กำหนด Y" ถ้า t1[X] = t2[X] แล้วต้องได้ t1[Y] = t2[Y] เสมอ FD มาจาก business rule ของโลกจริง ไม่ใช่ดูจากข้อมูลไม่กี่แถว และถ้า K เป็น key แล้ว K → ทุก attribute
Armstrong's Axioms
| Reflexivity | Y ⊆ X ⇒ X→Y |
| Augmentation | X→Y ⇒ XZ→YZ |
| Transitivity | X→Y, Y→Z ⇒ X→Z |
กฎเสริม
| Decomposition | X→YZ ⇒ X→Y, X→Z |
| Union | X→Y, X→Z ⇒ X→YZ |
| Pseudotransitivity | X→Y, WY→Z ⇒ WX→Z |
F⁺ = FD ทุกตัวที่อนุมานได้จาก F ส่วน X⁺ = attribute ทุกตัวที่ X กำหนดได้ ถ้า X⁺ ครอบคลุมทุกคอลัมน์ X เป็น superkey ถ้าตัดอะไรออกไม่ได้อีกแล้ว X คือ candidate key
Minimal cover: RHS มีตัวเดียว, ไม่มี FD ซ้ำซ้อน, LHS ไม่มีตัวเกิน
ลองหา Closure แบบฝึก 2
R(A,B,C,D,E), F = {A→B, B→C, AC→D, D→E}
A เป็น superkey และเป็น candidate key ส่วน AC ก็เป็น superkey แต่ไม่ใช่ candidate key เพราะ C เกินมา
Keys และ Prime attribute
ชุดที่มี candidate key อยู่ข้างใน อาจมีตัวเกิน
ชุด เล็กที่สุด ที่ระบุแถวได้ไม่ซ้ำ
candidate key ที่เลือกมาเป็นหลัก
candidate key ตัวอื่นที่ไม่ได้ถูกเลือก
attribute ที่อยู่ใน candidate key ใดก็ได้
attribute ที่ไม่อยู่ใน candidate key ใดเลย
บันได Normal Form จุดสอบ
ทุกช่องเป็นค่า atomic ไม่มี multivalued, composite หรือ nested
1NF + ไม่มี partial dependency คือ non-prime ต้องขึ้นกับ key ทั้งชุด
2NF + ไม่มี transitive dependency คือ non-prime ห้ามกำหนด non-prime
ทุก determinant ต้องเป็น superkey (เข้มกว่า 3NF)
ไม่มี MVD (X →→ Y) ที่ X ไม่ใช่ superkey
Join dependency หรือ PJNF แยกเป็นตั้งแต่ 3 ตารางแล้ว join กลับได้ถูก
งานจริงทำถึง 3NF หรือ BCNF ก็พอ ส่วน Denormalization คือการรวมตารางกลับเพื่อประสิทธิภาพ แลกกับการยอมคุมความซ้ำซ้อนเอง
→ 2NF
EMP_PROJ(SSN, PNumber, EName, Hours, PName, PLocation)
- SSN → EName (partial)
- PNumber → PName, PLocation (partial)
- {SSN, PNumber} → Hours (full)
แยกเป็น
EP2(PNumber, PName, PLocation)
EP3(SSN, PNumber, Hours)
→ 3NF
EMP_DEPT(SSN, EName, BDate, Address, DNumber, DName, Dmgr_ssn)
- SSN → …, DNumber
- DNumber → DName, Dmgr_ssn ทำให้เกิด transitive
แยกเป็น
ED2(DNumber, DName, Dmgr_ssn)
→ BCNF
ลงทะเบียน(StudentID, CourseID, Advisor)
- {StudentID, CourseID} → Advisor
- Advisor → CourseID แต่ Advisor ไม่ใช่ superkey
แยกเป็น
(StudentID, Advisor)
TEACH(Student, Course, Instructor) ก็เป็นแบบเดียวกัน คือผ่าน 3NF แต่ไม่ผ่าน BCNF
ทดสอบ Lossless Join ด้วย Matrix (Chase)
แถว = ตารางย่อย, คอลัมน์ = attribute ถ้าตารางย่อยมี attribute นั้นใส่ a ถ้าไม่มีใส่ b แล้วไล่ใช้ FD ทีละตัว แถวไหนที่ LHS เท่ากันให้ทำ RHS ให้เท่ากัน ถ้าสุดท้ายมีแถวที่เป็น a ครบทุกช่อง แปลว่า Lossless
R1 = EMP(SSN, ENAME), R2 = PROJECT(PNUMBER, PNAME, PLOCATION), R3 = WORKS_ON(SSN, PNUMBER, HOURS)
| SSN | ENAME | PNUM | PNAME | PLOC | HOURS | |
|---|---|---|---|---|---|---|
| R1 | a1 | a2 | b13 | b14 | b15 | b16 |
| R2 | b21 | b22 | a3 | a4 | a5 | b26 |
| R3 | a1 | b32→a2 | a3 | b34→a4 | b35→a5 | a6 |
SSN→ENAME (R1 กับ R3 มี SSN เท่ากัน) ทำให้ R3.ENAME เป็น a2 ส่วน PNUMBER→PNAME, PLOC (R2 กับ R3) ทำให้ R3 ได้ a4 และ a5 ผลคือ R3 เป็น a ครบทุกช่อง จึง Lossless
ถ้าแยกเป็น EMP_LOCS(ENAME, PLOCATION) กับ EMP_PROJ1(…) matrix จะไม่เปลี่ยนเลย จึงเป็น Lossy และเกิด spurious tuples
ทางลัด: แยกเป็น 2 ตาราง
{R1, R2} เป็น lossless เมื่อ
(R1 ∩ R2) → (R1 − R2) หรือ (R1 ∩ R2) → (R2 − R1)
หมายถึงคอลัมน์ที่ใช้ร่วมกันต้องเป็น key ของตารางใดตารางหนึ่ง
แบบฝึก 4: R(A,B,C), F={A→B} แยกเป็น R1(A,B), R2(A,C) จะได้ R1∩R2 = A และ A→B = R1−R2 จึงเป็น Lossless
คุณสมบัติที่ต้องเช็กหลังแยกตาราง
Attribute preservation, Dependency preservation และ Lossless join
Dangling tuple
แถวที่หาคู่ไม่เจอจะหายไปจาก INNER/NATURAL JOIN ถ้าอยากเก็บไว้ต้องใช้ OUTER JOIN (LEFT/RIGHT/FULL) ซึ่งช่องที่ไม่มีคู่จะเป็น NULL
4NF: Multivalued Dependency
| EName | PName | DName |
|---|---|---|
| Smith | X | Anna |
| Smith | X | John |
| Smith | Y | Anna |
| Smith | Y | John |
โปรเจกต์กับผู้ติดตามไม่เกี่ยวกัน แต่ต้องเก็บทุกคู่ผสม EName →→ PName และ EName →→ DName
แก้โดยแยกเป็น EMP_PROJECTS(EName, PName) และ EMP_DEPENDENTS(EName, DName)
MVD จะเป็น trivial เมื่อ Y ⊆ X หรือ X ∪ Y = R และ FD ถือเป็นกรณีพิเศษของ MVD
แบบฝึกในชั้น 1 และ 3 พร้อมแนวคำตอบ
แบบฝึก 1: ORDER_ALL(OrderID, OrderDate, CustomerID, CustomerName, ProductID, ProductName, Qty, UnitPrice) ให้หาข้อมูลที่ซ้ำและยกตัวอย่าง anomaly ทั้ง 3 แบบ
แบบฝึก 3: SALES(OrderID, OrderDate, CustomerID, CustomerName, ProductID, ProductName, UnitPrice, Qty) มี Key = {OrderID, ProductID}
2NF: ORDER(OrderID, OrderDate, CustomerID, CustomerName) · PRODUCT(ProductID, ProductName, UnitPrice) · ORDER_LINE(OrderID, ProductID, Qty)
3NF: ORDER(OrderID, OrderDate, CustomerID) + CUSTOMER(CustomerID, CustomerName)
แนวคำตอบนี้คิดเพิ่มเอง ไม่ได้มาจากสไลด์
Relation เข้า → ตัวดำเนินการ → Relation ออก
พื้นฐาน
- Closure property ผลลัพธ์ของทุก operator ยังเป็น relation จึงซ้อนกันได้
- Degree = จำนวน attribute, Cardinality = จำนวน tuple
- Relation ไม่มีลำดับแถว ไม่มีลำดับคอลัมน์ และไม่มี tuple ซ้ำ
- แต่ละ tuple คือประโยคที่เป็นจริงของ predicate (Intension = รูปแบบของประโยค, Extension = ชุดประโยคที่จริง ณ ตอนนั้น)
- Closed World Assumption ถ้า tuple ไม่อยู่ในตาราง แปลว่าฐานข้อมูลไม่รับรองว่าจริง
ข้อมูลตัวอย่างที่ใช้ทั้งบท
| IS_CALLED | |
|---|---|
| S1 | Anne |
| S2 | Boris |
| S3 | Cindy |
| S4 | Devinder |
| S5 | Boris |
| IS_ENROLLED_ON | |
|---|---|
| S1 | C1 |
| S1 | C2 |
| S2 | C1 |
| S3 | C3 |
| S4 | C1 |
| EXAM_MARK | ||
|---|---|---|
| S1 | C1 | 85 |
| S1 | C2 | 49 |
| S2 | C1 | 49 |
| S3 | C3 | 66 |
| S4 | C1 | 93 |
COURSE มี C1 Database, C2 HCI, C3 Op Systems และ C4 Programming
Selection
เลือก แถว ตามเงื่อนไข (WHERE)
| S2 | Boris |
| S5 | Boris |
Projection
เลือก คอลัมน์ และตัดแถวที่ซ้ำ (SELECT DISTINCT)
ได้ S1, S2, S3, S4 คือ 4 แถวจากเดิม 5 เพราะ S1 ที่ซ้ำเหลือตัวเดียว
Natural JOIN
จับคู่แถวด้วย attribute ที่ชื่อเหมือนกัน
| S1 | Anne | C1 |
| S1 | Anne | C2 |
| S2 | Boris | C1 |
| S3 | Cindy | C3 |
| S4 | Devinder | C1 |
S5 หายไปเพราะไม่มีคู่ (dangling) ถ้าชื่อคอลัมน์ไม่ตรงกันใช้ Theta join ⋈cond
RENAME
เปลี่ยนชื่อ attribute ซึ่งเปลี่ยนแค่ heading ค่าในตารางเหมือนเดิม (SQL ใช้ AS)
ใช้ทำ Self join เช่นหาคนที่ชื่อเหมือนกัน (S2 กับ S5 ชื่อ Boris) โดยใส่ WHERE Sid1 < Sid2 กันการจับคู่กับตัวเอง
EXTEND
เพิ่มคอลัมน์ที่คำนวณได้ จำนวนแถวเท่าเดิม
ได้ S1 Anne A, S2 Boris B, …
UNION
รวมแถวในแนวตั้งและตัดตัวซ้ำ โดยต้อง union-compatible คือจำนวน attribute เท่ากัน ชื่อตรงกัน และ domain ตรงกัน
มีคุณสมบัติ commutative, associative และ idempotent (R ∪ R = R) ใน SQL ถ้าใช้ UNION ALL จะเก็บแถวซ้ำไว้
Difference / NOT MATCHING
หาแถวที่ไม่มีคู่ ตรงกับ SQL NOT EXISTS หรือ EXCEPT
ได้ (S5, Boris) คือคนที่ไม่ได้ลงทะเบียน
SUMMARIZE / Aggregate
COUNT=5, SUM=342, AVG=68.4, MAX=93, MIN=49 ส่วน aggregate คืนค่าเดียว ไม่ได้คืน relation
| C1 | 93 |
| C2 | 49 |
| C3 | 66 |
SUMMARIZE BY vs PER จุดสอบ
BY
แสดงเฉพาะกลุ่มที่มีอยู่ในข้อมูล
PER COURSE{CourseId}
ใช้ relation อื่นเป็นตัวกำหนดกลุ่ม จึงแสดงกลุ่มที่ว่างด้วย
| C1 | 3 |
| C2 | 1 |
| C3 | 1 |
| C4 | 0 |
คุณสมบัติของ JOIN
r ⋈ r = r ถ้า attribute เหมือนกันทั้งหมด JOIN จะกลายเป็น ∩ ถ้าไม่มี attribute ร่วมเลยจะกลายเป็น × (TIMES) และ JOIN เป็น commutative กับ associative ทำให้ DBMS สลับลำดับเพื่อ optimize ได้
RA ↔ Tutorial D ↔ SQL
| ทำอะไร | RA | Tutorial D | SQL |
|---|---|---|---|
| เลือกแถว | σ | WHERE | WHERE |
| เลือกคอลัมน์ | π | {A, B} | SELECT DISTINCT |
| เชื่อม | ⋈ | JOIN | JOIN |
| เปลี่ยนชื่อ | ρ | RENAME | AS |
| รวม | ∪ | UNION | UNION |
| ผลต่าง | − | NOT MATCHING / MINUS | NOT EXISTS / EXCEPT |
| จัดกลุ่ม | γ | SUMMARIZE | GROUP BY |
SQL ยอมให้มีแถวซ้ำได้ แต่ relation ในทางทฤษฎีเป็นเซต จึงไม่มีแถวซ้ำ
สูตรแก้โจทย์ RA: เลือกแถว → เชื่อม → คำนวณ → เลือกคอลัมน์
โจทย์: หารหัส ชื่อ และคะแนนของคนที่สอบ C1 ได้ตั้งแต่ 50 ขึ้นไป
π StudentId, Name, Mark ( IS_CALLED ⋈ σ CourseId='C1' ∧ Mark≥50 (EXAM_MARK) )
ขั้นตอนตามสไลด์: (1) หาตารางที่มีคอลัมน์ที่ต้องใช้ (2) σ (3) ⋈ (4) EXTEND หรือ aggregate (5) π (6) ตรวจ heading ก่อนทำ ∪ หรือ − แล้วเขียนจากวงเล็บในสุดออกมา
Constraint เขียนแบบ RA ได้ เช่น IS_EMPTY(EXAM_MARK WHERE Mark < 0 OR Mark > 100) ส่วน KEY ใช้กันค่าซ้ำ และ FOREIGN KEY ใช้รักษาการอ้างอิงให้ถูกต้อง
แบบฝึก UNION และโจทย์ท้ายบท
- {S1,S2,S4} ∪ {S2,S3,S4} ได้ {S1,S2,S3,S4} cardinality = 4
- R(StudentId) กับ S(Sid) ยัง UNION ไม่ได้ ต้องใช้ ρ StudentId←Sid ก่อน ถ้าจำนวนคอลัมน์ไม่เท่ากันให้ π ฝั่งที่เกินออก
- "ชื่อ Boris หรือ ลง C1": π StudentId(σ Name='Boris'(IS_CALLED)) ∪ π StudentId(σ CourseId='C1'(IS_ENROLLED_ON)) ได้ {S1,S2,S4,S5}
- π StudentId(IS_CALLED) ∪ π CourseId(IS_ENROLLED_ON) ผิด เพราะความหมายของคอลัมน์ต่างกัน ห้าม RENAME CourseId เป็น StudentId
- โจทย์ท้ายบท: คนที่ลง C1 แต่ไม่ได้สอบ C1 (ใช้ −), ชื่อคนที่ได้คะแนนสูงสุดของแต่ละวิชา, วิชาที่ไม่มีคนสอบ (ใช้ SUMMARIZE PER), ทำไม π ทำให้ cardinality ลดลง (เพราะตัดแถวซ้ำ) และแปลง RA เป็น SQL
ลงมือสร้างและจัดการข้อมูล
เปิด phpMyAdmin ผ่าน XAMPP (macOS)
utf8mb4_general_ci → Create→ตั้งชื่อตาราง + จำนวนคอลัมน์ → Create→กรอกฟอร์ม → Preview SQL → Save
phpMyAdmin เป็นแค่ หน้าจอ (interface) สำหรับ MySQL/MariaDB ไม่ใช่ตัวฐานข้อมูล เข้าได้ที่ http://localhost/phpmyadmin
ช่องในฟอร์มสร้างคอลัมน์
| ช่อง | หมายถึง |
|---|---|
| Type / Length | INT, VARCHAR(100), DATE, DECIMAL(10,2) (รวม 10 หลัก ทศนิยม 2) |
| Default | None, As defined, NULL, CURRENT_TIMESTAMP |
| Collation | กติกาเทียบและเรียงข้อความ ใช้กับ VARCHAR/TEXT เท่านั้น ไม่มีผลกับ INT |
| Attributes | UNSIGNED = ห้ามค่าติดลบ |
| Null | ติ๊ก = ยอมให้ว่างได้ ไม่ติ๊ก = NOT NULL (NULL ≠ 0 ≠ '') |
| Index | PRIMARY, UNIQUE, INDEX, FULLTEXT |
| A_I | Auto Increment รันเลขให้เอง เหมาะกับ PK |
| Storage Engine | InnoDB ต้องใช้ตัวนี้ถึงจะทำ Foreign Key ได้ |
หน้า Structure ใช้ Change แก้คอลัมน์, Drop ลบ และ Add column after … เพิ่มคอลัมน์ต่อท้ายคอลัมน์ที่เลือก
DDL ที่ใช้ในแท็บ SQL
CREATE DATABASE IF NOT EXISTS BonusSuki CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; CREATE TABLE Member_test( member_id INT NOT NULL AUTO_INCREMENT, full_name VARCHAR(100) NOT NULL, phone VARCHAR(20) NOT NULL, joined_at DATE NOT NULL, PRIMARY KEY (member_id) ) ENGINE=InnoDB; ALTER TABLE member_test ADD COLUMN email VARCHAR(150) NULL; ALTER TABLE member_test MODIFY COLUMN phone VARCHAR(25) NOT NULL; ALTER TABLE member_test RENAME COLUMN full_name TO member_name; ALTER TABLE member_test DROP COLUMN email; DROP DATABASE IF EXISTS BonusSuki;
ลำดับ clause ของ SELECT จุดสอบ
SELECT คอลัมน์→FROM ตาราง→JOIN … ON→WHERE กรองแถว→GROUP BY→HAVING กรองกลุ่ม→ORDER BY ASC/DESC→LIMIT
ตาราง Products (ใช้ทุกตัวอย่าง)
| ID | Name | Price | Stock | Category |
|---|---|---|---|---|
| 1 | ปากกา | 15.00 | 100 | เครื่องเขียน |
| 2 | ดินสอ | 10.00 | 80 | เครื่องเขียน |
| 3 | ยางลบ | 8.00 | 50 | เครื่องเขียน |
| 4 | สมุด | 35.00 | 40 | เครื่องเขียน |
| 5 | สินค้าใหม่ | 99.00 | 5 | NULL |
PriceUpdates: (1, 19.00), (4, 39.00)
SELECT 12 แบบ เลือกดูทีละแบบได้
INSERT 7 แบบ
INSERT INTO Products (ProductName, Price, Stock, Category) VALUES (N'ปากกา', 15.00, 100, N'เครื่องเขียน'); -- 1 แถว VALUES (…), (…), (…); -- หลายแถว (ProductName, Price) VALUES (N'แฟ้ม', 25.00); -- บางคอลัมน์ VALUES (N'ไม้บรรทัด', 12.00, DEFAULT); -- ใช้ค่า default VALUES (N'สินค้าใหม่', 99.00, 5, NULL); -- NULL ไม่ใส่ '' VALUES (N'ชุดปากกา', 15.00 * 3, 20); -- คำนวณ → 45.00 INSERT INTO BudgetProducts (ProductName, Price) SELECT ProductName, Price FROM Products WHERE Price < 20;
เช็ก 4 ข้อ: (1) ลำดับและจำนวนค่าต้องตรงกับคอลัมน์ (2) type, NOT NULL และ UNIQUE (3) รันซ้ำจะได้แถวซ้ำ (4) SELECT ตรวจหลังเพิ่ม
ถ้าไม่ระบุ Stock จะได้ 0, Category ได้ NULL, CreatedAt ได้เวลาปัจจุบัน และ ID รันเลขเอง
UPDATE / DELETE ⚠
SELECT … WHERE x → UPDATE/DELETE … WHERE x → SELECT … WHERE x โดยใช้ WHERE เดียวกันทั้งสามคำสั่งUPDATE Products SET Price = 18.00 WHERE ProductID = 1; SET Stock = Stock - 3 WHERE ProductID = 1 AND Stock >= 3; SET Price = ROUND(Price * 0.90, 2) WHERE Category='เครื่องเขียน' AND Price >= 10; SET Price = CASE ProductID WHEN 1 THEN 18 WHEN 2 THEN 12 ELSE Price END UPDATE Products p JOIN PriceUpdates u ON p.ProductID=u.ProductID SET p.Price = u.NewPrice; DELETE FROM Products WHERE ProductID IN (2, 3); WHERE Price BETWEEN 8 AND 10; -- รวมค่าขอบ WHERE Price <= 15 ORDER BY ProductID LIMIT 1; DELETE u FROM PriceUpdates u JOIN Products p … WHERE p.Stock <= 40; DELETE FROM PriceUpdates WHERE NOT EXISTS (SELECT 1 FROM Products WHERE Products.ProductID = PriceUpdates.ProductID);
DELETE ลบทั้งแถวแต่ตารางยังอยู่ ส่วน DELETE u ลบเฉพาะตาราง u ถ้าค่าใหม่เท่ากับค่าเดิม จำนวนแถวที่เปลี่ยนจะเป็น 0
แบบฝึก Lab 11 (coffee_shop_lab) และ Lab 12
Lab 11
สร้าง DB coffee_shop_lab ใช้ collation utf8mb4_unicode_ci
- categories(category_id PK AI, category_name UNIQUE)
- products(product_id PK, product_name, price ≥ 0, category_id FK, is_available)
- members(member_id PK, first_name, last_name, phone UNIQUE, registered_at)
- sales(sale_id PK, member_id FK ที่เป็น NULL ได้, sale_datetime, total_amount)
- เพิ่ม
descriptionแบบ NULL ได้, เปลี่ยนชื่อ phone เป็น phone_number และลบ 1 คอลัมน์ที่ไม่ใช่ PK
Lab 12
- เพิ่ม กาว 18.00/25 และเพิ่ม กรรไกร 45 กับ เทปใส 20 ในคำสั่งเดียว (Stock ใช้ default)
- สมุดรหัส 4 เปลี่ยนชื่อเป็น "สมุดเส้นบรรทัด" ราคา 40, ดินสอเพิ่มสต็อก 20 และแก้หมวดที่เป็น NULL เป็น "อื่น ๆ"
- ราคา ≤ 15 เรียงน้อยไปมาก, นับสินค้าที่สต็อก ≥ 50 และใช้ LEFT JOIN หาสินค้าที่ไม่มีราคาใหม่
- ลบราคา > 30, ลบรหัส 1 กับ 3 และลบสินค้าสต็อก ≥ 50 แค่ 1 แถวโดยเลือกรหัสมากสุดก่อน
ร้านโบนัสสุกี้: จาก TOR สู่ ER
คิดแบบ System Analyst
ปัญหา 3 ข้อในหน้าร้าน
- ทอนเงินผิด ยอด 317.79 รับเงิน 500 ระบบทอน 182.25 ทั้งที่ต้องเป็น 182.21 จึงต้องใช้ Decimal ทศนิยม 2 ตำแหน่ง
- ใช้โปรผิดเงื่อนไข ลูกค้ามา 2 คนแต่กดโปร "มา 4 จ่าย 3" ได้ จึงต้องบังคับใส่ headcount แล้ว block โปรที่ไม่เข้าเงื่อนไข
- เปิดโต๊ะซ้อน โต๊ะที่กำลังใช้อยู่ต้องถูก hard lock สถานะมี 3 สี: เขียว = ว่าง, แดง = กำลังทาน, เหลือง = รอเช็คบิล
โมดูลใน TOR
โต๊ะ · Billing (ซุปพิเศษ +49/หม้อ, เงินสด และ Dynamic QR มาตรฐาน EMVCo) · Promotion engine (กฎ stacking) · CRM (บังคับแค่ชื่อกับเบอร์ 10 หลักที่ห้ามซ้ำ แต้มคิดหลังหักส่วนลดเมื่อปิดบิล) · Inventory (BOM ตัดสต็อกตามสูตร, COGS แบบ FIFO หรือ Moving Average, ของเสีย, re-order point) · HR/Payroll (Full-time หรือ Part-time, OT 1.5 เท่า และ 2 เท่าในวันหยุด, ต้อง approve ก่อนจ่าย) · Expense · P&L
Revenue − COGS = Gross Profit Gross Profit − OPEX = Net Profit
NFR: ACID, audit log (void bill, เปิดลิ้นชักโดยไม่มีการขาย), offline + sync, RBAC 3 ระดับ (ผู้จัดการ ห้ามเห็นเงินเดือน คนอื่น), PDPA และ password hashing
ER สุกี้โบนัส (Lab drawio)
| Entity | Attributes |
|---|---|
| Member | Member_ID, Name, PhoneNo, Address |
| Bill | Order_ID, Payment amount |
| Product | Table_ID* , Name, amount, {Product_type} |
| Dining_Table | Table_ID, TableName, Zone, Size |
| Employee | Emp_ID, Name, Address, PhoneNo, EmpType |
| vendor | Vendor_ID, Name, Address, phoneNo |
| Promotion | PromotionID, Pro_Name, Detail, start, End |
| Work_Time (weak) | Date, Entry_time, End_time, hours (derived) |
| Relationship | Card. | Attribute |
|---|---|---|
| Member payment Bill | 1 : M | Date |
| Member use Dining_Table | M : M | DataTime, {status_table} |
| Dining_Table of Bill | 1 : M | amount_member (headcount), {soup_type}, {package}, Start/End |
| Bill use Product | M : 1 | DateTime, unit |
| Bill use Promotion | M : M | DateTime |
| Import (ternary) | Employee 1, Product M, vendor M | number, DateTime |
| Employee in Work_Time | 1 : M (identifying) | – |
* ในรูปขีดเส้นใต้ PK ของ Product เป็น Table_ID ซึ่งน่าจะตั้งชื่อผิด และควรเป็น Product_ID