Objective-C Programming Chapter 17 (Part1)

Chapter 17

 

Database & Core Data

 

การจัดการข้อมูลด้วย Cocoa สามารถทำได้หลายวิธีด้วยกัน ในบทที่ผ่านมาก็ได้ใช้ทั้ง plist หรือเขียนไฟล์จัดการด้วยตัวเองกันไปแล้ว แต่เมื่อต้องจัดการกับข้อมูลที่มีปริมาณมากขึ้นหรือมีความซับซ้อน การจัดการข้อมูลด้วย plist อาจไม่ใช่ทางเลือกที่เหมาะสมมากนัก วิธีการที่ดีกว่านั้นคือการใช้ระบบฐานข้อมูล ซึ่งช่วยให้จัดการข้อมูลต่างๆได้อย่างมีประสิทธิภาพและง่ายกว่าเดิม ในบทนี้จะได้เรียนรู้การจัดการฐานด้วย SQLite ซึ่งเป็นระบบฐานข้อมูลเบื้องต้น นอกจากนี้ยังจะได้ศึกษาและทำความเข้าใจการจัดการข้อมูลในระดับสูงด้วยการใช้ Core Data Framework

SQLite

ระบบฐานข้อมูลเชิงสัมพันธ์หรือที่เรียกว่า Relational Database เป็นระบบการจัดเก็บข้อมูลที่อยู่ในรูปแบบของตาราง โดยแต่ละตารางจะแบ่งออกเป็นแถว ในแต่ละแถวก็ยังแบ่งย่อยออกเป็นคอลัมน์ตามแต่ผู้ใช้กำหนด การจัดการข้อมูลแบบตารางนี้มีความนิยมแพร่หลายมากที่สุดเพราะง่ายต่อการทำความเข้าใจ เช่น ระบบฐานข้อมูล MySQL , Oracle , Microsoft SQL เป็นต้น และ SQLite ก็เป็นหนึ่งในระบบฐานข้อมูลแบบ relational database เช่นกัน การใช้ระบบฐานข้อมูล SQLite มีข้อดีหลายอย่าง เช่นทำงานเร็ว ใช้หน่วยความจำน้อย และข้อดีที่เห็นได้ชัดอีกอย่างหนึ่งคือ SQLite เป็นฐานข้อมูลเบื้องต้นที่มาพร้อมกับ iOS และ Mac OS X กล่าวคือมีไลบรารีและชุดคำสั่งภาษา C ให้เรียกใช้งาน โดยไม่ต้องติดตั้งไลบรารีจากภายนอกเพิ่มเติมแต่อย่างใด

Expense Project

โปรแกรมที่จะได้เขียนกันต่อไปนี้เป็นโปรแกรมบันทึกรายจ่ายประจำวันด้วย SQLite เนื่องจากหนังสือเล่มนี้เน้นการใช้ Framework และ Library เป็นหลัก จึงจะไม่อธิบายการทำงานของคำสั่งทาง SQL โดยละเอียด ผู้อ่านควรมีความรู้เกี่ยวกับระบบการจัดการฐานข้อมูลเบื้องต้นมาก่อน

Create Database
ก่อนที่จะเริ่มลงมือเขียนโปรแกรม เราจะสร้างไฟล์ฐานข้อมูลกันเสียก่อน และเพื่อให้ง่ายต่อการทำความเข้าใจ ฐานข้อมูลที่ใช้ในโปรแกรมจึงได้ออกแบบไม่ซับซ้อน โดยประกอบไปด้วยตาราง EXPENSE เพียงตารางเดียว ที่เก็บข้อมูลหมายเลขของรายการ (index) , กลุ่มของรายการ (group), จำนวนเงิน (amount), และสุดท้ายคือเวลา(date) ดังที่แสดงต่อไปนี้

expense_table

จากตารางได้กำหนดให้ หมายเลขของรายการ (index) เพิ่มค่าขึ้นโดยอัตโนมัติ, และบันทึกค่าของเวลาอัตโนมัติ เช่นเดียวกัน เมื่อเขียนออกมาเป็นคำสั่ง SQL ก็จะได้ดังนี้

CREATE TABLE “EXPENSE” (“no” INTEGER PRIMARY KEY  NOT NULL ,”group” CHAR DEFAULT (null) ,”amount” DOUBLE,”date” DATETIME DEFAULT (CURRENT_TIMESTAMP) )

การสร้างไฟล์ฐานข้อมูล SQLite รวมไปถึงการใช้ sql statement ต่างๆใน Mac สามารถทำผ่าน Terminal โดยที่ไม่ต้องลงโปรแกรมเพิ่มแต่อย่างใด วิธีการคือให้เปิดโปรแกรม Terminal และใช้คำสั่ง sqlite3 ตามด้วยชื่อของ database ที่จะสร้างเช่น sqlite3 expense.sqlite หลังจากนั้นจะเห็น sqlite command prompt ดังภาพ

expense_database

 

