使用R

时间:2018-06-01 14:48:43

标签: sql r amazon-redshift

我需要每天多次向Redshift推送几千行。但出于管理原因,我无法使用S3的批量插入。什么是最快的方式?

详细信息:

有三种方法(我可以看到)从R中向Amazon Redshift中的表中插入行:

  1. 逐行插入查询。每行都作为自己的INSERT VALUES查询
  2. 插入
  3. 多行插入查询:与1相同,但每个查询插入多条记录。大小受行数或16MB最大SQL查询大小限制的约束。
  4. 从AWS S3,Dynamo或EMR批量插入。
  5. 上述每种方法比前一种方法快一个数量级。我喜欢使用批量插入来创建或更新表,但我们的组织已做出安全和管理决策,不允许业务用户批量加载或卸载数据到S3。这使我无法使用包redshiftTools上传文件。

    我可以使用RODBC::sqlSave执行上面的第1项。这很慢,但最终完成了工作。

    我更喜欢的是sqlSave类似于一次插入多行数据的内容。但并不是要超过红移的行/大小限制。这对于简单的数据结构来说很容易,但是处理整数,字符,日期等的通用函数将是理想的,因为我不能仅使用一个表来执行此操作。所以我弹出了sqlSave的源代码,并开始推出自己的函数来构建多行插入,将数据块化为1000行块,并为每个块构建并执行查询。

    但我已经停下来询问这是否已经完成了?有没有更好的办法?我有这种感觉,也许R的其他SQL包之一有一个功能来做这样的事情。但是,当我搜索所有我发现的是其他人having the same sort of problem

    任何提示?

    更新1

    由于一些提示,我调查了fast=TRUE中的RODBC::sqlSave开关。文档让我觉得这就是我之后的事情:

      快速:合乎逻辑。如果为false,则一次写入一行数据。如果为true,则使用参数化INSERT INTO或UPDATE查询在一次操作中写入所有数据。

    所以我想我应该测试一下。所以我创建了一个包含10条记录和2列的小数据框:

    df <- data.frame(a=rnorm(10), b=sample(letters, 10, replace=TRUE),
                     stringsAsFactors = FALSE)    
    

    然后我用benchmark计算了5次重复的执行时间:

    benchmark( sqlSave(dbhandle, df, tablename = 'jal_test1', append=TRUE, fast=TRUE), replications = 5)
    #                                                                         test replications elapsed relative user.self sys.self user.child sys.child
    # 1 sqlSave(dbhandle, df, tablename = "jal_test1", append = TRUE, fast = TRUE)            5  512.59        1      0.08     0.03         NA        NA
    
    benchmark( sqlSave(dbhandle, df, tablename = 'jal_test1', append=TRUE, fast=FALSE), replications = 5)
    #                                                                          test replications elapsed relative user.self sys.self user.child sys.child
    # 1 sqlSave(dbhandle, df, tablename = "jal_test1", append = TRUE, fast = FALSE)            5  264.37        1      0.08     0.02         NA        NA
    

    这有点难以阅读,但总结如下:

    • fast=TRUE耗时512秒
    • fast=FALSE花了264秒

    有25条记录,时间可以达到:

    • fast=TRUE花了1208秒
    • fast=FALSE花了604秒

    这对我来说毫无意义。

    更新2

    我试过test=TRUE开关,认为它会告诉我发生了什么,但我无法弄清楚它究竟发生了什么......但是转向verbose=TRUE有帮助我意识到fast=TRUE并没有按照我的想法去做。它似乎使用替换,但不做一个大的插入。它仍然会nrow(df)值插入:

    > df <- data.frame(a=rnorm(5), b=sample(letters, 5, replace=TRUE), stringsAsFactors = FALSE)
    > sqlSave(dbhandle, df, tablename = 'jal_test1', append=TRUE, fast=FALSE, verbose=TRUE)
    Query: INSERT INTO "jal_test1" ( "rownames", "a", "b" ) VALUES ( '1', -1.45261402, 'd' )
    Query: INSERT INTO "jal_test1" ( "rownames", "a", "b" ) VALUES ( '2', -0.01642518, 'm' )
    Query: INSERT INTO "jal_test1" ( "rownames", "a", "b" ) VALUES ( '3',  1.11767938, 'm' )
    Query: INSERT INTO "jal_test1" ( "rownames", "a", "b" ) VALUES ( '4', -0.63480166, 'a' )
    Query: INSERT INTO "jal_test1" ( "rownames", "a", "b" ) VALUES ( '5', -0.75538702, 'k' )
    > sqlSave(dbhandle, df, tablename = 'jal_test1', append=TRUE, fast=TRUE, verbose=TRUE)
    Query: INSERT INTO "jal_test1" ( "rownames", "a", "b" ) VALUES ( ?,?,? )
    Binding: 'rownames' DataType -9, ColSize 255
    Binding: 'a' DataType 6, ColSize 17
    Binding: 'b' DataType -9, ColSize 255
    Parameters:
    no: 1: rownames 1/***/no: 2: a -1.45261/***/no: 3: b d/***/
    no: 1: rownames 2/***/no: 2: a -0.0164252/***/no: 3: b m/***/
    no: 1: rownames 3/***/no: 2: a 1.11768/***/no: 3: b m/***/
    no: 1: rownames 4/***/no: 2: a -0.634802/***/no: 3: b a/***/
    no: 1: rownames 5/***/no: 2: a -0.755387/***/no: 3: b k/***/
    

