如何使用Kiba-ETL转换嵌套的JSON有效负载?

时间:2019-07-03 11:32:27

标签: ruby kiba-etl

我想用Kiba-ETL将嵌套的JSON有效负载转换为关系表。这是一个简化的伪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"
        }
      ]
    }
  ]
}

如何将有效负载转换为两个表:

  • 预订
  • 服务(每项服务都属于预订)

我在Wiki,博客等网站上阅读了Kiba::Common::Transforms::EnumerableExploder的帮助下产生多行的信息

您是通过产生多行(预订和多种服务)来解决我的用例,还是实施Destination来接收整个预订并调用某些子目的地(即创建或更新服务)?

1 个答案:

答案 0 :(得分:1)

木场的作者在这里!

这是一个常见的要求,但它可能(或并非特定于Kiba)变得或多或少复杂。这是您需要考虑的几点。

外键的处理

这里的主要问题是,一旦插入服务和预订,您将希望保持它们之间的关系。

使用业务密钥的外键

第一种(最简单的)处理方法是对“预订号”使用外键约束,并确保在每个服务行中插入该预订号,以便以后在查询中使用它。如果这样做(请参阅https://stackoverflow.com/a/18435114/20302),则必须在预订表目标中为“预订号”设置唯一约束。

使用主键的外键

如果您宁愿使用指向booking_idbookings键的id,则情况会更加复杂。

如果这是一次针对空白表的一次性导入,我建议您使用以下命令任意强制主键:

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

如果这不是一次性导入,则必须: *在第一阶段通过预订 *在第二遍中,(通过SQL查询)查找“预订”以找出要存储在id中的booking_id,然后对服务进行升序

如您所见,这还有很多工作要做,因此,如果您对此没有强烈要求,则坚持使用选项1(尽管从长远来看,选项2更可靠)。

示例实现(使用Kiba Pro和业务密钥)

实现此目标的最简单方法(假设您的目标是Postgres)是使用Kiba Pro的SQL Bulk Insert/Upsert destination

它会以这种方式(单次通过):

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 }

在这里,我们遍历每个输入文件,将其解析并获取“ bookings”,然后为“ bookings”的每个元素生成一行。

我们有2个目的地,进行“ upsert”(插入或更新),再加上一个技巧,以确保在插入子代之前保存父行,以避免由于缺少指向记录而导致失败。

您当然可以自己实现,但这还是有些工作!

如果您需要使用基于主键的外键,则必须(可能)分成两遍(每个目标一个),然后在中间添加某种形式的查找。

结论

我知道这并非易事(取决于您的需求,以及是否使用Kiba Pro),但是至少我正在分享在这种情况下使用的模式。 / p>

希望这会有所帮助!