R Academy · บทเรียน

คำสั่งสืบค้นแบบกำหนดพารามิเตอร์และธุรกรรม

ส่งพารามิเตอร์อย่างปลอดภัยและจัดการธุรกรรมฐานข้อมูลหลายขั้นตอนใน R

บทเรียน 4 จาก 413 ขั้นตอน

คำสั่งสืบค้นแบบกำหนดพารามิเตอร์และธุรกรรม เป็นบทเรียน R Academy ฟรีบน CoddyKit นี่คือบทเรียนที่ 4 จากทั้งหมด 4 บทเรียน คุณสามารถอ่านบทเรียนทั้งหมดด้านล่างฟรี — จากนั้นลองปฏิบัติด้วยตัวคุณเองในเบราว์เซอร์พร้อมตัวแก้ไขโค้ดในตัวและติวเตอร์ AI ตลอด 24/7 บทเรียนนี้เป็นส่วนหนึ่งของเส้นทางการเรียน R Academy และความก้าวหน้าของคุณจะซิงค์ข้ามเว็บและแอป CoddyKit คอร์ส R Academy มีบทเรียนทั้งหมด 4 บทเรียน

เหตุใดจึงควรใช้คำสั่งแบบมีพารามิเตอร์

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

library(DBI)

# DANGEROUS: string concatenation (SQL injection risk!)
user_input <- "'; DROP TABLE users; --"
# bad_sql <- paste0("SELECT * FROM users WHERE name = '", user_input, "'")
# dbGetQuery(con, bad_sql)   <- NEVER do this

# SAFE: parameterized query
# dbGetQuery(con, 'SELECT * FROM users WHERE name = $1',
#            params = list(user_input))

cat('Parameterized queries prevent SQL injection')

dbGetQuery() พร้อมพารามิเตอร์

dbGetQuery() ประมวลผลคำสั่ง SELECT และส่งผลลัพธ์กลับมาเป็น data frame ส่งรายการ params เพื่อผูกค่ากับตัวยึดตำแหน่ง รูปแบบของตัวยึดตำแหน่งจะแตกต่างกันไปตามไดรเวอร์: $1 สำหรับ PostgreSQL และ ? สำหรับ SQLite/MySQL

library(DBI)

con <- dbConnect(RSQLite::SQLite(), ':memory:')
dbWriteTable(con, 'employees', data.frame(
  name       = c('Alice', 'Bob', 'Carol'),
  department = c('Engineering', 'Marketing', 'Engineering'),
  salary     = c(90000, 75000, 95000)
))

# Parameterized SELECT — SQLite uses ?
result <- dbGetQuery(
  con,
  'SELECT name, salary FROM employees WHERE department = ?',
  params = list('Engineering')
)
print(result)

dbDisconnect(con)

dbExecute() — INSERT, UPDATE, DELETE

dbExecute() เรียกใช้คำสั่ง SQL ที่แก้ไขข้อมูล (INSERT, UPDATE, DELETE) และส่งคืนจำนวนแถวที่ได้รับผลกระทบ ใช้ params เพื่อผูกค่าอย่างปลอดภัย ฟังก์ชันนี้เหมาะสมสำหรับการดำเนินการเขียนข้อมูล

library(DBI)

con <- dbConnect(RSQLite::SQLite(), ':memory:')
dbWriteTable(con, 'users', data.frame(
  id     = 1:3,
  name   = c('Alice', 'Bob', 'Carol'),
  active = c(TRUE, FALSE, TRUE)
))

# Safe UPDATE with parameters
rows_affected <- dbExecute(
  con,
  'UPDATE users SET active = ? WHERE id = ?',
  params = list(TRUE, 2)
)
cat('Rows updated:', rows_affected, '\n')

# Verify
result <- dbGetQuery(con, 'SELECT * FROM users WHERE id = 2')
cat('Bob active:', result$active)

dbDisconnect(con)

พารามิเตอร์หลายรายการในคำสั่งเดียว

