Как преобразовать вложенные JSON-данные с помощью Kiba-ETL? - PullRequest
0 голосов
/ 03 июля 2019

Я хочу преобразовать вложенные JSON-данные в реляционные таблицы с помощью Kiba-ETL. Вот упрощенная псевдо-JSON-полезная нагрузка:

{
  "bookings": [
    {
      "bookingNumber": "1111",
      "name": "Booking 1111",
      "services": [
        {
          "serviceNumber": "45",
          "serviceName": "Extra Service"
        }
      ]
    },
    {
      "bookingNumber": "2222",
      "name": "Booking 2222",
      "services": [
        {
          "serviceNumber": "1",
          "serviceName": "Super Service"
        },
        {
          "serviceNumber": "2",
          "serviceName": "Bonus Service"
        }
      ]
    }
  ]
}

Как я могу преобразовать эту полезную нагрузку в две таблицы:

  • Бронирование
  • услуг (каждая услуга принадлежит к бронированию)

Я читал о получении нескольких строк с помощью Kiba::Common::Transforms::EnumerableExploder в вики, блоге, и т. Д.

Решите ли вы мой сценарий использования, указав несколько строк (бронирование и несколько служб), или внедрите Destination, который получает целое бронирование и вызывает некоторые поднаправления (например, для создания или обновления службы)

1 Ответ

1 голос
/ 03 июля 2019

Автор Киба здесь!

Это обычное требование, но оно может (и не относится к Кибе) быть более или менее сложным в обращении. Вот несколько моментов, о которых вам нужно подумать.

Обработка внешних ключей

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

Внешние ключи с использованием бизнес-ключей

Первый (самый простой) способ справиться с этим - использовать ограничение по внешнему ключу для «номера бронирования» и обязательно указывать этот номер бронирования в каждой строке обслуживания, чтобы вы могли использовать его позже в своих запросах. , Если вы сделаете это (см. https://stackoverflow.com/a/18435114/20302), вам нужно будет установить уникальное ограничение на «номер бронирования» в целевой таблице заказов.

Внешние ключи с использованием первичных ключей

Если вместо этого вы предпочитаете иметь booking_id, который указывает на ключ bookings table id, все немного сложнее.

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

transform do |r|
  @row_index ||= 0
  @row_index += 1
  r.merge(id: @row_index)
end

Если это не разовый импорт, вам необходимо: * Upsert бронирования в первый проход * Во втором проходе ищите (через SQL-запросы) «заказы», ​​чтобы выяснить, что id хранить в booking_id, а затем запустите службы

Как вы видите, это немного больше работы, поэтому придерживайтесь варианта 1, если у вас нет строгих требований по этому поводу (хотя вариант 2 более устойчив в долгосрочной перспективе).

Пример реализации (с использованием Kiba Pro и бизнес-ключей)

Самый простой способ добиться этого (при условии, что ваша цель - Postgres) - это использовать назначение массовой вставки / переноса в Kiba Pro .

Это будет так (за один проход):

extend Kiba::DSLExtensions::Config
config :kiba, runner: Kiba::StreamingRunner

source Kiba::Common::Sources::Enumerable, -> { Dir["input/*.json"] }

transform { |r| JSON.parse(IO.read(r)).fetch('bookings') }

transform Kiba::Common::Transforms::EnumerableExploder

# SNIP (remapping / renaming of fields etc)

first_destination = nil

destination Kiba::Pro::Destinations::SQLBulkInsert,
  row_pre_processor: -> (row) { row.except("services") },
  dataset: -> (dataset) {
    dataset.insert_conflict(target: :booking_number)
  },
  after_read: -> (d) { first_destination = d }

destination Kiba::Pro::Destinations::SQLBulkInsert,
  row_pre_processor: -> (row) { row.fetch("services") },
  dataset: -> (dataset) {
    dataset.insert_conflict(target: :service_number)
  },
  before_flush: -> { first_destination.flush }

Здесь мы перебираем каждый входной файл, анализируем его и собираем «резервирования», затем генерируем по одной строке на элемент «резервирования».

У нас есть 2 пункта назначения, выполняющих «upsert» (вставка или обновление), плюс один трюк, чтобы мы сохранили родительские строки перед тем, как вставить дочерние, чтобы избежать сбоя из-за пропущенной остроконечной записи.

Конечно, вы можете реализовать это самостоятельно, но это немного работы!

Если вам нужно использовать внешние ключи на основе первичного ключа, вам придется (вероятно) разделить на 2 прохода (по одному для каждого адресата), а затем добавить некоторую форму поиска в середине.

Заключение

Я знаю, что это не тривиально (в зависимости от того, что вам нужно, и будете ли вы использовать Kiba Pro или нет), но, по крайней мере, я делюсь шаблонами, которые я использую в таких ситуациях.

Надеюсь, это немного поможет!

...