Search code examples
sqlrsqliteshinyrsqlite

Create dynamic SQL query depending on user input in R Shiny App


I have an Shiny App where User can filter a SQL Database of Movies. So far, you can only filter by different countries.

con <- dbConnect(RSQLite::SQLite(), 'Movies.db')
movies_data <- dbReadTable(con, 'Movies')

ui <- fluidPage(
  fluidRow(
    selectInput(
      inputId = "country",
      label = "Country:",
      choices = movies_data$journal,
      multi=T
    ),
    br(),
    fluidRow(width="100%",
           dataTableOutput("table")
    )
  )
)

server <- function(input, output, session) {
        
  output$table <- renderDataTable({
    dbGetQuery(
      conn = con,
      statement = 'SELECT * FROM movies WHERE country IN ( ? )',
      params = list(input$country))
  })
}
shinyApp(ui = ui, server = server)

Now i want to give the user more Filters, for example Actor or Genre. All Filters are Multiselect and optional. How can i create the Statement dynamic? Would i use some switch statement for every possible combination (i.e. no Filter on Country but only Action Movies)? This seems ab it bit exhausting to me.


Solution

  • First off, you say the filter is optional but I see no way to disable it in your code. I'm assuming that deselecting all options is your way of disabling the filter, or at least that it's intended to work that way. If all options are selected for any filter, then the current approach should work fine, and will just show all films.

    You can probably just construct the overall query piece by piece, and then paste it all together at the end.

    Base query: 'SELECT * FROM movies'

    Country filter: 'country in ' input country

    Actor filter: 'actor in' input actor

    Genre filter: 'genre in' input genre

    Then you put it all together with paste.

    To summarize: Base query. Then, if any of the filters are active, add a WHERE. Join all filters together, separating by AND. Pass the final query in as a direct string.

    You can even put the filters into a list for easier parsing.

    # Here, filterList is a list containing input$country, input$actor, input$genre
    # and filterNames contains the corresponding names in the database
    # e.g. filterList <- list("c1", list("a1", "a2"), "g1")
    # filterNames <- filterNames <- list("c", "a", "g")
    
    baseQuery <- "SELECT * FROM movies"
    
    # If any of the filters have greater than 0 value, this knows to do the filters
    filterCheck <- any(sapply(filterList, length)>0)
    
    # NOTE: If you have a different selection available for None
    # just modify the sapply function accordingly
    
    if(filterCheck)
    {
      baseQuery <- paste(baseQuery, "WHERE")
    
      # This collapses multiselects for a filter into a single string with a comma separator
      filterList <- sapply(filterList, paste, collapse = ", ")
    
      # Now you construct the filters
      filterList <- sapply(1:length(filterList), function(x)
        paste0(filterNames[x], " IN (", filterList[x], ")"))
    
      # Paste the filters together
      filterList <- paste(filterList, collapse = " and ")
    
      baseQuery <- paste(baseQuery, filterList)
    }
    
    # Final output using the sample input above:
    # "SELECT * FROM movies WHERE c IN (c1) and a IN (a1, a2) and g IN (g1)"
    

    Now use baseQuery as the direct query statement