วิธีการให้นูบสั่งคิวรี่จากฐานข้อมูลอะไรก็ได้แบบเทพๆ

bank-note-209104_960_720

อย่างที่กล่าวไปในเอ็นทรี่เดิม เมื่ออัญเชิญเทพ “sqlite3″ มาประทับร่าง “database/sql” และเช็คสายว่าสามารถต่อติดด้วยการ “ปิง” แล้ว เราจะเริ่มตั้งคำถามกับเทพกันละ คำถามที่ถามบ่อยๆ กับเหล่าทวยเทพมีอะไรบ้างน้า…แล้วเราสามารถสั่งให้เทพทำอะไรได้อีก ยกเว้นใช้เทพไปซื้อกาแฟ อันนี้คนเดียวที่สั่งได้คือเมียเทพนะจ๊ะ (เนื่องจากบทความของผมจะเน้นนูบไม่เน้นซีเรียส ถ้าคำศัพท์อันไหนยากๆ ผมจะพยายามทำให้เป็นตัวแปรให้นูบเข้าใจง่ายก็แล้วกัน อย่างเช่น “เทพ” ในที่นี้ก็คือ *sql.DB ถ้ายังไม่เก็ตภาษาวิบัติเหล่านี้ก็ให้คิดเสียว่าอ่านตำราตลกละกันนะ)

หลังจากเราได้เรียก sql.Open() ไปในตอนที่แล้วผลลัพธ์ที่ได้กลับมานิยมใช้ตัวแปร db เข้ามารับค่า โดยมี type เป็น *sql.DB เปรียบสเมือน db ตอนนี้ก็คือ “ร่างอวตารของเทพ” ที่สามารถเรียก db ใช้ท่าเทพๆ (Method) ซึ่งก็คือการ Excecuting queries ทั้งหลายได้ดังต่อไปนี้

Executing queries

(คำว่าคิวรี่อันนี้ฝรั่งบางสายพันธุ์อ่านค<ตี๊ด>ยหรี่ชัดเจนดุดันจนสาวสะดุ้ง ดังนั้นเพื่อความตื่นเต้ลเราจะออกเสียง query ให้ได้แอคเซนส์ว่า “ควายหรี่” และ Data Row หรือแถวที่ส่งกลับมาว่า “สองแถว” โอเค๊!)

Exec ใช้เพื่อสั่งการ

มันเป็นการสั่งเทพทำอะไรก็ได้ที่เทพจะไม่ส่งสองแถวกลับมา เช่น INSERT, UPDATE, DELETE โดยจะส่งกลับมาแค่ ผลลัพธ์(result) แสดงจำนวนสองแถวที่นับได้บ้าง ไม่ได้บ้าง พร้อม “ยัยเอ๋อ” err เช่นอีกเคย ดังตัวอย่าง

result, err := db.Exec(
    "INSERT INTO users (name, age) VALUES ($1, $2)",
    "gopher",
    27,
)

ตัว result นั้นเราสามารถขอดู ID ที่ INSERT ล่าสุด และจำนวนสองแถวที่เกิดผลกระทบ ค่าเหล่านี้จะมีให้ใช้จริงหรือไม่ อันนี้ก็ขึ้นอยู่กับตัว Database Driver หรือเทพแต่ละท่านจะขยันเขียน Method ไว้ให้เราใช้ไหม และเวลาใช้ก็ควรต่อท้ายแถวโดยเช็คว่ายัยเอ๋อยังเป็น nil อยู่ไหมเช่นเคย

Query ควายหรี่ใช้เพื่อดูดข้อมูล

ควายหรี่เป็นการถามเทพนั่นเอง ส่วนใหญ่มักจะได้คำตอบหลายๆตัว ในครั้งเดียวกลับมา นิยมใช้คำว่า rows ในการรับค่าให้สังเกตุว่าเป็นพหูพจน์ ว่าแล้วเราก็ขอถามเทพว่าหวยรอบนี้รางวัลเลขท้ายสามตัวมีเลขอะไรบ้าง

rows, err := db.Query("SELECT number FROM lotteries WHERE price = $1", lastThreeDigitPrice)
if err != nil {
    log.Fatal(err)
}
for rows.Next() {
    var number int64
    if err := rows.Scan(&number); err != nil {
        log.Fatal(err)
    }
    fmt.Printf("Last 3 digit price = %d\n", number)
}
if err := rows.Err(); err != nil {
    log.Fatal(err)
}

สังเกตุว่าในวงเล็บเราก็ใส่คำสั่ง SQL แบบมีพารามิเตอร์เข้าไปตาม syntax ของ DB เทพต้นทางแต่ละตัวนั่นเอง (sqlite จะใช้พารามิเตอร์ได้ทั้ง $1 และ ?) และเมื่อคาดหวังว่าจะมีสองแถวกลับมาหลายคัน เราก็เลยทำท่าวนลูป for ซะโดยท่ามาตรฐานในที่นี้คือ for rows.Next() {} ซึ่งเป็นการทดสอบว่าค่า rows.Next() เป็น true ก็จะวนลูปจนกว่าจะเป็น False ซึ่งก็คือสองแถวหมดแล้วกลับบ้านไม่ได้นั่นเอง

ใน Go จะใช้ for เพื่อวนลูปแค่คำเดียว แต่มีหลายท่า เหมาะมากสำหรับคนขี้เกียจเช่นคุณ ไม่ต้อง งง กับ Do…While อะไรอีก เจ๋งมะ!