3 个答案:

答案 0 :(得分:1)

当尝试解决为什么花较长时间从Pandas(通过reticulate导入到R)中写入〜5000行数据帧的问题时,我找到了这个答案,并想发布更新。

自版本0.24.0(2019年1月)开始,pd.DataFrame.to_sql中有一个method参数,当您设置method = 'multi'时似乎实际上在进行合理的分块。比较两种将数据帧写到Redshift的方法,我看到了大约 50倍的加速。

# Called from R, takes ~500 seconds for a 5000 row data frame
write_result <- pd_df$to_sql( name=tablename
    , con=conn
    , index = FALSE
    , if_exists = if_exists
    , schema='my_schema'
    , chunksize=1000L)

# Takes ~10 seconds for the same 5000 row data frame.
write_result <- pd_df$to_sql( name=tablename
    , con=conn
    , index = FALSE
    , if_exists = if_exists
    , schema='my_schema'
    , chunksize=1000L
    , method='multi') # this is the new argument

答案 1 :(得分:0)

建议使用非S3选项进行批量加载:

  1. 来自Amazon EMR的COPY
  2. 来自远程主机(SSH)的COPY
  3. 来自Amazon DynamoDB的COPY
  4. https://docs.aws.amazon.com/redshift/latest/dg/copy-parameters-data-source.html

答案 2 :(得分:0)

我最终无法找到一个可以在R中进行分块的SQL写入函数的实现。但是我确实在Python中看到与sqlalchemy组合在一起的pandas包很容易做到这一点。所以我在一些R代码中掏出了Reticulate并包装了Python来创建一个写入redshift的函数。看起来像是矫枉过正,但它完成了工作,我不必重新实现任何东西:

start_python <- function(){
  library(reticulate)
  use_condaenv( "r-reticulate")
  pd <- import('pandas')
  sa <- import('sqlalchemy')
}

# write a table to RDW sandbox
write_to_redshift <- function(df, tablename, if_exists = 'append'){
  pd_df <- r_to_py(df)
  eng = sa$create_engine('postgres://user:pwd@redshift_name:5439/db_name')
  conn = eng$connect()
  write_result <- pd_df$to_sql( name=tablename, con=conn,  index = FALSE, if_exists = if_exists, schema='my_schema', chunksize=10000L)
  conn$close()
  return(write_result)
}