【问题标题】:Handling paginated SQL query results处理分页 SQL 查询结果
【发布时间】:2014-06-19 14:13:05
【问题描述】:

对于我的论文数据收集,其中一个来源是一个外部管理的系统,它基于用于提交SQL 查询的 Web 表单。使用RRCurl,我实现了一个自动化的数据收集框架,我在其中模拟了上述表格。当我限制结果数据集的大小时,一切都运行良好。但是,当我尝试超过 100000 条记录时(下面代码中的RQ_SIZE),串联的“我的代码 - 他们的系统”开始没有响应(“挂起”)。

所以,我决定使用SQL分页功能LIMIT ... OFFSET ...)提交一系列请求,希望将分页结果组合成目标数据框架。但是,在相应地更改我的代码后,我看到的输出只是一个分页进度字符(*),然后就没有更多的输出了。如果您能帮助我确定意外行为的可能原因,我将不胜感激。我无法提供可重现的示例,因为提取功能非常困难,更不用说数据了,但我希望以下代码 sn-p 足以揭示问题(或者,至少是解决问题的方向) .

# First, retrieve total number of rows for the request
srdaRequestData(queryURL, "COUNT(*)", rq$from, rq$where,
                DATA_SEP, ADD_SQL)
assign(dataName, srdaGetData()) # retrieve result
data <- get(dataName)
numRequests <- as.numeric(data) %/% RQ_SIZE + 1

# Now, we can request & retrieve data via SQL pagination
for (i in 1:numRequests) {

  # setup SQL pagination
  if (rq$where == '') rq$where <- '1=1'
  rq$where <- paste(rq$where, 'LIMIT', RQ_SIZE, 'OFFSET', RQ_SIZE*(i-1))

  # Submit data request
  srdaRequestData(queryURL, rq$select, rq$from, rq$where,
                  DATA_SEP, ADD_SQL)
  assign(dataName, srdaGetData()) # retrieve result
  data <- get(dataName)

  # some code

  # add current data frame to the list
  dfList <- c(dfList, data)
  if (DEBUG) message("*", appendLF = FALSE)
}

# merge all the result pages' data frames
data <- do.call("rbind", dfList)

# save current data frame to RDS file
saveRDS(data, rdataFile)

【问题讨论】:

  • 您好,目前还不清楚是什么问题。具体是什么不起作用?什么让你知道它不起作用。您得到了什么结果,它们与您预期的结果有何不同?
  • @RicardoSaporta:具体来说,问题是脚本变得无响应并且它的执行处于未确定(可能是挂起)状态(正如我所指出的,只打印了第一个 *,而结果包含180 万多条记录)。我将尝试减少 SQL 查询的限制(根据下面 Arthur 的回答),看看它是否有帮助。我在某处读到大约 100K 是 PostgreSQL 分页的边界值(如果我理解正确的话)。我不能使用可滚动游标或 SQL 查询之外的其他东西,因为我没有直接访问系统的权限。

标签: sql r postgresql pagination rcurl


【解决方案1】:

它可能属于 MySQL 阻碍 LIMIT OFFSET 的类别: Why does MYSQL higher LIMIT offset slow the query down?

总体而言,通过 HTTP 反复获取大型数据集并不是很可靠。

【讨论】:

  • 再次感谢您的帮助和漂亮的链接!我别无选择,只能通过 HTTP 检索数据,因为这是该特定系统提供的唯一接口。积极的是,在我的数据收集框架完全实现和测试之后,我不需要非常频繁地运行代码,因为我缓存了检索到的数据。
  • 如果您别无选择,那么与其检索整个数据集,不如将其限制为一个小子集。如果你得到它,开始增加。我怀疑你会达到极限。这可能是源内部设置不尊重长查询(云的东西)。
【解决方案2】:

由于这是给你的论文,这里有一只手:

## Folder were to save the results to disk.
##  Ideally, use a new, empty folder. Easier then to load from disk
folder.out <- "~/mydissertation/sql_data_scrape/"
## Create the folder if not exist. 
dir.create(folder.out, showWarnings=FALSE, recursive=TRUE)


## The larger this number, the more memory you will require. 
## If you are renting a large box on, say, EC2, then you can make this 100, or so
NumberOfOffsetsBetweenSaves <- 10

## The limit size per request
RQ_SIZE <- 1000

