【问题标题】:Using jq to extract common prefixes in a JSON data structure使用 jq 提取 JSON 数据结构中的公共前缀
【发布时间】:2014-07-18 04:29:10
【问题描述】:

我有一个 JSON 数据集,其中包含从 Redis 存储中提取的大约 870 万个键值对,其中每个键都保证是一个 8 位数字,并且键是一个 8 个字母数字字符值,即

[{
"91201544":"INXX0019",
"90429396":"THXX0020",
"20140367":"ITXX0043",
 ...
}]

为了减少 Redis 内存使用,我想将其转换为哈希哈希,其中哈希前缀键是键的前 6 个字符(请参阅this link),然后将其存储回 Redis。

具体来说,我希望生成的 JSON 数据结构(然后我将编写一些代码来解析这个 JSON 结构并创建一个包含 HSET 等的 Redis 命令文件)看起来更像

[{
 "000000": { "00000023": "INCD1234",
             "00000027": "INCF1423",
              ....
           },
 ....
 "904293": { "90429300": "THXX0020",
             "90429302": "THXX0024",
             "90429305": "THXY0013"}
 }]

因为jq 给我留下了深刻的印象,并且我正在努力更精通函数式编程,所以我想使用 jq 来完成这项任务。到目前为止,我想出了以下几点:

% jq '.[0] | to_entries | map({key: .key, pfx: .key[0:6], value: .value}) | group_by(.pfx)'

这给了我类似的东西

[
  [
    {
      "key": "00000130",
      "pfx": "000001",
      "value": "CAXX3231"
    },
    {
      "key": "00000162",
      "pfx": "000001",
      "value": "CAXX4606"
    }
  ],
  [
    {
      "key": "00000238",
      "pfx": "000002",
      "value": "CAXX1967"
    },
    {
      "key": "00000256",
      "pfx": "000002",
      "value": "CAXX0727"
    }
  ],
  ....
]

我尝试了以下方法:

% jq 'map(map({key: .pfx, value: {key, value}})) 
      | map(reduce .[] as $item ({}; {key: $item.key, value: [.value[], $item.value]} )) 
      | map( {key, value: .value | from_entries} ) 
      | from_entries'

这确实给了我正确的结果,但也为每次减少(我相信)打印出一个错误

jq: error: Cannot iterate over null

最终结果是

{
   "000001": {
     "00000130": "CAXX3231",
     "00000162": "CAXX4606"
   },
   "000002": {
     "00000238": "CAXX1967",
     "00000256": "CAXX0727"
   },
   ...
}

这是正确的,但我怎样才能避免同时抛出这个 stderr 警告?

【问题讨论】:

    标签: json jq


    【解决方案1】:

    我不确定这里是否有足够的数据来评估问题的根源。我发现很难相信你尝试的结果是这样的。我一直遇到错误。

    试试这个过滤器:

    .[0]
        | to_entries
        | group_by(.key[0:6])
        | map({
              key:   .[0].key[0:6],
              value: map(.key=.key[6:8]) | from_entries
          })
        | from_entries
    

    给出如下所示的数据:

    [{
        "91201544":"INXX0019",
        "90429396":"THXX0020",
        "20140367":"ITXX0043",
        "00000023":"INCD1234",
        "00000027":"INCF1423",
        "90429300":"THXX0020",
        "90429302":"THXX0024",
        "90429305":"THXY0013"
    }]
    

    结果如下:

    {
      "000000": {
        "23": "INCD1234",
        "27": "INCF1423"
      },
      "201403": {
        "67": "ITXX0043"
      },
      "904293": {
        "00": "THXX0020",
        "02": "THXX0024",
        "05": "THXY0013",
        "96": "THXX0020"
      },
      "912015": {
        "44": "INXX0019"
      }
    }
    

    【讨论】:

    • 这是使用 jq-1.4,但是是的,你的方法肯定更好。我缺少的一点是,您可以通过在map 中使用map 将数组 一层,即map({key: .[0].key[0:6], value: map(.key=.key[6:8]) | from_entries})
    • 是的,我无法让您的过滤器工作here。据说他们使用的是 1.4。
    • 我在第一次提取时遇到了一个错误,我现在已经修复了,应该是key[0:6],而不是key[0-6]。 jqplay.com 上确实出现了问题是尝试使用reducejqplay 只是打印出我在命令行上遇到的相同错误,jq: error: Cannot iterate over null。当我在真正的命令行环境中运行它时,jq 将打印该错误但继续并成功打印出结果。
    • 实际上你编辑的部分(虽然仍然需要更正)我理解你想要做什么。那不是我看到的问题。您尝试的第二个我无法上班。甚至传入第一个过滤器的结果。你还有原来的过滤器吗? jqplay.org 只显示错误。
    • 是的,我终于在使用 jqplay.org 时完成了这个工作(+1 向我展示了这一点!)这是我最终将完整过滤器更改为:.[0] | to_entries | map({key: .key, pfx: .key[0:6], value: .value}) | group_by(.pfx) | map(map({key: .pfx, value: {key, value}})) | map(reduce ( .[] | iterables) as $item ({}; {key: $item.key, value: [$item.value, if (.value | iterables) then .value[] else . end]} )) | map( {key, value: .value | from_entries} ) | from_entries。但显然你的解决方案要简单得多,也不那么复杂,所以我会使用它。
    【解决方案2】:

    我知道这不是您要求的,但仅供参考,我认为使用 Redis 的内置 Lua 脚本执行此操作会更快。

    事实证明它更简单:

    for _,key in pairs(redis.call('keys', '*')) do
      local val = redis.call('get', key)
      local short_key = string.sub(key, 0, -2)
      redis.call('hset', short_key, key, val)
      redis.call('del', key)
    end
    

    这将在原地完成,无需从/向 Redis 传输和从/向 JSON 转换。

    从控制台运行它:

    $ redis-cli eval "$(cat script.lua)" 0
    

    【讨论】:

    • 你是对的。在我的 2.xGHz 笔记本电脑上使用 lua 确实可以在大约 5 1/2 分钟内更快地工作。原始数据库中有 8MM+ 键。使用提取到 .rdb,然后 rdb 到 json,使用 jq 处理 json,然后使用 redis-cli --pipe 重新执行总共大约需要 11 分钟。
    【解决方案3】:

    作为记录,jq 的group_by 依赖于排序,当输入足够大时,这当然会显着减慢速度。即使输入数组只有 100,000 个项目,以下代码也快了大约 40%:

    def compress:
      . as $in
      | reduce keys[] as $key ({};
          $key[0:6] as $k6
          | $key[6:] as $k2
          | .[$k6] += {($k2): $in[$key]} );
    
    .[0] | compress
    

    鉴于 Jeff 的输入,输出是相同的。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2018-10-02
      • 2021-12-06
      • 1970-01-01
      • 2012-02-01
      • 2014-10-13
      • 2021-09-11
      • 1970-01-01
      相关资源
      最近更新 更多