คุณสามารถผูกพารามิเตอร์หลายรายการได้โดยใส่ทั้งหมดไว้ในรายการ params พารามิเตอร์เหล่านี้จะถูกผูกตามลำดับกับตัวยึดตำแหน่ง (? หรือ $1, $2, ...) ในสตริง SQL จำนวนสมาชิกในรายการต้องตรงกับจำนวนตัวยึดตำแหน่งเสมอ

library(DBI)

con <- dbConnect(RSQLite::SQLite(), ':memory:')
dbWriteTable(con, 'products', data.frame(
  id    = 1:4,
  name  = c('Apple', 'Banana', 'Cherry', 'Date'),
  price = c(1.5, 0.8, 3.0, 5.0),
  stock = c(100, 200, 50, 30)
))

# Filter by two parameters
result <- dbGetQuery(
  con,
  'SELECT name, price FROM products WHERE price > ? AND stock > ?',
  params = list(1.0, 40)
)
print(result)

dbDisconnect(con)

INSERT แบบมีพารามิเตอร์

คำสั่ง INSERT แบบมีพารามิเตอร์ช่วยเพิ่มระเบียนใหม่ได้อย่างปลอดภัย ให้ผูกค่าของแต่ละคอลัมน์เป็นพารามิเตอร์ สำหรับการแทรกข้อมูลจำนวนมาก ให้ใช้ dbAppendTable() พร้อม data frame แทน เนื่องจากทำงานได้เร็วกว่า และ DBI จะจัดการการผูกค่าให้โดยอัตโนมัติ

library(DBI)

con <- dbConnect(RSQLite::SQLite(), ':memory:')
dbExecute(con, 'CREATE TABLE logs (level TEXT, message TEXT, ts TEXT)')

# Parameterized INSERT
dbExecute(
  con,
  'INSERT INTO logs (level, message, ts) VALUES (?, ?, ?)',
  params = list('INFO', 'User logged in', as.character(Sys.time()))
)

result <- dbGetQuery(con, 'SELECT * FROM logs')
print(result)

dbDisconnect(con)

dbBegin() และ dbCommit() — ธุรกรรม

ธุรกรรมจะรวมคำสั่ง SQL หลายรายการไว้เป็นหน่วยอะตอมเดียว กล่าวคือทุกคำสั่งต้องสำเร็จทั้งหมด หรือไม่สำเร็จเลย ใช้ dbBegin() เพื่อเริ่มต้น แล้วใช้ dbCommit() เพื่อยืนยันการเปลี่ยนแปลงทั้งหมดพร้อมกัน

library(DBI)

con <- dbConnect(RSQLite::SQLite(), ':memory:')
dbExecute(con, 'CREATE TABLE accounts (id INTEGER, balance REAL)')
dbExecute(con, "INSERT INTO accounts VALUES (1, 1000), (2, 500)")

# Transfer 200 from account 1 to account 2
dbBegin(con)
  dbExecute(con, 'UPDATE accounts SET balance = balance - ? WHERE id = ?',
            params = list(200, 1))
  dbExecute(con, 'UPDATE accounts SET balance = balance + ? WHERE id = ?',
            params = list(200, 2))
dbCommit(con)

result <- dbGetQuery(con, 'SELECT * FROM accounts')
print(result)
dbDisconnect(con)

dbRollback() — ยกเลิกธุรกรรม

หากคำสั่งใด ๆ ในธุรกรรมล้มเหลว ให้เรียกใช้ dbRollback() เพื่อยกเลิกการเปลี่ยนแปลงทั้งหมดที่เกิดขึ้นนับตั้งแต่ dbBegin() วิธีนี้ช่วยให้ฐานข้อมูลอยู่ในสถานะที่สอดคล้องกัน ควรใช้ dbBegin() คู่กับ dbCommit() หรือ dbRollback() เสมอ

library(DBI)

