Р: sqlAppendTable работает только с числами?

Я не могу заставить функцию DBI::sqlAppendTable языка R работать с чем-либо, кроме чисел. Ниже приведен небольшой код, иллюстрирующий проблему. Я подозреваю, что проблема в том, что sqlAppendTable не цитирует данные. Любое исправление или обходной путь будет принят с благодарностью.

num = data.frame(matrix(1:26, ncol=2))
let = data.frame(matrix(letters, ncol=2))

test.sqlAppendTable = function(dfr) {
    #dfr: A data frame.
    conx <- dbConnect(RSQLite::SQLite(), ":memory:")
    on.exit(dbDisconnect(conx))
    dbWriteTable(conx, "temp", dfr[1:5, ])
    temp = dbReadTable(conx, 'temp')
    print(temp)
    sat = sqlAppendTable(conx, 'temp', dfr[6:10, ])
    print(sat)
    rs = dbExecute(conx, sat)
    cat('Result set (rs): ')
    print(rs)
    temp = dbReadTable(conx, 'temp')
    print(temp)
}

test.sqlAppendTable(num) #Runs fine.
test.sqlAppendTable(let) #Generates error:
#Error in rsqlite_send_query(conn@ptr, statement) : no such column: j

person Argent    schedule 31.03.2017    source источник
comment
Почему бы не использовать dbWriteTable RSQLite, указав аргумент append?   -  person Parfait    schedule 01.04.2017
comment
dbWriteTable с добавлением звучит многообещающе.   -  person Argent    schedule 03.04.2017
comment
Однако в документации dbWriteTable я не вижу упоминания о добавлении: 127.0.0.1: 31912/library/DBI/html/dbReadTable.html   -  person Argent    schedule 03.04.2017
comment
Проверьте официальные документы CRAN, где дополняется является аргументом в методе dbWriteTable.   -  person Parfait    schedule 03.04.2017
comment
@Parfait: вы правы в том, что dbWriteTable имеет опцию добавления. Но см. мой пересмотренный ответ ниже: запись временной таблицы в базу данных и вставка ее в целевую таблицу выполняется быстрее.   -  person Argent    schedule 06.04.2017


Ответы (3)


Я сталкивался с этой проблемой слишком много раз, чтобы не попытаться написать свой собственный обходной путь. Лично я столкнулся с той же проблемой с Microsoft SQL Server, но решил, что это же решение будет работать и для SQLite. Я работаю с:

  • База данных: Microsoft SQL Server, размещенный в Azure.
  • R: 3.5.0
  • ДБИ: 1.0.0
  • одбк: 1.1.6
  • ОС: Убунту 18.04

Подход:

Я хотел избежать зацикливания строк ради эффективности. Я обнаружил, что mapply и paste0 можно комбинировать в более ориентированной на столбцы манере.

Я признаю, что это немного хакерски, но это хорошо работает для меня. Используйте на свой страх и риск; Я использую это только для небольшого побочного проекта, а не для корпоративного решения. В любом случае эффективность не должна быть такой большой проблемой, поскольку существует ограничение в 1000 строк в любом случае.

Замена для sqlAppendTable:

db_sql_append_table <- function(p_df, p_tbl) {
    # p_df: data.frame that contains the data to append/insert into the table
    # the names must be the same as those in the database
    # p_tbl: the name of the database table to insert/append into
    
    num_rows <- nrow(p_df)
    num_cols <- ncol(p_df)
    requires_quotes <- sapply(p_df, class) %in% c("character", "factor", "Date")
    commas <- rep(", ", num_rows)
    quotes <- rep("'", num_rows)
    
    str_columns <- ' ('
    column_names <- names(p_df)
    
    for(i in 1:num_cols) {
        if(i < num_cols) {
            str_columns <- paste0(str_columns, column_names[i], ", ")
        } else {
            str_columns <- paste0(str_columns, column_names[i], ") ")
        }
    }
    
    str_query <- paste0("INSERT INTO ", p_tbl, str_columns, "\nVALUES\n")   
    str_values <- rep("(", num_rows)
    
    for(i in 1:num_cols) {
        
        # not the last column; follow up with a comma
        if(i < num_cols) {
            if(requires_quotes[i]) {
                str_values <- mapply(paste0, str_values, quotes, p_df[[column_names[i]]], quotes, commas)        
            } else {
                str_values <- mapply(paste0, str_values, p_df[[column_names[i]]], commas)
            }
            
        # this is the last column; follow up with closing parenthesis
        } else {
            if(requires_quotes[i]) {
                str_values <- mapply(paste0, str_values, quotes, p_df[[column_names[i]]], quotes, ")")
            } else {
                str_values <- mapply(paste0, str_values, p_df[[column_names[i]]], ")")
            }
        }
    }
    
    # build out the query; collapse values with comma & newline; end with semicolon;
    str_values <- paste0(str_values, collapse=",\n")
    str_query <- paste0(str_query, str_values)
    str_query <- paste0(str_query, ";")
    return(str_query)
}

Вызов функции:

Я хотел, чтобы это было максимально похоже на исходную функцию sqlAppendTable. Эта функция только создает запрос.

Вам все еще нужно обернуть эту функцию вызовом dbExecute(), чтобы фактически вставлять/добавлять строки в базу данных.

dbExecute(conn=conn, statement = db_sql_append_table(my_dataframe, "table_name"))

РЕДАКТИРОВАТЬ

  • Добавлена ​​дата как один из типов, которые также должны быть заключены в кавычки этой функцией. Спасибо за этот комментарий!
person TaylorV    schedule 26.08.2018
comment
Отличный ответ/обходной путь - не могу поверить, что им до сих пор не удалось заставить этот INSERT нормально работать?! Я заметил, и в качестве будущей ссылки для всех, у кого есть эти проблемы, по крайней мере, с базами данных MS SQL также помогает добавить класс Date в строку кода 3, т.е. requires_quotes <- sapply(p_df, class) %in% c("character", "factor", "Date") - person GWD; 04.06.2020

Это похоже на недостаток пакета RSQLite; значения должны быть указаны в кавычках. Обратите внимание, что кавычки добавлены в следующем примере:

DBI::sqlAppendTable(DBI::ANSI(), table = "test", data.frame(a = 1, b = "2"))
#> <SQL> INSERT INTO "test"
#>   ("a", "b")
#> VALUES
#>   (1, '2')

См. также соответствующую ошибку GitHub.

person krlmlr    schedule 01.04.2017
comment
Спасибо, что подняли это на GitHub. - person Argent; 03.04.2017

Я смог собрать обходной путь:

1) Запишите фрейм данных, который вы хотите добавить в качестве временной таблицы (temp) в файл базы данных SQLite.

2) Добавьте его в целевую таблицу (цель) с помощью инструкции SQLite:

insert into target select * from temp;

3) Падение температуры.

Это работает довольно быстро, по-видимому, благодаря хорошей оптимизации SQLite.

ДОПОЛНЕНИЕ:

Вы действительно можете добавить фрейм данных в таблицу базы данных, используя dbWriteTable с параметром append=TRUE. Я протестировал это на моем обходном пути, описанном выше, и, что удивительно, обходной путь работает почти на 40% быстрее, чем dbWriteTable.

person Argent    schedule 02.04.2017