How to make Shiny reactivity work with SQL database? - sql

Alright, I modified the script following #Pork Chop advice:
server.R
library(shiny)
library(DT)
library(RMySQL)
con <- dbConnect(MySQL(), user="myuser", host="myhost", dbname="mydb")
shinyServer(function(input, output) {
sqlOutput <- reactive({
sqlInput <- paste0("select * from mydb.mytable",
" where value < ", input$value,
";")
dbGetQuery(con, sqlInput)
})
output$table <- DT::renderDataTable(sqlOutput(), server=TRUE, rownames=FALSE, filter="top", options=list(pageLength=10))
output$download <- downloadHandler("filtered.data.txt", content = function(file) {
rows <- input$table_rows_all
write.table(sqlOutput()[rows, ], file, sep="\t", quote=FALSE, col.names=TRUE, row.names=FALSE)
})
})
The DataTable now works!
However when I try to download the displayed data, I get a file with only column names and no data. According to the DT docs, input$table_rows_all should contain the row indices of the displayed table.
What's wrong?
I'm having troubles with Shiny reactivity and a MySQL database.
In short, I get an input value from the user, create an SQL query, capture the output and display it as a DataTable.
The output can be further filtered using the DataTable column filters and the user should be able to download the filtered dataset.
server.R
library(shiny)
library(DT)
library(RMySQL)
con <- dbConnect(MySQL(), user="myuser", host="myhost", dbname="mydb")
shinyServer(function(input, output) {
sqlInput <- reactive({
paste0("select * from mydb.mytable",
" where value < ", input$value,
";")
})
sqlOutput <- reactive({
dbGetQuery(con, sqlInput)
})
output$table <- DT::renderDataTable(sqlOutput, server=TRUE, rownames=FALSE, filter="top", options=list(pageLength=10))
output$download <- downloadHandler("filtered.data.txt", content = function(file) {
rows <- input$table_rows_all
write.table(sqlOutput[rows, ], file)
})
})
Instead of the DataTable, I get this error:
This works as expected if I embed sqlInput and sqlOutput within a reactive expression in DT::renderDataTable(), but then I'm not able to refer to sqlOutput from within downloadHandler() (object 'sqlOutput' not found). I thought this was the perfect use case for using reactive() but I can't get it to work.
What's the best way to make this work?
Any help is much appreciated, thanks!

1. sqlOutput is a function so change it to sqlOutput()
2. Try this, note this will export is as .csv hope its ok
output$download <- downloadHandler(filename = function() {paste(Sys.time(), ' Fltered_data.csv', sep='')}, content = function(file) {write.csv(sqlOutput()[input$table_rows_all, ], file, row.names = FALSE)})

Related

D3 Table Filter in R Shiny update SQL server