con <- dbConnect(RSQLite::SQLite(), ':memory:')
dbExecute(con, 'CREATE TABLE orders (id INTEGER, amount REAL)')
dbExecute(con, 'INSERT INTO orders VALUES (1, 500)')

# Simulate a failed transaction
dbBegin(con)
tryCatch({
  dbExecute(con, 'UPDATE orders SET amount = ? WHERE id = ?',
            params = list(600, 1))
  stop('Simulated error during processing')  # something goes wrong
  dbCommit(con)
}, error = function(e) {
  dbRollback(con)
  cat('Transaction rolled back:', e$message, '\n')
})

# Amount is still 500 (rollback worked)
result <- dbGetQuery(con, 'SELECT * FROM orders')
cat('Amount after rollback:', result$amount)
dbDisconnect(con)

dbWithTransaction() — รูปแบบที่ปลอดภัยกว่า

dbWithTransaction() จะห่อบล็อกโค้ดของคุณไว้ในธุรกรรมโดยอัตโนมัติ หากบล็อกทำงานสำเร็จ ฟังก์ชันจะยืนยันการเปลี่ยนแปลง และหากเกิดข้อผิดพลาดใด ๆ จะย้อนกลับการเปลี่ยนแปลง วิธีนี้สะอาดและมีโอกาสเกิดข้อผิดพลาดน้อยกว่าการเรียก dbBegin() / dbCommit() / dbRollback() ด้วยตนเอง

library(DBI)

con <- dbConnect(RSQLite::SQLite(), ':memory:')
dbExecute(con, 'CREATE TABLE ledger (account TEXT, amount REAL)')
dbExecute(con, "INSERT INTO ledger VALUES ('Alice', 1000), ('Bob', 500)")

# Automatic transaction management:
dbWithTransaction(con, {
  dbExecute(con, "UPDATE ledger SET amount = amount - 100 WHERE account = 'Alice'",
            params = list())
  dbExecute(con, "UPDATE ledger SET amount = amount + 100 WHERE account = 'Bob'",
            params = list())
})
# Committed automatically on success

print(dbGetQuery(con, 'SELECT * FROM ledger'))
dbDisconnect(con)

การจัดการข้อผิดพลาดภายในธุรกรรม

การใช้ tryCatch() ร่วมกับ dbWithTransaction() ช่วยให้รายงานข้อผิดพลาดได้อย่างเป็นระเบียบ เมื่อบล็อกภายในทำให้เกิดข้อผิดพลาด dbWithTransaction() จะย้อนกลับการเปลี่ยนแปลงโดยอัตโนมัติ และตัวจัดการข้อผิดพลาดของคุณสามารถบันทึกหรือส่งต่อปัญหานั้นได้

library(DBI)

con <- dbConnect(RSQLite::SQLite(), ':memory:')
dbExecute(con, 'CREATE TABLE inventory (item TEXT, qty INTEGER)')
dbExecute(con, "INSERT INTO inventory VALUES ('Widget', 100)")

safe_update <- function(con, item, qty_change) {
  tryCatch(
    dbWithTransaction(con, {
      dbExecute(con,
        'UPDATE inventory SET qty = qty + ? WHERE item = ?',
        params = list(qty_change, item))
      cat('Updated', item, 'by', qty_change, '\n')
    }),
    error = function(e) cat('Failed (rolled back):', e$message, '\n')
  )
}

safe_update(con, 'Widget', -30)
print(dbGetQuery(con, 'SELECT * FROM inventory'))
dbDisconnect(con)

การแทรกข้อมูลเป็นชุดเพื่อประสิทธิภาพ

การแทรกข้อมูลทีละแถวในลูปจำนวนมากทำงานช้า วิธีที่ดีกว่ามีสองแบบ ได้แก่ ใช้ dbAppendTable() เพื่อแทรก data frame ทั้งชุดในครั้งเดียว หรือห่อการแทรกทีละรายการไว้ในธุรกรรมเดียว ฐานข้อมูลจะยืนยันการเขียนลงดิสก์เพียงครั้งเดียวเมื่อสิ้นสุด ทำให้การแทรกข้อมูลจำนวนมากภายในธุรกรรมเร็วกว่า การแทรกแบบยืนยันอัตโนมัติมาก

