在 Web 应用程序会话中获取大 SQL table

Fetching big SQL table in the web app session

我是网络应用程序的新手,如果我的问题有点基础,我深表歉意。我正在开发一个带有 R shiny 的 Web 应用程序,其中输入是来自 Azure SQL 服务器的非常大的表。它们是 20 个表,每个表按十万行和数百列的顺序排列,包含数字、字符等。我调用它们没有问题,我的主要问题是从 Azure SQL 服务器。大约需要 20 分钟。所以网络应用程序的用户需要等待相当长的时间。 我正在使用 DBI 包如下:

db_connect <- function(database_config_name){
  dbConfig <- config::get(database_config_name)
  connection <- DBI::dbConnect(odbc::odbc(),
                        Driver = dbConfig$driver,
                        Server = dbConfig$server,
                        UID    = dbConfig$uid,
                        PWD    = dbConfig$pwd,
                        Database = dbConfig$database,
                        encoding = "latin1"
  )

  return(connection)
}

然后通过 :

获取表格
connection <- db_connect(db_config_name)
table <- dplyr::tbl(con, dbplyr::in_schema(fetch_schema_name(db_config_name,table_name,data_source_type), fetch_table_name(db_config_name,table_name,data_source_type)))

我搜索了很多但没有找到好的解决方案,我很感激任何解决方案都可以解决这个问题。

我每天使用 R 访问 SQL 服务器(不是 Azure)。对于较大的数据(如您的示例),我总是恢复使用命令行工具 sqlcmd,速度要快得多。对我来说唯一的痛点是学习参数并解决它不 return 正确 CSV 的事实,需要 post-query munging。您可能还有一个额外的痛点,那就是必须调整我的示例以连接到您的 Azure 实例(我没有帐户)。

为了在闪亮的环境中使用它并保持其交互性,我使用 processx 包在后台启动该进程,然后定期轮询其退出状态以确定它何时完成。

预先说明:这主要是一个“松散指南”,我不会假装这是一个适合您的全功能解决方案。您可能需要自己解决一些粗糙的问题。例如,虽然我说您可以异步执行此操作,但轮询过程和延迟数据可用性到您闪亮的应用程序中的工作取决于您。我在这里的回答是启动流程并在完成后读取文件。最后,如果 encoding= 对你来说是个问题,我不知道 sqlcmd 是否正确地使用了非拉丁语,而且我不知道是否或如何解决这个非常有限的问题,甚至过时的论点。

步骤:

  1. 将查询保存到文本文件中。可以在命令行上提供简短的查询,但过了某个点(128 个字符?我不知道它是否明确定义,而且最近看得不够多)它就失败了。使用 query-file 非常简单并且总是有效,所以我总是使用它。

    我总是为每个查询使用临时文件,而不是对文件名进行硬编码;这是有道理的。为了方便(对我而言),我使用相同的临时文件基本名称并为查询附加 .sql 并为 returned 数据附加 .csv,这样匹配查询要容易得多-临时文件中的数据。这是我使用的约定,仅此而已。

    tf <- tempfile()
    # using the same tempfile base name for both the query and csv-output temp files
    querytf <- paste0(tf, ".sql")
    writeLines(query, querytf)
    csvtf <- paste0(tf, ".csv")
    # these may be useful in troubleshoot, but not always [^2]
    stdouttf <- paste0(tf, ".stdout")
    stderrtf <- paste0(tf, ".stderr")
    
  2. 拨打电话。我建议你先看看同步方式有多快,看看你是否需要在你的闪亮界面中添加异步查询和轮询。

    exe <- "/path/to/sqlcmd" # or "sqlcmd.exe"
    args <- c("-W", "b", "-s", "7", "-i", querytf, "-o", csvtf,
              "-S", dbConfig$server, "-d", dbConfig$database,
              "-U", dbConfig$uid, "-P", dbConfig$pwd)
    ## as to why I use "7", see [^1]
    ## note that the user id and password will be visible on the shiny server
    ## via a `ps -fax` command-line call
    proc <- processx::process$new(command = exe, args = args,
                                  stdout = stdouttf, stderr = stderrtf) # other args exist
    # this should return immediately, and should be TRUE until
    # data retrieval is done (or error)
    proc$is_alive()
    # this will hang (pause R) until retrieval is complete; if/when you
    # shift to asynchronous queries, do not do this
    proc$wait()
    

    可以使用 processx::run 代替 process$newproc$wait(),但我想我会引导你走这条路,以防你 want/need 去异步。

  3. 如果您进行异步操作,则定期检查(可能每 3 或 10 秒)proc$is_alive()。一旦 returns FALSE,您就可以开始处理文件了。在此期间,shiny 将继续正常运行。 (如果你不去异步,因此选择 proc$wait(),那么 shiny 将挂起,直到查询完成。)

    如果您犯了错误而没有 proc$wait() 并尝试继续阅读文件,那就是错误的。该文件可能不存在,在这种情况下它会出错 No such file or directory。该文件可能存在,也可能是空的。它可能存在并且数据不完整。所以真的,做出坚定的决定保持同步并因此调用 proc$wait(),或者进行异步并定期轮询直到 proc$is_alive() returns FALSE.

  4. 正在读入文件。使用 sqlcmd 的三个“乐趣”需要对文件进行特殊处理。

    1. 它没有始终如一地嵌入引号,这就是我选择使用 "7" 作为分隔符的原因。 (参见 [^1]。)
    2. 它在列名下添加了一行破折号,这会在 R 读入数据时破坏数据的自动分类。为此,我们对文件进行两步读取。
    3. 数据库中的空值是数据中的文字 NULL 字符串。为此,我们在读取文件时更新 na.strings= 参数。
    exitstat <- proc$get_exit_status()
    if (exitstat == 0) {
      ## read #1: get the column headers
      tmp1 <- read.csv(csvtf, nrows = 2, sep = "7", header = FALSE)
      colnms <- unlist(tmp1[1,], use.names = FALSE)
      ## read #2: read the rest of the data
      out <- read.csv(csvtf, skip = 2, header = FALSE, sep = "7",
                      na.strings = c("NA", "NULL"), quote = "")
      colnames(out) <- colnms
    } else {
      # you should check both stdout and stderr files, see [^2]
      stop("'sqlcmd' exit status: ", exitstat)
    }
    

注:

  1. 在经历了几个问题的痛苦之后(一些在 sqlcmd.exe,一些在 data.table::fread 和其他读者,都在处理 CSV 格式的不合规问题),在有一点我选择停止使用逗号分隔的 returns,而是选择 "7" 字段 Delimiter。它适用于所有 CSV 读取工具,并修复了很多问题(有些问题未在此处提及)。如果您不担心,请随时将 args 更改为 "-s", ","(同时调整读取)。

  2. sqlcmd 似乎在出现问题时以不同的方式使用 stdout 或 stderr。我确定某处有理由,但关键是如果有问题,请检查两个文件。

    我添加了 stdout=stderr= 的使用,因为我做了很多故障排除,如果我进行了查询,我会继续这样做。并非严格要求使用它们,但如果省略这些选项,您可能会失去警惕。

  3. 顺便说一句,如果您选择 使用 sqlcmd 进行所有查询,则无需创建连接对象在 R 中。也就是说,db_connect 可能不是必需的。在我的使用中,我倾向于对已知的小查询使用“真实”R DBI 连接,对超过 10K 行的任何内容使用批量 sqlcmd。有一个权衡;我没有在我的环境中对其进行充分测量,无法知道临界点在哪里,您的情况可能有所不同。