ส่วนด้านในเราก็จะทำการเช็ค if ซะหน่อยตรงนี้ส่วนแรกคือ err := rows.Scan(&number); เป็นการทำ Expression ก่อนตรวจสอบเงื่อนไข If ซึ่งนิยมนำมากำหนดค่าตั้งต้นก่อนเช็คเงื่อนไข ในที่นี้ก็คือเอายัยเอ๋อ (err) มาตรวจหลังจากเราสั่ง rows.Scan() เก็บค่าไปแล้ว “&” หมายถึงตัวแปรนี้เป็น Pointer สามารถเขียนค่ากลับได้ ใส่ไว้ให้ท่า Scan เขียนค่าที่อ่านได้กลับเข้าไปที่ number)


QueryRow ควายหรี่เรียก(สอง)แถวใช้ตอนที่ต้องการผลลัพธ์ที่ยังไงก็มีตัวเดียวอันเดียวแน่นอน

เช่นให้คุณอยากขอท่านใบ้หวยรางวัลที่หนึ่งเป็นต้น

var number int64
row := db.QueryRow("SELECT number FROM lotteries WHERE price = $1", firstPrice)
err := row.Scan(&number)

จุดสำคัญของ QueryRow คือเราทำตัวแปร row ไว้รอรับรอบเดียวไม่มีวนลูป และก็ใช้ท่า row.Scan() เช่นเดียวกัน

Prepared statements

สำหรับเตรียม SQL statement ที่ต้องใช้ซ้ำบ่อยๆ คล้ายกับคำสั่งควายหรี่ (Query) แต่ทำให้เราไม่ต้องสั่ง ควายหรี่ประโยคเดิมซ้ำๆ เช่น

age := 27
stmt, err := db.Prepare("SELECT name FROM users WHERE age = $1")
if err != nil {
    log.Fatal(err)
}
rows, err := stmt.Query(age)
// process rows

จากตัวอย่างนี้เราสามารถใช้ stmt รับค่าจาก db.Prepare แล้วเอา stmt มาสั่ง stmt.Query() ส่งตัวแปร age ไปแทน $1 ใน statement ได้เลย

Transactions

ทรานแซคชั่นนั้นเริ่มต้นด้วย Begin เสมอ

tx, err := db.Begin()
if err != nil {
    log.Fatal(err)
}

ด้วยการใช้เมธอด Exec, Query, QueryRow และ Prepare เพียงแค่ 4 ตัวนี้ก็เพียงพอที่จะใช้งานในทรานแซคชั่นได้หมดแล้ว และตัว transaction จะต้องปิดท้ายด้วยการเรียก Commit หรือ Rollback เสมอ

แล้วทรานแซคชั่นมีไว้ทำอะไร ตอบคือเพื่อยืนยันการทำรายการแต่ละรายการว่าถูกบันทึกสมบูรณ์ทั้งกระบวนการตามเงื่อนไขหรือไม่ ถ้าไม่ก็สั่ง Rollback ถ้าใช่ก็ Commit เพื่อบันทึกรายการ

writing check

อื่มนึกภาพนู๊บบางคนงงงวยลอยมาเลย เอางี้ ถ้าเมียใช้คุณไปโอนเงินจากสมุดบัญชีของ “คุณ” ไปเข้าบัญชี “เมีย” สิ่งที่เกิดขึ้นจริงๆแบบสโลวโมชั่นคือ คุณกำลังยืนอยู่หน้าเทลเลอร์สาวสวย “กิ๊ก” ของคุณ คุณสั่งถอนเงินจากบัญชีของคุณก่อน ระหว่างนั้นระบบจะล๊อคไม่ให้ใครแก้ไขบัญชีของคุณและเมียนอกเสียจากจะมีเงินเข้าบัญชี “เมีย” ระบบจึงจะ Commit ข้อมูล บันทึกรายการ และปลดล๊อคห้ามแก้ให้ แต่ถ้าปรากฏว่าเงินดันไปเข้าบัญชี “กิ๊ก” ระบบก็จะ Rollback รายการทิ้ง ภาพที่คุณเห็นจะเหมือนย้อนฟิล์มกลับมาอยู่ที่เดิมหน้าเคาท์เตอร์ของเทลเลอร์สาวสวยกิ๊กของคุณอีกครั้ง เหมือนกับคุณไม่เคยถอนเงินมาก่อนเลย… แม้คุณจะลองทำผิดพลาดซ้ำซากอย่างไรก็ไม่เป็นผล แหม่ยังกะแสตนย้อนเวลายังไงอย่างนั้น ดังนั้นให้จำง่ายๆว่า Transaction ก็คือพลังเทพเมียที่สั่งย้อนเวลาได้ถ้าผิดเงื่อนไข ควรเขียนครอบชุดคำสั่งสำคัญที่อาจมีช่องโหว่ให้จิ๊กเงินเมียได้เอาไว้ซะ

แล้วจะใช้ยังไงละ …ตอนนี้ไม่รู้ยังไม่เคยใช้เหมือนกัล ไปลองเอาเองก่อนนะเหอๆๆ โอกาสหน้าจะมาคุยเรื่องการรับมือกับเมียน้อย เอ๊ยค่า Null หรือไม่ก็ Connection Pool แล้วแต่จะสะดวกน๊ะจ๊ะ

อ้างอิงจาก https://github.com/golang/go/wiki/SQLInterface