I am working on a project to update a SQL database with a Shiny app using D3 Table Filter.
I am able to query the server with different text inputs and the table will render with only those rows. The next step is to edit the table in the shiny app, and have that send a query back to the server to update it.
I have enabled editing in specific columns. How could I make an edit and have it send a query?
Thank you very much in advance.
Here is my code so far:
#install.packages("devtools")
#devtools::install_github("ThomasSiegmund/D3TableFilter")
library(shiny)
library(htmlwidgets)
library(D3TableFilter)
library(RSQLite)
library(RODBCext)
library(sqldf)
dbhandle = odbcDriverConnect(connection = "driver={SQL Server};server= ... ;database= ... ;trusted_connection=true")
fulldata = sqlExecute(dbhandle, "SELECT * FROM ...", fetch = TRUE, stringsAsFactors = FALSE)
ui <- fluidPage(
# Application title
titlePanel("Patient Search"),
sidebarLayout(
sidebarPanel(
textInput(inputId = "Id", label = "Search by Account Number, Date of Birth (YYYY-MM-DD) or Last Name"),
textInput(inputId = "NextAppt", label = "Search by Next Appointment (YYYY-MM-DD)"),
submitButton(text = "Go!")
),
mainPanel(
title = 'Patient Search with D3 Table Filter in Shiny',
fluidRow(
column(width = 12, d3tfOutput('data'))
)
)
)
)
# server.R
# --------------------------------------------------------
server <- shinyServer(function(input, output, session) {
#this reactive will return the row numbers that will need to be returned in our table.
#this could depend on any of our inputs: last name, DoB, account number, or next appointment
search.criteria <- reactive({
out <- c()
outAppt <- c()
if(grepl("\\d{4}\\-\\d{2}\\-\\d{2}", input$Id)==TRUE){
out <- which(fulldata$PatientDOB==input$Id)
print(out)
} else if(grepl("\\d{5}", input$Id)==TRUE){
out <- which(fulldata$AccountNo==input$Id)
} else{
out <- which(fulldata$PatientLastName==toupper(input$Id))
}
# filter for appointment
if(grepl("\\d{4}\\-\\d{2}\\-\\d{2}", input$NextAppt)==TRUE){
outAppt <- which(fulldata$NextAppt==input$NextAppt)
if(length(out)){
out <- intersect(out, outAppt)
}else{
out <- outAppt
}
}
out
})
#make the output table
output$data <- renderD3tf({
# Define table properties
tableProps <- list(
btn_reset = TRUE,
# alphabetic sorting for the row names column, numeric for all other columns
col_types = c("string", rep("number", ncol(fulldata)))
);
d3tf(fulldata[search.criteria(),],
tableProps = tableProps,
extensions = list(
list(name = "sort")
),
showRowNames = TRUE,
tableStyle = "table table-bordered",
#this optional argument enables editing on these specific columns
edit = c("col_49", "col_50", "col_51", "col_52", "col_53"));
})
#NEED TO ADD SOMETHING HERE TO SEND QUERY TO SERVER WHEN USER EDITS
})
runApp(list(ui=ui,server=server))
I used rhandsontable. It works better as you can convert the output using hot_to_r. But because of it's simple excel like formatting, it's difficult to render images like DT
If only data, go ahead and use rhandsontable.
Eg.
rhandsontable(df) %>%
hot_cols(colWidths = c(80,150,80,80,80,80,200,200,80,80,300,80,80), manualColumnResize = TRUE) %>%
hot_col(2:13, renderer = "html") %>%
hot_col(2:13, renderer = htmlwidgets::JS("safeHtmlRenderer")) %>%
hot_col(1, renderer = "
function(instance, td, row, col, prop, value, cellProperties) {
var escaped = Handsontable.helper.stringify(value),
img;
if (escaped.indexOf('http') === 0) {
img = document.createElement('IMG');
img.src = value;
Handsontable.dom.addEvent(img, 'mousedown', function (e){
e.preventDefault(); // prevent selection quirk
});
Handsontable.dom.empty(td);
td.appendChild(img);
}
else {
// render as text
Handsontable.renderers.TextRenderer.apply(this, arguments);
}
return td;
}")
})
observeEvent(input$submitComments, {
a = hot_to_r(input$upcomingAuctionsTable)
# sqlSave(myConnUpcom, a, tablename = "test", rownames = FALSE, varTypes = c(date = "varchar(255)"))
sqlUpdate(myConnUpcom, a, tablename = "temp", index = "item_id")
})

Subsetting or filtering of data as per dynamic user input

