Оптимизируйте денормализацию JSON, используя JQ - "декартово произведение" от 1:N

У меня есть журнал изменений базы данных JSON, вывод wal2json, Это выглядит так:

{"xid":1190,"timestamp":"2018-07-19 17:18:02.905354+02","change":[
    {"kind":"update","table":"mytable2","columnnames":["id","name","age"],"columnvalues":[401,"Update AA",20],"oldkeys":{"keynames":["id"],"keyvalues":[401]}},
    {"kind":"update","table":"mytable2","columnnames":["id","name","age"],"columnvalues":[401,"Update BB",20],"oldkeys":{"keynames":["id"],"keyvalues":[401]}}]}
 ...

Каждая запись верхнего уровня (xid) является транзакцией, каждый элемент в change это, ну, изменение. Одна строка может меняться несколько раз.

Чтобы импортировать в систему OLAP с ограниченным набором функций, мне нужно четко указать порядок. Так что мне нужно добавить sn за каждое изменение в транзакции.
Кроме того, каждое изменение должно быть записью верхнего уровня - OLAP не может перебирать подпункты внутри одной записи.

{"xid":1190, "sn":1, "kind":"update", "data":{"id":401,"name":"Update AA","age":20} }
{"xid":1190, "sn":2, "kind":"update", "data":{"id":401,"name":"Update BB","age":20} }
{"xid":1191, "sn":1, "kind":"insert", "data":{"id":625,"name":"Inserted","age":20} }
{"xid":1191, "sn":2, "kind":"delete", "data":{"id":625} }

(Причина в том, что OLAP имеет ограниченную способность преобразовывать данные во время импорта, а также не имеет порядок в качестве параметра.)

Итак, я делаю это с помощью jq:

function transformJsonDataStructure {
    ## First let's reformat it to XML, then transform using XPATH, then back to JSON.

    ## Example input:
    # {"xid":1074,"timestamp":"2018-07-18 17:49:54.719475+02","change":[
    #   {"kind":"update","table":"mytable2","columnnames":["id","name","age"],"columnvalues":[401,"Update AA",20],"oldkeys":{"keynames":["id"],"keyvalues":[401]}},
    #   {"kind":"update","table":"mytable2","columnnames":["id","name","age"],"columnvalues":[401,"Update BB",20],"oldkeys":{"keynames":["id"],"keyvalues":[401]}}]}
    cat "$1" | while read -r LINE ; do
        XID=`echo "$LINE" | jq -c '.xid'`;
        export SN=0;
        #serr "{xid: $XID, changes: $CHANGES}";
        echo "$LINE" | jq -c '.change[]' | while read -r CHANGE ; do
            SN=$((SN+=1))
            KIND=`echo "$CHANGE" | jq -c --raw-output .kind`;
            TABLE=`echo "$CHANGE" | jq -c --raw-output .table`;
            DEST_FILE="$TARGET_PATH-$TABLE.json";
            case "$KIND" in
                update|insert)
                    MAP=$(convertTwoArraysToMap "$(echo "$CHANGE" | jq -c ".columnnames")" "$(echo "$CHANGE" | jq -c ".columnvalues")") ;;
                delete)
                    MAP=$(convertTwoArraysToMap "$(echo "$CHANGE" | jq -c ".oldkeys.keynames")" "$(echo "$CHANGE" | jq -c ".oldkeys.keyvalues")") ;;
            esac
            #echo "{\"xid\":$XID, \"table\":\"$TABLE\", \"kind\":\"$KIND\", \"data\":$MAP }" >> "$DEST_FILE"; ;;
            echo "{\"xid\":$XID, \"sn\":$SN, \"kind\":\"$KIND\", \"data\":$MAP }" | tee --append "$DEST_FILE";
        done;
    done;

    return;
}

Проблема в производительности. я звоню jq несколько раз за запись. Это довольно медленно, примерно в 1000 раз медленнее, чем без преобразования.

Как можно выполнить преобразование выше, используя только один проход? (jq это не обязательно, можно использовать и другой инструмент, но он должен быть в пакетах CentOS. Я хочу избежать написания дополнительного инструмента для этого.

От man jq кажется, что он может быть способен обрабатывать весь файл (запись JSON на строку) за один раз. Я мог бы сделать это в XSLT, но не могу обернуть голову jq, Особенно итерация change массив и объединение columnnames а также columnvalues на карту.

  • Для итерации, я думаю, map или же map_values может быть использован.
  • Для отображения 2 массивов я вижу from_entries а также with_entries функции, но не могу заставить его работать.

любой jq мастер посоветовать?

1 ответ

Следующая вспомогательная функция преобразует входящий массив в объект, используя headers в качестве ключей:

def objectify(headers):
  [headers, .] | transpose | map({(.[0]): .[1]}) | add;

Хитрость теперь заключается в использовании range(0;length) чтобы генерировать .sn:

  {xid} +
  (.change
   | range(0;length) as $i
   | .[$i]
   | .columnnames as $header
   | {sn: ($i + 1),
      kind,
      data: (.columnvalues|objectify($header)) } )

Выход

Для данной записи журнала вывод будет:

{"xid":1190,"sn":1,"kind":"update","data":{"id":401,"name":"Update AA","age":20}}
{"xid":1190,"sn":2,"kind":"update","data":{"id":401,"name":"Update BB","age":20}}

моральный

Если решение выглядит слишком сложным, это, вероятно, так.

Другие вопросы по тегам