# First, retrieve total number of rows for the request
srdaRequestData(queryURL, "COUNT(*)", rq$from, rq$where,
                DATA_SEP, ADD_SQL)


## Get the total number of rows
TotalRows <- as.numeric(srdaGetData())

TotalNumberOfRequests <- TotalRows %/% RQ_SIZE

TotalNumberOfGroups <- TotalNumberOfRequests %/% NumberOfOffsetsBetweenSaves + 1

## FYI: Total number of rows being requested is
##  (NumberOfOffsetsBetweenSaves * RQ_SIZE * TotalNumberOfGroups) 


for (g in seq(TotalNumberOfGroups)) {

  ret <- 
    lapply(seq(NumberOfOffsetsBetweenSaves), function(i) {

      ## function(i) is the same code you have
      ##    inside your for loop, but cleaned up.

      # setup SQL pagination
      if (rq$where == '') 
          rq$where <- '1=1'

      rq$where <- paste(rq$where, 'LIMIT', RQ_SIZE, 'OFFSET', RQ_SIZE*g*(i-1))

      # Submit data request
      srdaRequestData(queryURL, rq$select, rq$from, rq$where,
                      DATA_SEP, ADD_SQL)

       # retrieve result
      data <- srdaGetData()

      # some code

      if (DEBUG) message("*", appendLF = FALSE)    


      ### DONT ASSIGN TO dfList, JUST RETURN `data`
      # xxxxxx DONT DO: xxxxx dfList <- c(dfList, data)
      ### INSTEAD:

      ## return
      data
  })

  ## save each iteration
  file.out <- sprintf("%s/data_scrape_%04i.RDS", folder.out, g)
  saveRDS(do.call(rbind, ret), file=file.out)

  ## OPTIONAL (this will be slower, but will keep your rams and goats in line)
  #    rm(ret)
  #    gc()
}

然后,一旦你完成了抓取:

library(data.table)

folder.out <- "~/mydissertation/sql_data_scrape/"

files <- dir(folder.out, full=TRUE, pattern="\\.RDS$") 

## Create an empty list
myData <- vector("list", length=length(files))


## Option 1, using data.frame
    for (i in seq(myData))
      myData[[i]] <- readRDS(files[[i]])

    DT <- do.call(rbind, myData)

## Option 2, using data.table
    for (i in seq(myData))
      myData[[i]] <- as.data.table(readRDS(files[[i]]))

    DT <- rbindlist(myData)

【讨论】:

  • 里卡多,非常感谢您的帮助!我查看了您建议的代码,看起来主要的本质区别是添加了一个 intermediate 步骤,将结果存储在 .rds 文件中。我有点困惑为什么这比我原来的方法更好。特别是,由于存储数据收集结果的最终目标是两次执行文件操作。如果您能澄清您的方法(以及将中间结果存储在内存中(dfList)而不是文件系统有什么问题),将不胜感激。再次感谢你!我期待您的来信。
  • 我们两个代码的真正区别在于我的lapply 的使用。将其保存到磁盘的目的只是备份我们的工作。记住,耶稣拯救。如果你觉得数据小到没必要,那么你可以跳过这一步,直接将数据保存到一个列表中,然后rbind整个列表
  • 顺便说一句,我在saveRDS 中忘记了do.call(rbind, ret)。编辑和修复
【解决方案3】:

我正在回答我自己的问题,因为我终于弄清楚了问题的真正根源。我的调查显示,程序的意外等待状态是由于 PostgreSQL 被畸形的 SQL 查询弄糊涂了,其中包含多个 LIMITOFFSET 关键字。

其中的原因非常简单:我在for 循环的外部和内部都使用了rq$where,这使得paste() 将前一个迭代的WHERE 子句与当前的子句连接起来。我通过处理WHERE 子句的内容并将其保存在循环之前,然后在循环的每次迭代中安全地使用保存的值来修复代码,因为它变得独立于原始WHERE 子句的值。

这项调查还帮助我修复了代码中的一些其他缺陷并进行了改进(例如使用子选择来正确处理 SQL 查询,返回带有聚合函数的查询的记录数)。故事的道德:在软件开发中你永远不会太小心。 非常感谢那些帮助解决这个问题的好人。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-12-31
    • 2021-09-11
    • 2019-12-23
    • 1970-01-01
    • 2011-03-14
    • 1970-01-01
    相关资源
    最近更新 更多