I like to filter data as per dynamic user input. Below mentioned sample code is executable, however, currently, it does not have the logic to filter the data. Therefore, the table does not change irrespective of selection of any variables. The code has mainly three components: 1. First few lines of code creates a data frame of which information can be used in creating controls widgets on the fly; 2. ui code; 3. server currently has logic to create control widgets on the fly, and I need to figure out how to use information from dynamically created control widgets and filter data (which is used for different purposes). I'm unable to figure out how to track the number of variable and filtering as per their range. Greatly appreciate suggestions.
## Create a data frame of which information is used in creating (dynamical) control widgets
varnames <- names(iris[,1:4]) # names
varinit <- apply(iris[,1:4],2,median) # initival value used in slider
varmin <- apply(iris[,1:4],2,min) # min.
varmax <- apply(iris[,1:4],2,max) # max.
## dataframe
vardf <- data.frame(varnames,varmin,varmax,varinit)
ui <- fluidPage(
checkboxGroupInput("ConditioningVariables", "Conditioning variables (choose one or more):",
varnames,inline = TRUE),
uiOutput("ControlWidgetsofConditioningVariables"),
tableOutput("data")
)
server <- function(input, output, session) {
output$ControlWidgetsofConditioningVariables <- renderUI({
if (is.null(input$ConditioningVariables)){
return()
} else {
selvarnames = sort(input$ConditioningVariables)
selpos = sapply(selvarnames,function(x) which(varnames==x))
# create a taglist of dynamic widgets
ListofDynamicWidgets <- lapply(selpos, function(x){sliderInput(as.character(vardf[x,1]),
as.character(vardf[x,1]),
vardf[x,2],vardf[x,3],
vardf[x,4],.1)})
do.call(tagList, ListofDynamicWidgets)
}
})
## filter data as per selected variables and their range
## this is where I'm kind of struck, I think I need to track number of variables (is list good idea?)
## and filter as per selected range of a specific variable
newdata <- reactive({
subset(iris)
})
output$data <- renderTable({ newdata() })
}
shinyApp(ui, server)
Here's one approach (note that I slightly modified the code to generate the widgets). The main idea is to generate a named list of the range vectors (conds), convert the ranges into a list of character strings that represent the filtering conditions (subs), collapse them into one long string of conditions, and use that as the subsetting argument for filter_().
library(dplyr)
server <- function(input, output, session) {
allControls <- lapply(setNames(varnames, varnames), function(x) {
sliderInput(x, x, varmin[x], varmax[x], c(varmin[x], varinit[x]),
round = -2)
})
output$ControlWidgetsofConditioningVariables <- renderUI({
if (is.null(input$ConditioningVariables)){
return()
} else {
allControls[input$ConditioningVariables]
}
})
## filter data as per selected variables and their range
newdata <- reactive({
if(!is.null(input$ConditioningVariables)) {
cond_names <- input$ConditioningVariables
conds <- lapply(setNames(cond_names, cond_names), function(x) input[[x]])
subs <- mapply(function(name, range){
if (!is.null(range))
sprintf("%1$s >= %2$f & %1$s <= %3$f", name, range[1], range[2])
}, names(conds), conds)
subs <- subs[!sapply(subs, is.null)]
if (length(subs) > 0)
filter_(iris, paste0(subs, collapse = " & "))
} else {
iris
}
})
output$data <- renderTable({ newdata() })
}
Output:

multiple selectizeInput from fileInput

