【问题标题】:How to query number of Ids in batches in R如何在R中批量查询ID数
【发布时间】:2020-05-18 23:42:00
【问题描述】:

我在下面提到了 R 中的数据框。

ID       Amount     Date
IK-1     100        2020-01-01
IK-2     110        2020-01-02
IK-3     120        2020-01-03
IK-4     109        2020-01-03
IK-5     104        2020-01-03

我正在使用ID 使用以下代码从 MySQL 获取一些详细信息。

library(RMySQL)

conn<- connection

query<-paste0("SELECT c.ID,e.Parameters, d.status
FROM Table1 c
left outer join Table2 d ON d.seq_id=c.ID
LEFT outer JOIN Table3 e ON e.role_id=d.role
           where c.ID IN (", paste(shQuote(dataframe$ID, type = "sh"),
                                      collapse = ', '),") 
and e.Parameters in
           ('Section1',
           'Section2','Section3',
           'Section4');")

res1 <- dbGetQuery(conn,query)

res2<-res1[res1$Parameters=="Section1",4:5]
colnames(res2)[colnames(res2)=="status"] <- "Section1_Status"

上面的代码工作正常,如果我传递了 ~1000 个 ID,但一次传递 10000 个或更多 ID 时会引发 R 终止错误。

如何创建一个循环并批量传递 ID 以获得 10000 ID 的最终输出。

错误信息:

Warning message:
In dbFetch(rs, n = n, ...) : error while fetching rows

【问题讨论】:

  • 只是猜测,我们也可以不加入 r 数据帧吗?用JOIN dataframe[, "ID", drop = FALSE] x ON x.ID = e.role_id 替换你的“where ...”?
  • @zx8754:我试过了,没用。
  • @Vector JX 能否请您包含错误消息。
  • @A.Suliman:运行大约 10 分钟后,R 在会话到期时终止,没有显示任何错误消息。传递大约 1000 个 ID 时,代码运行良好。
  • @A.Suliman:有时会收到类似Warning message: In dbFetch(rs, n = n, ...) : error while fetching rows的错误消息

标签: r dataframe dplyr rmysql


【解决方案1】:

正如@A.Suliman 的链接所暗示的,这很可能是由于您的 IN 子句中有大量值。以下是一些可以尝试的解决方案:

批处理

我喜欢使用模数来批处理。这假设您批处理的 ID 值是数字:

num_batches = 100
output_list = list()

for(i in 1:num_batches){
    this_subset = filter(dataframe, ID %% num_batches == (i-1))

    # subsequent processing using this_subset

    output_list[i] = results_from_subsetting
}
output = data.table::rbindlist(output_list)

在您的情况下,ID 似乎采用XX-123 的形式(两个字符,一个连字符,后跟一些数字)。您可以使用以下方法将其转换为数字:just_number_part = substr(ID, 4, nchar(ID))

临时文件写入

如果您要将dataframe 从 R 写入 sql,那么您将不需要这么大的 IN 子句,而可以使用连接。 dbplyr 包包含一个函数copy_to,可用于将临时表写入数据库。

这看起来像:

library(RMySQL)
library(dbplyr)

conn<- connection

copy_to(conn, dataframe, name = "my_table_name") # copy local table to mysql

query<-paste0("SELECT c.ID,e.Parameters, d.status
FROM Table1 c
INNER JOIN my_table_name a ON a.ID = c.ID # replace IN-clause with inner join
left outer join Table2 d ON d.seq_id=c.ID
LEFT outer JOIN Table3 e ON e.role_id=d.role
WHERE e.Parameters in
           ('Section1',
           'Section2','Section3',
           'Section4');")

res1 <- dbGetQuery(conn,query)

作为参考,我推荐the tidyverse documentation。您可能还会发现 this question 在使用 copy_to 编写时对调试很有帮助。

增加超时延迟

当 IN 子句中有很多值时,查询的执行速度会慢得多,因为 IN 子句本质上被转换为一系列 OR 语句。

根据this link,您可以通过以下方式更改 MySQL 的超时选项:

  • 编辑您的 my.cnf(MySQL 配置文件)
  • 添加超时配置并调整它以适合您的服务器。
    • wait_timeout = 28800
    • interactive_timeout = 28800
  • 重启 MySQL

【讨论】:

  • 收到错误could not run statement: CREATE command denied to user
  • 我认为这是您尝试copy_to 的时候。您似乎无权在 MySQL 数据库中创建新表。所以我建议的第二种方法不适用于您的配置
【解决方案2】:

在您的 SQL 查询之前将 ID 的数据框传递到一个临时表中,然后使用它对您正在使用的 ID 进行内部联接,这样您就可以避免循环。您只需使用dbWriteTable 并在调用时设置参数temporary = TRUE

前:

library(DBI)
library(RMySQL)
con <- dbConnect(RMySQL::MySQL(), user='user', 
password='password', dbname='database_name', host='host')
#here we write the table into the DB and then declare it as temporary
dbWriteTable(conn = con, value = dataframe, name = "id_frame", temporary = T)
res1 <- dbGetQuery(con = conn, "SELECT c.ID,e.Parameters, d.status
FROM Table1 c
left outer join Table2 d ON d.seq_id=c.ID
LEFT outer JOIN Table3 e ON e.role_id=d.role
Inner join id_frame idf on idf.ID = c.ID 
and e.Parameters in
       ('Section1',
       'Section2','Section3',
       'Section4');")

这应该可以提高代码的性能,并且您不再需要使用 where 语句在 R 中循环。让我知道它是否无法正常工作。

【讨论】:

  • 我收到错误could not run statement: CREATE command denied to user
  • 您的系统管理员有什么方法可以授予您对临时表的创建权限吗?您可能对数据库具有只读访问权限。 CREATE 和 CREATE TEMPORARY TABLE 的权限有所不同。我只是想验证您是否有临时表权限。
  • 不,我没有写权限。
【解决方案3】:
# Load Packages
library(dplyr) # only needed to create the initial dataframe
library(RMySQL)

# create the initial dataframe
df <- tribble(
    ~ID,       ~Amount,     ~Date
    , "IK-1"    , 100       , 2020-01-01
    , "IK-2"    , 110       , 2020-01-02
    , "IK-3"    , 120       , 2020-01-03
    , "IK-4"    , 109       , 2020-01-03
    , "IK-5"    , 104       , 2020-01-03
)

# first helper function
createIDBatchVector <- function(x, batchSize){
    paste0(
        "'"
        , sapply(
            split(x, ceiling(seq_along(x) / batchSize))
            , paste
            , collapse = "','"
        )
        , "'"
    )
}

# second helper function
createQueries <- function(IDbatches){
    paste0("
SELECT c.ID,e.Parameters, d.status
FROM Table1 c
    LEFT OUTER JOIN Table2 d ON d.seq_id =c.ID
    LEFT OUTER JOIN Table3 e ON e.role_id = d.role
WHERE c.ID IN (", IDbatches,") 
AND e.Parameters in ('Section1','Section2','Section3','Section4');
")
}

# ------------------------------------------------------------------

# and now the actual script

# first we create a vector that contains one batch per element
IDbatches <- createIDBatchVector(df$ID, 2)

# It looks like this:
# [1] "'IK-1','IK-2'" "'IK-3','IK-4'" "'IK-5'" 

# now we create a vector of SQL-queries out of that
queries <- createQueries(IDbatches)

cat(queries) # use cat to show what they look like

# it looks like this:

# SELECT c.ID,e.Parameters, d.status
# FROM Table1 c
#     LEFT OUTER JOIN Table2 d ON d.seq_id =c.ID
#     LEFT OUTER JOIN Table3 e ON e.role_id = d.role
# WHERE c.ID IN ('IK-1','IK-2') 
# AND e.Parameters in ('Section1','Section2','Section3','Section4');
#  
# SELECT c.ID,e.Parameters, d.status
# FROM Table1 c
#     LEFT OUTER JOIN Table2 d ON d.seq_id =c.ID
#     LEFT OUTER JOIN Table3 e ON e.role_id = d.role
# WHERE c.ID IN ('IK-3','IK-4') 
# AND e.Parameters in ('Section1','Section2','Section3','Section4');
#  
# SELECT c.ID,e.Parameters, d.status
# FROM Table1 c
#     LEFT OUTER JOIN Table2 d ON d.seq_id =c.ID
#     LEFT OUTER JOIN Table3 e ON e.role_id = d.role
# WHERE c.ID IN ('IK-5') 
# AND e.Parameters in ('Section1','Section2','Section3','Section4');

# and now the loop
df_final <- data.frame() # initialize a dataframe

conn <- connection # open a connection
for (query in queries){ # iterate over the queries
    df_final <- rbind(df_final, dbGetQuery(conn,query))
}

# And here the connection should be closed. (I don't know the function call for this.)

【讨论】:

    【解决方案4】:

    也许只是尝试...

    根据上述评论,MySQL IN (...) 条件中可能存在大小限制。 也许您可以通过将dataframe$IDs 的整个列表拆分到子列表中并使用以下条件重写您的查询来绕过它:

    WHERE c.ID IN sublist#1
    OR c.ID IN sublist#2
    OR c.ID IN sublist#3
    ...
    

    而不是唯一的c.ID IN list ?

    假设我们创建最大长度为 1000 的子列表,它可以给出:

    sublists <- split(dataframe$ID, ceiling(seq_along(dataframe$ID)/1000))
    

    然后,你可以构建一个类似"OR c.ID IN (...) OR c.ID IN (...) OR c.ID IN (...) ...的字符串

    插入您的代码,这将给出:

    library(RMySQL)
    conn<- connection
    sublists <- split(dataframe$ID, ceiling(seq_along(dataframe$ID)/1000))
    
    query <- paste0("SELECT c.ID,e.Parameters, d.status
    FROM Table1 c
    left outer join Table2 d ON d.seq_id=c.ID
    LEFT outer JOIN Table3 e ON e.role_id=d.role
               where 1 = 1 AND (", # to get rid of the "where"
           paste(lapply(sublists, 
                        FUN = function(x){
                          paste0("OR c.ID IN (",  paste(shQuote(x, type = "sh"), collapse = ', '), ")")
                        }), 
                 collapse = "\n"), ")
    and e.Parameters in
               ('Section1',
               'Section2','Section3',
               'Section4');") %>% cat
    
    res1 <- dbGetQuery(conn,query)
    
    res2<-res1[res1$Parameters=="Section1",4:5]
    colnames(res2)[colnames(res2)=="status"] <- "Section1_Status"
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2023-04-08
      • 1970-01-01
      • 1970-01-01
      • 2016-12-04
      • 2020-10-31
      • 1970-01-01
      • 2022-01-28
      • 1970-01-01
      相关资源
      最近更新 更多