Interacting with Databases from R and Shiny

Barbara Borges Ribeiro

WSDS 2017

Show of hands!


Overview

PART I

PART II

Databases, and its many flavors

Databases, and its many flavors

Relational-ish databases

Databases, and its many flavors

NoSQL/object oriented databases

DBI (theory)

DBI (practice)

con <- DBI::dbConnect(RSQLite::SQLite(), ":memory:")

DBI::dbWriteTable(con, "iris", iris)

DBI::dbGetQuery(con, "SELECT count() FROM iris")
#>   count()
#> 1     150

DBI::dbDisconnect(con)

DBI (practice) – SQL injections edition!

sql <- "SELECT * FROM X WHERE name = ?name"

DBI::sqlInterpolate(DBI::ANSI(), sql, name = "Hadley")
#> <SQL> SELECT * FROM X WHERE name = 'Hadley'

# This is safe because the single quote has been double escaped
DBI::sqlInterpolate(DBI::ANSI(), sql, name = "H'); DROP TABLE--;")
#> <SQL> SELECT * FROM X WHERE name = 'H''); DROP TABLE--;'

DBI (practice) – SQL injections edition!

bobby-tables from xkcd:

odbc (theory)

Aside: what is a database driver?

In a computer system, an adaptor program is required for making a connection to another system of different type. Similar to connecting a printer to a computer by using a printer driver, a DBMS (database management system) needs a database driver that enables a database connection in other systems. (source)

odbc (practice)

con <-  DBI::dbConnect(odbc::odbc(), 
  Driver = "{postgresql}",
  Server = "postgresdemo.cfd8mtk93q6a.us-west-2.rds.amazonaws.com",
  Port = 5432, 
  Database = "postgresdemo",
  UID = "guest",
  PWD = "guest"
)

DBI::dbGetQuery(con, "SELECT * FROM city LIMIT 2;")
#>   id     name countrycode district population
#> 1  1    Kabul         AFG    Kabol    1780000
#> 2  2 Qandahar         AFG Qandahar     237500

DBI::dbDisconnect(con)

dplyr (theory)

dplyr (practice)

library(dplyr)

con <- DBI::dbConnect(RMySQL::MySQL(),
  dbname = "shinydemo",
  host = "shiny-demo.csa7qlmguqrf.us-east-1.rds.amazonaws.com",
  username = "guest", 
  password = "guest"
)

con %>% tbl("City") %>% head(2)
#> # Source:   lazy query [?? x 5]
#> # Database: mysql 5.5.5-10.0.17-MariaDB
#> #   [guest@shiny-demo (...) amazonaws.com:/shinydemo]
#>      ID     Name CountryCode District Population
#>   <dbl>    <chr>       <chr>    <chr>      <dbl>
#> 1     1    Kabul         AFG    Kabol    1780000
#> 2     2 Qandahar         AFG Qandahar     237500

DBI::dbDisconnect(con)

pool (theory)

Problem: how to interact with a database from Shiny?


pool (theory)

pool (practice)

library(dplyr)

pool <- pool::dbPool(RMySQL::MySQL(),
  dbname = "shinydemo",
  host = "shiny-demo.csa7qlmguqrf.us-east-1.rds.amazonaws.com",
  username = "guest", 
  password = "guest"
)

pool %>% tbl("City") %>% head(2)
#> # Source:   lazy query [?? x 5]
#> # Database: mysql 5.5.5-10.0.17-MariaDB
#> #   [guest@shiny-demo (...) amazonaws.com:/shinydemo]
#>      ID     Name CountryCode District Population
#>   <dbl>    <chr>       <chr>    <chr>      <dbl>
#> 1     1    Kabul         AFG    Kabol    1780000
#> 2     2 Qandahar         AFG Qandahar     237500

pool::poolClose(pool)

Resources



Getting help

Shiny app

Shiny app: skeleton

library(shiny)
library(shinydashboard)
library(dplyr)
library(pool)

pool <- dbPool(RSQLite::SQLite(), dbname = "db.sqlite")

tbls <- reactiveFileReader(500, NULL, "db.sqlite",
  function(x) db_list_tables(pool)
)

# ui
ui <- dashboardPage(...)

# server
server <- function(input, output, session) {...}

shinyApp(ui, server)

Shiny app: create table (adapted!)

# ui (snippet)
actionButton("create", "Create table"),
textInput("tableName", "Table name"),
numericInput("ncols", "Number of columns"),
uiOutput("cols")

# server (snippet)
output$cols <- renderUI({
  input$tableName
  cols <- vector("list", input$ncols)
  for (i in seq_len(input$ncols)) {
    textInput(paste0("colName", i), "Column name"),
    selectInput(paste0("colType", i), "Column type", 
      c(Integer = "INT", Character = "VARCHAR"))
  }
  cols
})
observeEvent(input$create, {
  # finalCols is a list. E.g: list(ID = "INT", item = "VARCHAR", count = "INT")
  db_create_table(pool, input$tableName, finalCols)
})

Shiny app: read table (adapted!)

# ui (snippet)
selectInput("tableName", "Table name", NULL),
checkboxGroupInput("select", "Choose columns to read"),
selectInput("filter", "Choose column to filter on", NULL),
checkboxGroupInput("vals", "Choose values to include"),
tableOutput("res")

# server (snippet)
observeEvent(tbls(), {
  updateSelectInput(session, "tableName", choices = tbls())
})
observe({
  cols <- db_query_fields(pool, input$tableName)
  updateCheckboxGroupInput(session, "select", choices = cols)
})
output$res <- renderTable({
  pool %>% 
    tbl(input$tableName) %>% 
    select(input$select) %>% 
    filter(input$filter %in% input$vals)
})

Shiny app: in action!