I would like to have a user input a file (.csv) and from that file, two selectizeInputs will populate with the column names of the .csv. One will ask the user which of the columns from their uploaded file is the y-variable and which of the columns is the x-variable. This, I was able to do.
What I cannot do is the following: I would like to get the selection from their y-variable to disappear from their x-variable choices in the x-variable drop-down menu.
Also, I've used the answer to this question to try to help, but they are not using the values from a fileInput. As such, I cannot get my code, which is below, to work. Thank you for any advice/help you can give.
ui<- fluidPage(
titlePanel("Test"),
sidebarPanel(
fileInput(inputId = "file1", label = "Upload File"),
selectizeInput(
"sampleyvars", "Y-vars", choices = NULL, multiple = FALSE
),
selectizeInput(
"samplevars", "X-vars", choices = NULL, multiple = TRUE
)
),
mainPanel(h3("Nothing special")
)
)
server<- function(input, output, session) {
observe({
file1 <- input$file1
if(is.null(file1)){return()}
dataSet <- read.csv(file=file1$datapath)
vals1<-input$sampleyvars
vals2<-input$samplevars
updateSelectizeInput(session, "sampleyvars",
choices = colnames(dataSet)[! vals1 %in% vals2])
updateSelectizeInput(session, "samplexvars",
choices =colnames(dataSet)[! vals2 %in% vals1])
})
}
shinyApp(ui = ui,server = server)
You had wrong ID of the widget for X variable: samplevars instead of samplexvars which you used in update* function. I changed it to the latter ID and also slightly tweaked your code to get the desired effect.
Full example:
ui<- fluidPage(
titlePanel("Test"),
sidebarPanel(
fileInput(inputId = "file1", label = "Upload File"),
selectizeInput(
"sampleyvars", "Y-vars", choices = NULL, multiple = FALSE
),
# you had ID here wrong
selectizeInput(
"samplexvars", "X-vars", choices = NULL, multiple = TRUE
)
),
mainPanel(h3("Nothing special")
)
)
server<- function(input, output, session) {
data <- reactive({
file1 <- input$file1
req(file1)
dataSet <- read.csv(file=file1$datapath)
vars <- colnames(dataSet)
updateSelectizeInput(session, "sampleyvars", "Y-vars",
choices = vars, selected = vars[1])
updateSelectizeInput(session, "samplexvars", choices = vars[-1], selected = vars[2])
dataSet
})
observe({
varX <- colnames(data())
varX <- varX[!(varX %in% input$sampleyvars)]
updateSelectizeInput(session, "samplexvars", "X-vars", choices = varX)
})
}
shinyApp(ui = ui,server = server)

SQL Query Error when selecting separate columns