library(DBI)

con <- dbConnect(RSQLite::SQLite(), ':memory:')
dbExecute(con, 'CREATE TABLE events (user_id INTEGER, action TEXT)')

# Fast: insert a whole data frame at once
new_events <- data.frame(
  user_id = c(101, 102, 103, 104),
  action  = c('login', 'view', 'purchase', 'logout')
)
dbAppendTable(con, 'events', new_events)

result <- dbGetQuery(con, 'SELECT COUNT(*) AS n FROM events')
cat('Rows inserted:', result$n)

dbDisconnect(con)

dbDisconnect() — ปิดการเชื่อมต่อเสมอ

การเชื่อมต่อฐานข้อมูลใช้ทรัพยากรบนเซิร์ฟเวอร์ ควรเรียกใช้ dbDisconnect(con) เสมอเมื่อใช้งานเสร็จแล้ว การใช้ on.exit(dbDisconnect(con)) ที่ต้นฟังก์ชันจะช่วยให้การเชื่อมต่อถูกปิดแม้จะเกิดข้อผิดพลาด

library(DBI)

# Pattern: use on.exit to guarantee disconnection
run_query <- function(sql) {
  con <- dbConnect(RSQLite::SQLite(), ':memory:')
  on.exit(dbDisconnect(con), add = TRUE)  # always runs

  dbExecute(con, 'CREATE TABLE t (x INTEGER)')
  dbExecute(con, 'INSERT INTO t VALUES (1), (2), (3)')
  result <- dbGetQuery(con, sql)
  result  # con is closed automatically after return
}

df <- run_query('SELECT * FROM t WHERE x > 1')
cat('Rows:', nrow(df))
# on.exit fires after return — connection cleanly closed

ตรวจสอบความเข้าใจอย่างรวดเร็ว

คุณต้องรวมคำสั่ง UPDATE ที่เกี่ยวข้องกันสองรายการ เพื่อให้ทั้งสองรายการสำเร็จ หรือไม่มีรายการใดเปลี่ยนแปลงฐานข้อมูลเลย วิธีใดเหมาะสมที่สุด

คำสั่งแบบมีพารามิเตอร์และธุรกรรม — ประเด็นสำคัญ

การดำเนินการกับฐานข้อมูลใน R ด้วย DBI อย่างปลอดภัยและเชื่อถือได้:

  • ห้ามนำข้อมูลจากผู้ใช้มาต่อรวมในสตริง SQL โดยตรง ให้ใช้ params เสมอ
  • dbGetQuery(con, sql, params = list(...)) — SELECT อย่างปลอดภัย
  • dbExecute(con, sql, params = list(...)) — INSERT/UPDATE/DELETE อย่างปลอดภัย
  • ตัวยึดตำแหน่ง: ? สำหรับ SQLite/MySQL และ $1 / $2 สำหรับ PostgreSQL
  • dbBegin() + dbCommit() + dbRollback() — ควบคุมธุรกรรมด้วยตนเอง
  • dbWithTransaction(con, { ... }) — ยืนยันหรือย้อนกลับโดยอัตโนมัติ (แนะนำ)
  • dbAppendTable(con, 'tbl', df) — แทรกข้อมูลจำนวนมากได้อย่างรวดเร็ว
  • on.exit(dbDisconnect(con)) — ปิดการเชื่อมต่อเสมอ
library(DBI)

con <- dbConnect(RSQLite::SQLite(), ':memory:')
on.exit(dbDisconnect(con), add = TRUE)

dbExecute(con, 'CREATE TABLE transfers (from_id INT, to_id INT, amount REAL)')