เมื่อเข้าสู่ sqlite command prompt แล้วก็สามารถสร้างตารางจาก sql statement ที่ได้ให้ไปได้ทันที เสร็จแล้วก็สั่ง .exit เพื่อออกจาก command prompt เพียงเท่านี้ก็จะได้ไฟล์ database ที่พร้อมสำหรับการใช้งาน อย่างไรก็ตามการสร้างไฟล์ด้วยวิธีแบบนี้อาจจะไม่สะดวกมากนักและเกิดความผิดพลาดได้ง่ายเพราะต้องพิมพ์ sql statement เองทั้งหมด จึงแนะนำให้ใช้โปรแกรมจัดการ SQLite ที่เป็นแบบ GUI ซึ่งหาได้ใน App Store หรือจะเป็นโปรแกรมแจกฟรี เช่น SQLite Manager ดังที่แสดงในรูป (add on ของ Firefox) ก็สามารถใช้งานได้เช่นเดียวกัน

sqlite_manager

 

Adding SQLite Library
หลังจากสร้างไฟล์ฐานข้อมูลเรียบร้อยแล้ว ต่อไปก็คือขั้นตอนการเพิ่มไลบรารี่ SQLite เพื่อใช้งานในโปรเจค เพราะเมื่อสร้างโปรเจคขึ้นมาใหม่จะยังไม่สามารถใช้ SQLite C API ได้ต้องเพิ่ม libsqlite3.dylib เข้ามายังโปรเจคเสียก่อน การเพิ่ม library นี้สามารถทำได้ที่ Build Phases > Link Binary With Libraryies เมื่อเพิ่มเรียบร้อยแล้วก็จะเห็นดังรูป
sql_lib

 

หลังจากที่ได้เพิ่มไลบรารีในโปเจคแล้ว ต่อไปจะสร้างคลาส DBController ขึ้นมาเพื่อใช้อำนวยความสะดวกในการจัดการฐานข้อมูล เช่น การอ่านไฟล์ เป็นต้น

DBController.h

ฟังชั่นต่างๆของ SQLite ประกาศใน sqlite3.h ดังนั้นก่อนจะใช้คำสั่งต่างของ SQLite ได้ต้องเพิ่ม sqlite3.h เข้ามาเสียก่อน จากนั้นคลาส DBController ก็ได้ประกาศ _database ที่เป็นพ้อยเตอร์ของ sqlite3 ซึ่งในเอกสารการ SQL API ได้บอกว่าตัวแปรชนิดนี้เป็น database connection ที่จะทำหน้าที่ติดต่อกับไฟล์ฐานข้อมูล ในส่วนของเมธอดที่ได้ประกาศประกอบไปด้วยเมธอดที่ใช้ เปิดปิดฐานข้อมูล , บันทึกรายจ่าย และขอดูรายการค่าใช้จ่ายต่างๆ

DBController.m

 

เมธอด initWithFile: มีหน้าที่หลักคือเรียกฟังชั่น sqlite3_open ซึ่งเป็นฟังชั่นที่ใช้ในการเปิดไฟล์ SQLite โดยพารามิเตอร์แรกคือที่ไฟล์ SQLite ที่ต้องการจะใช้งาน และต้องส่งเป็นสตริงของภาษา C ดังนั้นตัวแปร fileName จึงใช้เมธอด UTF8String ซึ่งจะได้สตริงของภาษา C เมื่อฟังชั่น sqlite3_open ทำงานสำเร็จก็จะส่งค่า YES กลับคืนมาพร้อมกับ database connection ที่จะส่งกลับออกมาผ่านทางพารามิเตอร์ที่สอง แต่ถ้าไม่สำเร็จจะได้ NO และไม่ได้รับ database connection ดังนั้นตัวแปร _database ก็จะเป็น nil ส่วนเมธอดที่สอง closeDB ทำหน้าที่ปิด database connection หลังจากการใช้งานเสร็จสิ้น

