使用 JQ 优化 JSON 非规范化 - 从 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数组的迭代,以及将columnnamescolumnvalues组合到地图中。

  • 对于迭代,我认为可以使用mapmap_values
  • 对于要映射的 2 个数组,我看到了from_entrieswith_entries函数,但无法使其工作。

周围有jq大师的建议吗?

以下帮助程序函数使用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}}

道德

如果解决方案看起来太复杂,它可能是。

最新更新