Как запросить количество идентификаторов в пакетах в R - PullRequest
4 голосов
/ 03 февраля 2020

У меня есть упомянутый ниже фрейм данных в 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, но он выдает ошибку завершения R при передаче 10000 или более ID одновременно.

Как мне создать al oop и передать пакетный идентификатор для получения одного окончательного вывода для идентификатора 10000.

Сообщение об ошибке:

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

Ответы [ 4 ]

4 голосов
/ 07 февраля 2020

Передайте фрейм данных идентификатора во временную таблицу перед запросом SQL, а затем используйте его для внутреннего объединения идентификаторов, которые вы используете, таким образом вы можете избежать зацикливания. Все, что вам нужно сделать, это использовать dbWriteTable и установить параметр temporary = TRUE при его вызове.

EX:

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');")

Это должно повысить производительность вашего кода так же, как вы выиграли ' Мне больше не нужно l oop в R с помощью оператора where. Дайте мне знать, если он не работает должным образом.

1 голос
/ 12 февраля 2020
# 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.)
0 голосов
/ 13 февраля 2020

Возможно, просто попытка ...

После вышеприведенного комментария может быть ограничение размера в условии MySQL IN (...). Может быть, вы могли бы обойти его, разделив весь список dataframe$ID s в подсписке и переписав свой запрос с условием типа:

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"
0 голосов
/ 06 февраля 2020

Как показывают ссылки @ A.Suliman, это, скорее всего, связано с большим количеством значений в вашем предложении IN. Вот несколько решений, которые можно попробовать:

Пакетная обработка

Я фанат использования модуля для пакетного процесса. Предполагается, что значения идентификаторов, через которые вы выполняете пакетную обработку, равны цифрам c:

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)

В вашем случае выглядит, как идентификатор принимает форму 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)

Для справки я рекомендую документация о состоянии дел . Вы также можете найти этот вопрос при написании с использованием copy_to полезным для отладки.

Увеличение задержки времени ожидания

Когда в предложении IN много значений запрос становится намного медленнее, поскольку IN-предложение по существу транслируется в последовательность операторов OR.

Согласно этой ссылке вы можете изменить параметры времени ожидания для MySQL следующим образом:

  • Отредактируйте ваш my.cnf (MySQL файл конфигурации)
  • Добавьте конфигурацию тайм-аута и настройте ее в соответствии с вашим сервером.
    • wait_timeout = 28800
    • interactive_timeout = 28800
  • Перезагрузка MySQL
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...