Как с помощью языка R и MongoDB выгрузить данные из источника со сложной структурой

Всем привет. Я работаю веб-аналитиком и часто строю отчеты для руководства, показываю прибыль и расходы в разных срезах, визуализирую данные и тому подобное. Сегодня расскажу о том, как решил классическую задачу: организовать регулярную выгрузку данных из CRM системы и внести их в базу данных. В моем случае речь идет о RetailCRM.

Задача

Первоначальный план заключался в том, чтобы получить все записи в R, объединить их в таблицу и загрузить в базу.

По ходу выполнения задачи столкнулся с такими проблемами:

  1. Записи заказов были представлены в виде сложной древовидной структуры с вложенными полями и списками.
  2. Пустое поле не видно в ответе. То есть у каждого заказа ряд общих полей, но при этом количество полученных полей у каждого заказа разное. Из-за этого оказалось сложно что-либо делать с полученным массивом.
  3. Скорость обработки всех заказов (а их около 47 000) могла составить от семи до девяти часов.

В поисках способов оптимизации решения наткнулся на упоминания о MongoDB.

MongoDB — документоориентированная система управления базами данных (СУБД) с открытым исходным кодом, не требующая описания схемы таблиц. Классифицирована как NoSQL, использует JSON-подобные документы и схему базы данных.

Решение

После того, как ознакомился с документацией и гайдом для R, решил попробовать. Установил базу, а затем — пакет mongolite и приступил.

  1. Записал первые 100 заказов.
library(jsonlite)library(httr)library(mongolite)host 
<- "http://<YOUR_DOMINE>.retailcrm.ru" # свой домен от RetailCRMtoken 
<- "<YOUR_TOKEN>" # свой токен от RetailCRMorder_number <- 1a 
<- content(GET(paste0(host, "/api/v5/orders", "?apiKey=", token, 
"&limit=100")))mdb <- mongo(collection = "test", url = 
'mongodb://localhost:27017/') #подключение к MongoDB
 'mongodb://username:password@localhost:27017/'for(s in
 1:length(a$orders)){ifelse( a$orders[[s]]$payments == 
"named list()", 0, names(a$orders[[s]]$payments) 
<- "paymants_info")mdb$insert(toJSON(a$orders[[s]]))order_number 
<- order_number + 1}test <- mdb$find('{}')View(test)

Важно: если вы используете учетную запись в MongoDB, путь подключения указывайте так: 'mongodb://username:password@localhost:27017/'

  1. Получил сформированную таблицу. Запись заняла чуть меньше пяти секунд, получение прошло ещё быстрее. При этом важно помнить: проблема заключалась в том, что не совпадало количество полей в каждом заказе. Здесь этой проблемы не было. Дело в том, что если в документе (в заказе) подобное поле отсутствует, возвращается NULL.

MongoDB позволяет фильтровать поля и количество возвращаемых строк на этапе запроса. Это делает работу намного удобнее и быстрее. Важная особенность — подобный формат значений в таблице:

Перед записью в реляционную базу необходимо перевести данные в общий тип. Если не проверять каждый столбец на тип данных, такой функции будет достаточно:

for(i in 1:length(colnames(test))){test[[i]] <- as.character(test[[i]])}

Что делать со сложной структурой?

Для решение первой проблемы (древовидная структура) пришлось повозится чуть больше. Под сложной структурой я понимаю записи такого вида:

Список покупок в рамках заказа

Для парсинга этих полей я пошел следующим путем:

  1. Написал функцию, которая получает на входе таблицу с полями «id» и «вложенные листы», раскрывает вложения и присваивает вложению «id» из соседней строчки:
ltdf <- function(x , f_name, col_number) {st3 
<- data.frame( "id" = as.character(x[[col_number]]))st3$row_id 
<- as.character(rownames(st3))tryCatch({st4 
<- ct(data.table::rbindlist(eval(parse(text = paste0("x$", f_name))), 
idcol = TRUE, fill = T )) }, error = function(x){})colnames(st4)
[ colnames(st4) == '.id'] <- "row_id"st4$row_id 
<- as.character(st4$row_id)results <- left_join(x =st4, 
y = st3, by = c("row_id"))return(results)}
  1. Получил из Mongo только нужные мне поля и применил данную функцию:
items <- c$find( fields = '{"id" : true ,"items.id" : 
true,"items.initialPrice" : true,"items.discountTotal" : 
true,"items.vatRate" : true,"items.quantity" : 
true,"items.status" : true,"items.purchasePrice" : 
true,"items.isCanceld" : true,"_id" : false}')items 
<- ltdf(items, "items", 1)
  1. Результат записал в реляционную базу данных.

По итогу, в базе появились таблички и возможность строить модель данных из вложенных таблиц.

Эту модель можно использовать для отчетов или других манипуляций.

Используя описанный метод, общее время получения данных (47к заказов), обработки и записи в реляционную базу сократилось до 30 минут. Поэтому рекомендую MongoDB (с использованием пакета mongolite) — точно упростите для себя парсинг записей со сложной структурой. Важно: в MongoDB просто писать данные, при этом система остается быстрой для CRUD-операций.

Узнайте больше
22
2
0
Обнаружили ошибку? Выделите ее и нажмите Ctrl + Enter.