เมธอด insertExpenseToGroup:amount: เป็นเมธอดเพื่อใช้บันทึกรายจ่าย เริ่มต้นด้วยการประกาศ stmt เป็นตัวแปรเก็บ sqlite statement จากนั้นประกาศตัวแปร update เพื่อใช้เป็น query ของ statement นี้ ซึ่ง query ที่ได้เขียนไปใช้สำหรับการเพิ่มข้อมูลลงในฐานข้อมูล เมื่อพิจารณา query จะเห็นว่าจำนวนของค่าที่ query ต้องการคือ 2 อย่างด้วยกันคือ group และ amout ถ้าหากย้อนกลับไปดูโครงสร้างของตารางจะเห็นว่ามีค่าที่ต้องเก็บทั้งหมดด้วยกัน 4 ค่า แต่ที่ส่งไปเพียง 2 ค่า นั่นก็เพราะว่า index ได้ประกาศให้เพิ่มค่าโดยอัตโนมัติ และ date ก็กำหนดให้บันทึกเวลาอัตโนมัติเช่นกัน ดังนั้น query จึงไม่จำเป็นต้องใช้ค่าเหล่านี้
ในลำดับต่อมาเรียกใช้ฟังชั่น sqlite3_prepare_v2 เพื่อเตรียม sqlite statement ที่จะใช้ทำงาน ในฟังชั่นนี้ต้องการพารามิเตอร์ด้วยกันทั้งหมด 5 ค่า โดยค่าแรกเป็น database connection ที่ได้อธิบายไปแล้ว พารามิเตอร์ที่สองคือ query ที่ต้องส่งเป็นสตริงของภาษา C ดังนั้นตัวแปร update จึงใช้เมธอด UTF8String เพื่อให้ได้ C string ที่ฟังชั่นนำไปใช้งานได้ พารามิเตอร์ที่สามเป็นการกำหนดว่าสตริงที่เป็น query นั้นมีขนาดเท่าใด ในกรณีที่ส่งค่าเป็น -1 ฟังชั่นจะอ่านสตริงไปจนกว่าจะเจอ zero terminator หรือจุดสิ้นสุดของสตริง พารามิเตอร์ที่สี่คือ sqlite statement ที่ต้องการเตรียมใช้งาน และสุดท้ายจะเป็น pointer ไว้ชี้ตำแหน่งของ byte แรกหลังจากการอ่าน query เสร็จสิ้น ในกรณีนี้ไม่ได้ใช้จึงส่ง NULL เมื่อฟังชั่นทำงานเสร็จสิ้นก็จะได้ sql statement ที่พร้อมสำหรับการใช้งาน
และในขั้นตอนต่อไปก็คือการกำหนดค่าที่จะใช้ใน query ของ sql statement ซึ่งการกำหนดค่าให้กับ query นั้นมีฟังชั่นให้ใช้ตามชนิดของข้อมูล เช่น sqlite3_bind_text ใช้เพื่อกำหนดค่าตัวแปรที่เป็นชนิดสตริง โดยพารามิเตอร์แรกเป็น sql statement ส่วนพารามิเตอร์ตัวที่สองคือลำดับของตัวแปรที่ได้กำหนดใน query ส่วนพารามิเตอร์ที่สามคือค่าของสตริงที่ต้องการ ส่วนฟังชั่น sqlite3_bine_double ก็มีการทำงานคล้ายกันคือต้องบอกลำดับและค่าที่ต้องการ
เมื่อกำหนดตัวแปรให้กับ query เรียบร้อยแล้ว ขั้นตอนต่อมาคือ คือ sqlite3_step เพื่อสั่งให้ sqlite statement ที่ได้เตรียมไว้ให้ทำงาน เมื่อทำงานจบ ก็ต้องคืนหน่วยความจำด้วยการเรียก sqlite3_finalize และในกรณีที่เกิดข้อผิดพลาด เราสามารถใช้ฟังชั่น sqlite3_errmsg เพื่อขอข้อมูลความผิดพลาดจากการทำงานได้

 

เมธอดสุดท้ายคือ expenseFromGroup เมธอดนี้ใช้สำหรับขอดูรายการค่าใช้จ่ายตามกลุ่มที่กำหนด การทำงานในช่วงแรกของเมธอดก็ต้องเตรียม sql statement เช่นเดียวกันกับเมธอดที่ผ่านมา ส่วนที่สำคัญของเมธอดนี้คือ while loop เนื่องจากในการทำงานของเมธอด sqlite3_step จะได้ผลลัพธ์ทีละ 1 แถว ดังนั้นจึงใช้ลูป while สั่งให้ทำงานไปเรื่อยๆจนกว่าจะถึงผลลัพธ์แถวสุดท้าย และใน while loop นี้ก็จะดึงเอาค่าต่างๆตามลำดับของคอลัมน์ โดยเริ่มที่ 0 เป็นคอลัมน์แรกสุด ไปจนถึงคอลัมน์สุดท้าย ก็จะได้ค่าครบตามที่ต้องการ

main.m

 

โปรแกรมหลักได้เพิ่มรายจ่าย 4 รายการด้วยกัน หลังจากนั้นก็เรียกดูข้อมูลเฉพาะในกลุ่มของ Food ดังนั้นผลลัพธ์จึงเป็นดังนี้

Program 17.1 Output

1 Food 400 2013-11-02 23:37:42
4 Food 900 2013-11-02 23:37:42

เห็นได้ชัดว่าการใช้ระบบฐานข้อมูลมีความสะดวกมากกว่า plist หรือการจัดเก็บข้อมูลแบบไบนารี่ด้วยตัวเอง แต่อย่างไรก็ตาม plist ก็ยังมีเหมาะกับการใช้เก็บข้อมูลที่ไม่มากนัก เพราะไม่ต้องออกแบบฐานข้อมูลให้ยุ่งยาก ในหัวข้อต่อไปเราจะได้ศึกษาระบบการจัดเก็บข้อมูลของ Cocoa ขั้นสูง ที่เรียกว่า Core Data กันต่อไป

Leave a Reply