我有一个 Oracle 数据库,每天刷新一次。 我对应用程序如何在 Shiny 中工作、应用程序启动时运行一次以及每个会话运行一次感到有点困惑。
我的天真的方法是创建一个数据库连接,并在 UI 和服务器代码之外运行查询,以创建一个包含大约 600,000 条记录的数据帧……然后可以在会话期间对其进行过滤和切片。 我有点担心在全局范围内的 app.R 中执行此操作,该连接和数据帧只会在服务器启动应用程序时创建一次,并且永远不会再次运行(如果有意义的话)。
如果我在服务器中创建数据框,那么我的 UI 代码将失败,因为它依赖于填充选择列表的查询结果,并且我目前在 app.R 范围内执行此操作,因此 UI 可以访问它.
library(shiny)
library(DBI)
library(dplyr)
library(odbc)
library(stringdist)
library(reactable)
############################################################################
# business functions #
############################################################################
get_list_of_actives_from_db <- function() {
con <- dbConnect(odbc::odbc(), Driver="oracle", Host = "server.mycompany.net", Port = "1521", SVC = "service1", UID = "user_01", PWD = "hello", timeout = 10)
ingredients_df = dbGetQuery(con,
'
select DISTINCT INGREDIENTS FROM AES
'
)
}
get_adverse_events_from_db <- function() {
con <- dbConnect(odbc::odbc(), Driver="oracle", Host = "server.mycompany.net", Port = "1521", SVC = "service1", UID = "user_01", PWD = "hello", timeout = 10)
cases_df = dbGetQuery(con,
'
select * FROM AES
'
)
return(cases_df)
}
############################################################################
# load data sets for use in dashboard #
############################################################################
cases_df = get_adverse_events_from_db() # drive select list in UI
ingredients_df = get_list_of_actives_from_db() # main data to slice and filter
############################################################################
# shiny UI #
############################################################################
ui <- fluidPage(
"Adverse Event Fuzzy Search Tool",
fluidRow(
selectInput("ingredients", label = "Select on or more Active Ingredients:", choices = ingredients_df$PRIMARY_SUSPECT_KEY_INGREDIENT, multi=TRUE),
textInput("search_term", "AE Search Term:"),
actionButton("do_search", "Perform Search")
)
,
fluidRow(
reactableOutput("search_results")
)
)
############################################################################
# shiny server #
############################################################################
server <- function(input, output, session) {
# do stuff here to filter the data frame based on the selected value and render a table
}
# Run the application
shinyApp(ui = ui, server = server)
我主要关心的是在 app.R 的根目录中执行此操作,这两个函数都运行 Oracle 查询,这些查询永远不需要为会话重新运行,因为数据只会通过 ETL 过夜更改。
############################################################################
# load data sets for use in dashboard #
############################################################################
cases_df = get_adverse_events_from_db()
ingredients_df = get_list_of_actives_from_db()
何时以及多久调用一次? 一旦应用程序初始化,数据集就永远不会更新并由用户在会话之间共享? 或者每当新会话启动时整个脚本都会端到端运行?
我的一部分认为它应该在服务器功能中,所以它每个会话运行一次。 但作为 Shiny 的新手,我觉得只要 UI 发生变化,服务器就会不断地被调用,我不想不断地从 Oracle 加载 600,000 条记录。
理想情况下,我会每天缓存一次结果,并让所有会话中的所有用户都可以使用它们,但不确定如何实现这一点 - 所以现在只想知道实现此目的的最佳方法,因此每个用户运行一次查询并为会话缓存了数据帧。
在此上下文中,请查看 RStudio 的文章 Shiny 应用程序的范围规则。
如果我没猜错的话,您是要求在闪亮的会话之间共享数据集并每天更新(问题的标题并不真正符合您对问题的解释 - 我编辑了它)。
我建议使用跨会话
reactivePoll
来避免不必要的数据库查询(我曾经问过类似的问题here - 在那里我给出了一个示例,表明可以通过reactiveValues实现相同的效果,但它更多复杂)。
这是您可以使用的简单模式 - 请注意 reactivePoll
是在服务器功能之外定义的,因此所有会话共享相同的数据:
library(shiny)
ui <- fluidPage(textOutput("my_db_data"))
updated_db_data <- reactivePoll(
intervalMillis = 1000L*60L*5L, # check for a new day every 5 minutes
session = NULL,
checkFunc = function() {
print(paste("Running checkFunc:", Sys.time()))
Sys.Date()
},
valueFunc = function() {
# your db query goes here:
paste("Latests DB update:", Sys.time())
}
)
server <- function(input, output, session) {
output$my_db_data <- renderText(updated_db_data())
}
shinyApp(ui, server)
在这里,每 5 分钟 checkFunc
检查新的一天 - 仅当
valueFunc
的结果发生变化时才执行
checkFunc
。作为
checkFunc
的(现实世界)替代方案,您可以实现一个查询来检查某个数据库表的行数。 PS:查看
reactiveFileReader
reactivePoll
(基于?reactiveFileReader
)的示例
PPS:在该数据集上进行进一步过滤等时,还要检查 bindCache()。
server <- function(input, output, session) {
dailydata_ <- reactiveValues(when = NULL, what = NULL)
dailydata <- reactive({
oldwhen <- dailydata_$when
if (is.null(oldwhen) ||
as.Date(oldwhen) < Sys.Date()) {
newdata <- tryCatch(
DBI::dbGetQuery(con, "..."),
error = function(e) e)
if (inherits(newdata, "error")) {
warning("error retrieving new data: ", conditionMessage(e))
warning("using stale data instead")
} else {
dailydata_$when <- Sys.time()
dailydata_$what <- newdata
}
}
dailydata_$what
})
# some consumer of the real data
output$tbl <- renderTable(dailydata())
}
这样做的好处是,当在不同的天检索数据时,会触发重新查询。诚然,当新的 ETL 可用时,可能会改变此条件的具体形成方式,如果它在(例如)凌晨 2 点更新,那么您可能需要更多的时间数学来确定当前数据是之前还是之后最新更新。
此逻辑存在“数据可用”失败:如果无法查询,则重新使用当前/过时的数据。如果您希望它不返回数据,则可以很容易地在代码中进行更改。(您可能想做的一件事是向用户显示上次检索数据的时间;可以使用
dailydata_$when
直接检索数据,接受它可能是
NULL
。)