I do not understand the error which appears in my SQL Query. If i choose in SQL
Select * -> it is working fine and i do get the table,
however if i select any of the column/s it is giving me an Error:
Error in $<-.data.frame(*tmp*, "PROBE", value =
structure(integer(0), .Label = character(0), class = "factor")) :
replacement has 0 rows, data has 1427
Here is my SQL code:
if(input$filter == 1){
sqlOutput <- reactive({
sqlInput <- paste("select * from DWH.PROBE where DWH.PROBE.Nr =",paste0("'",d(),"'"), "And DWH.PROBE.AB BETWEEN",input$abmfrom, "AND",input$abmto,"ORDER BY Datum asc")
print(sqlInput)
dbGetQuery(con$cc, sqlInput)
})
}else{
sqlOutput <- reactive({
sqlInput <- paste("select * from DWH.PROBE where DWH.PROBE.S BETWEEN",d2(), "AND",input$sgehalt2, "And DWH.PROBE.AB BETWEEN",input$abmfrom2, "AND",input$abmto2,"ORDER BY Datum asc")
dbGetQuery(con$cc, sqlInput)
})}
And if i just add to those SQL Queries
select DWH.PROBE.S, DWH.PROBE.AB.. from DWH.PROBE
Then it comes above mentioned Error.
Additionally i need to say if i will use this SQL Query in a simple code:
rs <- dbSendQuery(con, paste("select DWH.PROBE.AB, DWH.PROBE.S from DWH.PROBE where DWH.PROBE.Nr = '50' And DWH.PROBE.AB BETWEEN 40 AND 50 ORDER BY Datum asc"))
data <- fetch(rs)
It is giving me the results...
Any ideas?
[EDIT *as my question is not a duplicate]
The question posted here: http://stackoverflow.com/questions/32048072/how-to-pass-input-variable-to-sql-statement-in-r-shiny actually has nothing to do with my topic. As we can see the error in this post:
Error in .getReactiveEnvironment()$currentContext() : Operation not
allowed without an active reactive context. (You tried to do something
that can only be done from inside a reactive expression or observer.)
I do not have a problems with passing input variable to sql statement and additionally if you can see in my SQL: The Query is in reactive context!:
sqlOutput <- reactive({...
The solution for above question was:
to put SQL Query in reactive context which is not a thing in my case
[EDIT 2] -> bits related to sqlOutput()
Here is a bit of code related to sqlOutput() which i am using in my Shiny App (at the moment this is the only bit because i am stuck with SQL Query)
output$tabelle <- DT::renderDataTable({
data <- sqlOutput()
data$PROBE <- as.factor(as.character(data$PROBE))
data
}, rownames=TRUE, filter="top", class = 'cell-border stripe',
options = list(pageLength = 100, lengthMenu=c(100,200,500), columnDefs = list(list(width = '200px', targets = "_all"),list(bSortable = FALSE, targets = "_all"))))
Thanks
Error doesn't relate to SQL statements, however, try changing your code to below:
sqlOutput <- reactive({
if(input$filter == 1){
sqlInput <- paste("select * from DWH.PROBE where DWH.PROBE.Nr =",paste0("'",d(),"'"), "And DWH.PROBE.AB BETWEEN",input$abmfrom, "AND",input$abmto,"ORDER BY Datum asc")
} else {
sqlInput <- paste("select * from DWH.PROBE where DWH.PROBE.S BETWEEN",d2(), "AND",input$sgehalt2, "And DWH.PROBE.AB BETWEEN",input$abmfrom2, "AND",input$abmto2,"ORDER BY Datum asc")
}
dbGetQuery(con$cc, sqlInput)
})

How to write a MODULE with two textInputs, in which input in one, blanks out the other and vv?

I want to write a MODULE where the client function returns a taglist with 2 textInputs. The user can enter a value in either textInput 1 or textInput 2 but not both. In other words, one textInput excludes the other, emulating a set of radio buttons.
Hence, the server function should observe the input in such a way that if the user enters a value in textInput 1, then textInput 2 is made blank and vice versa.
Also, the server function returns a dataframe with the values in the textInputs, i.e. either data.frame (one = enteredValue, two = NA) or data.frame (one = NA, two = enteredValue)
As we are planning on using this two-textInput widget in many of our shiny apps, I really want to make it a module. It seems like a simple thing to implement, but so far I have not been successful. (My experience is that observe, observeEvent, and eventReactive work differently in modules than in regular apps)
Any ideas to point me in the right direction are welcome.
I believe eventReactive and observeEvent work inside of a Shiny module. I've created a small module that basically does what you describe.
ui.R
library(shiny)
library(shinydashboard)
source("doubleField.R")
shinyUI(dashboardPage(
dashboardHeader(title = "Test"),
dashboardSidebar(disable = T),
dashboardBody(
doubleFieldUI("fields"),
fluidRow(
dataTableOutput("outputTable")
)
)
))
server.R
library(shiny)
source("doubleField.R")
shinyServer(function(input, output) {
fields <- callModule(doubleField, "fields")
output$outputTable <- renderDataTable(fields())
})
doubleField.R
library(stringr)
doubleFieldUI <- function(id) {
ns <- NS(id)
return(
tagList(
fluidRow(
column(width = 6, textInput(ns("fieldA"), "Field A")),
column(width = 6, textInput(ns("fieldB"), "Field B"))
),
fluidRow(
column(width = 2, "Output: "),
column(width = 4, textOutput(ns("outputValue")))
)
)
)
}
is_empty_string <- function(s) {
return(str_length(s) == 0)
}
doubleField <- function(input, output, session) {
valueA <- eventReactive(input$fieldA, {
if(!is_empty_string(input$fieldA)) {
ns <- session$ns
updateTextInput(session, "fieldB", value = "")
return(input$fieldA)
}
return("")
})
valueB <- eventReactive(input$fieldB, {
if(!is_empty_string(input$fieldB)) {
ns <- session$ns
updateTextInput(session, "fieldA", value = "")
return(input$fieldB)
}
return("")
})
value <- reactive({
values <- c(input$fieldA, input$fieldB)
return(values[which(!is_empty_string(values))])
})
output$outputValue <- renderText({
value()
})
result_df <- reactive({
v_A <- valueA()
v_B <- valueB()
df <- data.frame(
list(
"valueA" = ifelse(is_empty_string(v_A), NULL, v_A),
"valueB" = ifelse(is_empty_string(v_B), NULL, v_B)
)
)
return(df)
})
return(result_df);
}
I hope this helps getting you started.