# Safe parameterized insert inside a transaction:
dbWithTransaction(con, {
  dbExecute(con,
    'INSERT INTO transfers (from_id, to_id, amount) VALUES (?, ?, ?)',
    params = list(1, 2, 250.00)
  )
})

result <- dbGetQuery(con, 'SELECT * FROM transfers')
cat('Transfer recorded:', result$amount)
เริ่มต้นได้ฟรี

เรียนรู้ R ด้วย AI tutor — ฟรี

เขียนและเรียกใช้โค้ดจริงในเบราว์เซอร์ของคุณ รับความช่วยเหลือทันทีจาก AI tutor 24/7 และเรียนรู้ต่อจากที่คุณหยุดบนเว็บหรือในแอป

คอร์ส
43
บทเรียน
159

คำถามที่พบบ่อย

บทเรียน “คำสั่งสืบค้นแบบกำหนดพารามิเตอร์และธุรกรรม” ฟรีหรือไม่

ใช่ — ข้อความเต็มของ “คำสั่งสืบค้นแบบกำหนดพารามิเตอร์และธุรกรรม” ฟรีให้อ่านที่นี่บนเว็บ เพื่อปฏิบัติแบบโต้ตอบ (ตัวแก้ไขโค้ดในตัวและติวเตอร์ AI ตลอด 24/7) และปลดล็อคส่วนที่เหลือของคอร์ส R Academy ให้อัปเกรดเป็น CoddyKit PRO คอร์ส R Academy มีบทเรียนทั้งหมด 4 บทเรียน

คุณจะเรียนรู้อะไรในบทเรียน “คำสั่งสืบค้นแบบกำหนดพารามิเตอร์และธุรกรรม”

ส่งพารามิเตอร์อย่างปลอดภัยและจัดการธุรกรรมฐานข้อมูลหลายขั้นตอนใน R คุณปฏิบัติ R Academy ด้วยโค้ดที่ใช้งานได้จริงที่คุณเรียกใช้โดยตรงในเบราว์เซอร์ และติวเตอร์ AI ตลอด 24/7 ตอบคำถามของคุณขณะที่คุณไปผ่านบทเรียน

คุณต้องมีประสบการณ์ก่อนที่จะเริ่มเรียน R Academy หรือไม่

ไม่จำเป็นต้องมีประสบการณ์มาก่อน R Academy บน CoddyKit ออกแบบมาสำหรับผู้เริ่มต้นไปจนถึงผู้เรียนขั้นสูง คุณสามารถเริ่มต้นที่นี่หรือเริ่มจากตัวแรกและเรียนด้วยความเร็วของคุณเอง นี่คือบทเรียนที่ 4 จากทั้งหมด 4 บทเรียน

บทเรียน “คำสั่งสืบค้นแบบกำหนดพารามิเตอร์และธุรกรรม” ใช้เวลานานแค่ไหน

บทเรียน CoddyKit ส่วนใหญ่ใช้เวลาประมาณ 5–10 นาที แต่ละบทเรียนจึงสั้นและเป็นแบบโต้ตอบ คุณสามารถก้าวหน้าอย่างต่อเนื่องและกลับมาเรียนต่อจากตรงที่เพิ่งหยุดบนเว็บและแอปได้เลย

ฉันเขียนและรันโค้ดในบทเรียน R Academy นี้ได้ไหม

ได้ บทเรียน R Academy ทุกบทมีตัวแก้ไขโค้ดในตัว คุณจึงเขียนและรันโค้ดจริงได้เลยในเบราว์เซอร์ และได้รับข้อเสนอแนะจาก AI ในทันที — ไม่ต้องติดตั้งในเครื่องของคุณ

บทเรียนทั้งหมดในหลักสูตรนี้

  1. พื้นฐาน DBI และ RSQLite
  2. การเชื่อมต่อ PostgreSQL และ MySQL
  3. dbplyr: SQL ผ่านไวยากรณ์ dplyr
  4. คำสั่งสืบค้นแบบกำหนดพารามิเตอร์และธุรกรรม
← กลับไปที่ R Academy