Analisis sering membutuhkan data yang tersimpan di basis data, bukan di berkas. DBI adalah antarmuka standar R untuk basis data, sedangkan dbplyr memperluasnya dengan menerjemahkan verba dplyr, yaitu fungsi transformasi data seperti filter() dan arrange(), menjadi SQL. Topik ini membangun basis data pertama yang berjalan penuh di dalam memori menggunakan SQLite, menulis tabel ke dalamnya, menjalankan SQL dengan dbGetQuery(), lalu mengulang query yang sama dengan verba dplyr. Kebiasaan menutup koneksi menutup pembahasan. Pemahaman yang dibentuk di sini berlaku untuk semua driver, bukan hanya SQLite.
Koneksi pertama dengan DBI#
DBI mendefinisikan antarmuka generik, yaitu dbConnect() untuk menyambung, dbWriteTable() untuk menulis, dan dbGetQuery() untuk menjalankan SQL. Yang menangani detail teknisnya adalah driver, yaitu paket yang mengimplementasikan DBI untuk satu jenis basis data. Driver RSQLite dipakai di sini karena tidak membutuhkan instalasi server, dan nilai :memory: pada dbConnect() membuat seluruh basis data hidup di memori selama koneksi terbuka.
library(tidyverse)
library(DBI)
library(RSQLite)
con <- dbConnect(RSQLite::SQLite(), ":memory:")
dbListTables(con)── Attaching core tidyverse packages ──────────────────────── tidyverse 2.0.0 ──
✔ dplyr 1.2.1 ✔ readr 2.2.0
✔ forcats 1.0.1 ✔ stringr 1.6.0
✔ ggplot2 4.0.3 ✔ tibble 3.3.1
✔ lubridate 1.9.5 ✔ tidyr 1.3.2
✔ purrr 1.2.2
── Conflicts ────────────────────────────────────────── tidyverse_conflicts() ──
✖ dplyr::filter() masks stats::filter()
✖ dplyr::lag() masks stats::lag()
ℹ Use the conflicted package (<http://conflicted.r-lib.org/>) to force all conflicts to become errors
character(0)Pemuatan tidyverse pada blok pertama menyediakan tibble dan verba dplyr yang dipakai di bagian akhir. Fungsi dbListTables() mengembalikan daftar tabel, dan daftar kosong character(0) menegaskan bahwa basis data memori baru saja lahir tanpa tabel apa pun. Segala yang dibuat setelah ini hilang ketika koneksi ditutup, dan sifat itulah yang membuat seluruh contoh mudah diulang dari awal. Bila seluruh tabel memang ingin dibaca sekaligus sebagai data.frame, DBI juga menyediakan dbReadTable() dengan argumen nama tabelnya.
Menulis tabel dan menjalankan SQL#
Tabel pertama ditulis dengan dbWriteTable(), yang menerima objek koneksi, nama tabel, dan tibble sumbernya. Fungsi ini mengembalikan TRUE tanpa menampilkannya, sehingga keberhasilannya diperiksa lewat dbListTables().
siswa <- tibble(
nim = c(1, 2, 3, 4, 5, 6),
nama = c("Ali", "Budi", "Citra", "Dewi", "Eka", "Fajar"),
kelas = c("A", "A", "B", "B", "A", "B"),
nilai = c(82, 75, 88, 79, 91, 68)
)
dbWriteTable(con, "siswa", siswa)
dbListTables(con)[1] "siswa"SQL dijalankan lewat dbGetQuery(), yang mengirim query ke basis data lalu mengembalikan hasilnya sebagai data.frame. Query pertama memakai SELECT untuk memilih kolom, WHERE untuk menyaring baris, dan ORDER BY untuk mengurutkan hasil dari nilai tertinggi. Seluruh pekerjaan penyaringan berlangsung di sisi basis data, dan R hanya menerima baris yang lolos.
dbGetQuery(con, "SELECT nama, nilai FROM siswa WHERE nilai >= 80 ORDER BY nilai DESC") nama nilai
1 Eka 91
2 Citra 88
3 Ali 82Agregasi juga berjalan di sisi basis data. Query kedua menghitung rata-rata dan banyaknya siswa per kelas dengan GROUP BY, dan alias AS memberi nama pada kolom hasilnya.
dbGetQuery(con, "SELECT kelas, AVG(nilai) AS rata_nilai, COUNT(*) AS n FROM siswa GROUP BY kelas") kelas rata_nilai n
1 A 82.66667 3
2 B 78.33333 3Verba dplyr di atas basis data#
Menulis SQL dalam string membuat kode sulit dites dan mudah salah ketik. Paket dbplyr menawarkan jalan lain, yaitu verba dplyr ditulis seperti biasa lalu diterjemahkan menjadi SQL oleh dbplyr. Fungsi tbl() menghasilkan tampilan malas (lazy) atas tabel basis data, yaitu objek yang hanya menyimpan rencana query dan belum menarik datanya ke R.
library(dbplyr)
siswa_db <- tbl(con, "siswa")
siswa_db
Attaching package: ‘dbplyr’
The following objects are masked from ‘package:dplyr’:
ident, sql, sql_escape_ident, sql_escape_string
# A query: ?? x 4
# Database: sqlite 3.53.3 [:memory:]
nim nama kelas nilai
<dbl> <chr> <chr> <dbl>
1 1 Ali A 82
2 2 Budi A 75
3 3 Citra B 88
4 4 Dewi B 79
5 5 Eka A 91
6 6 Fajar B 68Pesan Attaching package pada keluaran memperingatkan bahwa dbplyr menutupi beberapa objek dari dplyr. Peringatan ini wajar dan tidak mengganggu fungsi yang dipakai di sini. Tampilan berawalan # A query menunjukkan bahwa siswa_db hanyalah pintu menuju tabel, bukan salinan datanya, dan tanda ?? pada jumlah baris berarti banyaknya baris belum dihitung.
Verba dplyr kini bekerja di atas tampilan malas tersebut. Rantai filter(), arrange(), dan select() disusun persis seperti pada tibble biasa, tetapi sifat malasnya terlihat dari tanda ?? yang masih tersisa.
siswa_db |>
filter(nilai >= 80) |>
arrange(desc(nilai)) |>
select(nama, nilai)# A query: ?? x 2
# Database: sqlite 3.53.3 [:memory:]
# Ordered by: desc(nilai)
nama nilai
<chr> <dbl>
1 Eka 91
2 Citra 88
3 Ali 82Terjemahan SQL yang dihasilkan dbplyr dapat dilihat dengan show_query(). Fungsi ini berguna untuk memeriksa logika query dan sekaligus menjadi sarana belajar SQL bagi yang terbiasa dengan dplyr. Tidak setiap fungsi R memiliki padanan SQL, sehingga bila suatu langkah tidak dapat diterjemahkan, pesan error dbplyr akan menunjukkannya dan pekerjaan itu perlu dipindah ke sisi R setelah collect().
siswa_db |>
filter(nilai >= 80) |>
arrange(desc(nilai)) |>
select(nama, nilai) |>
show_query()<SQL>
SELECT `nama`, `nilai`
FROM `siswa`
WHERE (`nilai` >= 80.0)
ORDER BY `nilai` DESCData baru benar-benar ditarik ke R ketika collect() dipanggil. Hasilnya adalah tibble biasa yang dapat diolah tanpa koneksi sekalipun. Kebiasaan yang baik adalah menarik hanya hasil yang sudah disaring dan diringkas, bukan seluruh tabel, karena pengerjaan agregasi di sisi basis data jauh lebih hemat memori.
nilai_tinggi <- siswa_db |>
filter(nilai >= 80) |>
collect()
nilai_tinggi# A tibble: 3 × 4
nim nama kelas nilai
<dbl> <chr> <chr> <dbl>
1 1 Ali A 82
2 3 Citra B 88
3 5 Eka A 91Menutup koneksi#
Koneksi ditutup dengan dbDisconnect(), dan statusnya dapat dicek dengan dbIsValid(). Basis data :memory: hilang bersama koneksi itu, sedangkan basis data berkas tetap tersimpan apabila dbConnect() diberi jalur berkas, misalnya “sekolah.db”, alih-alih :memory:. Di dalam fungsi yang membuka koneksi sendiri, penutupan dapat dijamin dengan on.exit(dbDisconnect(con)) sehingga koneksi tetap tertutup meskipun terjadi error di tengah fungsi.
dbDisconnect(con)
dbIsValid(con)[1] FALSEPola kerja yang sama berlaku untuk basis data lainnya. Cukup ganti driver pada dbConnect(), misalnya RPostgres untuk PostgreSQL atau RMariaDB untuk MySQL dan MariaDB, sementara seluruh fungsi DBI dan verba dbplyr tetap dipakai tanpa perubahan. Kebiasaan menutup koneksi penting karena skrip panjang dan laporan yang dirender berulang kali dapat menghabiskan kuota koneksi pada server basis data sungguhan.
Ringkasan#
- dbConnect() dengan driver RSQLite dan
:memory:membuat basis data sementara yang seluruh alurnya dapat dijalankan ulang, dan dbWriteTable() mengisinya dari tibble. - dbGetQuery() menjalankan SQL secara langsung, sedangkan dbplyr menerjemahkan verba dplyr menjadi SQL yang dapat diperiksa lewat show_query() dan ditarik ke R dengan collect().
- Koneksi ditutup dengan dbDisconnect(), dan pola yang sama berlaku untuk PostgreSQL maupun MySQL dengan mengganti driver pada dbConnect().
Referensi#
- DBI: R Database Interface — r-dbi — Dokumentasi resmi antarmuka basis data DBI beserta daftar driver-nya.
- dbplyr: A ‘dplyr’ Back End for Databases — tidyverse — Dokumentasi resmi dbplyr tentang penerjemahan verba dplyr menjadi SQL.
- RSQLite: ‘SQLite’ Interface for R — r-dbi — Dokumentasi driver RSQLite yang dipakai pada seluruh contoh topik ini.