0Pricing
R Academy · درس

الاستعلامات ذات المعاملات والمعاملات الذرّية

مرّر المعاملات بأمان وأدر المعاملات متعددة الخطوات في قواعد بيانات R.

الاستعلامات ذات المعاملات والمعاملات الذرّية درس مجاني في R Academy على CoddyKit. هذا هو الدرس 4 من أصل 4. يمكنك قراءة الدرس كاملاً أدناه مجاناً — ثم تمرن عليه مباشرة في المتصفح باستخدام محرر أكواد مدمج ومدرس ذكاء اصطناعي متاح 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 وتعيد النتائج في صورة إطار بيانات. مرّر قائمة 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() مع إطار بيانات بدلًا من ذلك — فهو أسرع، وتتولى 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() لإدراج إطار بيانات كامل دفعة واحدة، أو ضع عمليات الإدراج الفردية داخل معاملة واحدة — إذ تعتمد قواعد البيانات عمليات الكتابة على القرص مرة واحدة في النهاية، مما يجعل الإدراج بالجملة داخل معاملة أسرع بكثير من عمليات الإدراج ذات الاعتماد التلقائي.

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)

الأسئلة الشائعة

هل درس «الاستعلامات ذات المعاملات والمعاملات الذرّية» مجاني؟

نعم — نص درس «الاستعلامات ذات المعاملات والمعاملات الذرّية» كامل متاح مجاناً هنا على الويب. لتمرينه بشكل تفاعلي (محرر أكواد مدمج ومدرس ذكاء اصطناعي متاح 24/7) وفتح باقي دورة R Academy، انتقل إلى CoddyKit PRO. تتضمن دورة R Academy 4 دروس في المجموع.

ماذا ستتعلم في «الاستعلامات ذات المعاملات والمعاملات الذرّية»؟

مرّر المعاملات بأمان وأدر المعاملات متعددة الخطوات في قواعد بيانات R. تتمرن على R Academy مع أكواد عملية تشغلها مباشرة في المتصفح، ومدرس ذكاء اصطناعي متاح 24/7 يجيب على أسئلتك أثناء عملك.

هل أحتاج إلى خبرة سابقة لأبدأ R Academy؟

لا تُشترط خبرة سابقة. R Academy على CoddyKit منظم للمبتدئين حتى المتقدمين، لذا يمكنك البدء من هنا أو من البداية والتقدم بسرعتك الخاصة. هذا هو الدرس 4 من أصل 4.

كم من الوقت يستغرق درس «الاستعلامات ذات المعاملات والمعاملات الذرّية»؟

معظم دروس CoddyKit تستغرق حوالي 5–10 دقائق. كل منها موجز وتفاعلي، لذا تحرز تقدماً مستمراً وتستأنف من حيث توقفت عبر الويب والتطبيق.

هل يمكنني كتابة وتشغيل أكواد في درس R Academy هذا؟

نعم. كل درس في R Academy يتضمن محرر أكواد مدمج، لذا تكتب وتشغل أكواداً حقيقية مباشرة في متصفحك وتحصل على تعليقات فورية من الذكاء الاصطناعي — بدون إعداد محلي.

جميع الدروس في هذه الدورة

  1. أساسيات DBI وRSQLite
  2. الاتصال بـ PostgreSQL وMySQL
  3. dbplyr: ‏SQL عبر صيغة dplyr
  4. الاستعلامات ذات المعاملات والمعاملات الذرّية
← العودة إلى R Academy