是否有一个函数可以让我输出json数组中元素的当前索引?

时间:2017-02-23 14:11:55

标签: sql json postgresql select postgresql-9.4

我有以下示例Postgres SQL查询。

SELECT name, json_array_elements(myjson)->>'id' id FROM
(SELECT 'Dingsda'::text AS name, json_build_array('{"id" : 100}'::json, '{"id" : 200}'::json) myjson
  UNION ALL
 SELECT 'Dingsbums'::text AS name, json_build_array('{"id" : 101}'::json, '{"id" : 201}'::json, '{"id" : 301}'::json) myjson
) t;

输出下表。

+-----------+-----+
|   name    | id  |
+-----------+-----+
| Dingsda   | 100 |
| Dingsda   | 200 |
| Dingsbums | 101 |
| Dingsbums | 201 |
+-----------+-----+

现在我想要实现的是在末尾放置第三列,其中包含以0开头的json数组元素的索引,依此类推。结果应如下所示。

+-----------+-----+-------+
|   name    | id  | index |
+-----------+-----+-------+
| Dingsda   | 100 |     0 |
| Dingsda   | 200 |     1 |
| Dingsbums | 101 |     0 |
| Dingsbums | 201 |     1 |
| Dingsbums | 301 |     2 |
+-----------+-----+-------+

不幸的是我没有找到能帮助我的功能。我尝试使用row_number() OVER进行试验,但如果涉及json类型的列,它似乎会返回奇怪的结果。

是否有任何功能可以支持这个相当简单的要求?

修改

Mureinik的答案很有帮助,但我希望能够保留json中元素的原始序列。在此示例中,序列丢失。我认为这不是确定性的。

SELECT name, id, ROW_NUMBER() OVER (PARTITION BY name ORDER BY 1)
FROM (SELECT name, JSON_ARRAY_ELEMENTS(myjson)->>'id' id 
      FROM   (SELECT 'Dingsda'::text AS name, JSON_BUILD_ARRAY('{"id" : 100}'::json, '{"id" : 200}'::json, '{"id" : 300}'::json, '{"id" : 400}'::json) myjson
          UNION ALL
          SELECT 'Dingsbums'::text AS name, JSON_BUILD_ARRAY('{"id" : 101}'::json, '{"id" : 201}'::json, '{"id" : 301}'::json) myjson
         ) t
      ) s;
+-----------+-----+-------+
|   name    | id  | index |
+-----------+-----+-------+
| Dingsbums | 101 |     1 |
| Dingsbums | 201 |     2 |
| Dingsbums | 301 |     3 |
| Dingsda   | 100 |     1 |
| Dingsda   | 400 |     2 |
| Dingsda   | 200 |     3 |
| Dingsda   | 300 |     4 |
+-----------+-----+-------+

3 个答案:

答案 0 :(得分:3)

永远不要在json_array_elements()子句中使用设置返回功能(例如SELECT),除非您知道自己正在做什么。它会让事情变得更难。

您可以使用表函数的WITH ORDINALITY子句(9.4+),这是用于您的问题:

SELECT name, elem->>'id' id, "index"
FROM (SELECT 'Dingsda'::text AS name, json_build_array('{"id" : 100}'::json, '{"id" : 200}'::json) myjson
        UNION ALL
      SELECT 'Dingsbums'::text AS name, json_build_array('{"id" : 101}'::json, '{"id" : 201}'::json, '{"id" : 301}'::json) myjson) t,
     json_array_elements(t.myjson) WITH ORDINALITY e(elem, "index");

您也可以将generate_series()json_array_length()一起使用,但WITH ORDINALITY更简单。

答案 1 :(得分:1)

使用row_number似乎是正确的方法。您只需要用另一个查询包装此查询以生成行号:

SELECT name, id, ROW_NUMBER() OVER (PARTITION BY name ORDER BY 1)
FROM (SELECT name, JSON_ARRAY_ELEMENTS(myjson)->>'id' id 
      FROM   (SELECT 'Dingsda'::text AS name, JSON_BUILD_ARRAY('{"id" : 100}'::json, '{"id" : 200}'::json) myjson
              UNION ALL
              SELECT 'Dingsbums'::text AS name, JSON_BUILD_ARRAY('{"id" : 101}'::json, '{"id" : 201}'::json, '{"id" : 301}'::json) myjson
             ) t
      ) s;

答案 2 :(得分:1)

您可以使用横向连接(无论如何推荐)和with ordinality

SELECT name, x.element ->> 'id' as id, x.idx
FROM (
  SELECT 'Dingsda'::text AS name, json_build_array('{"id" : 100}'::json, '{"id" : 200}'::json) myjson
  UNION ALL
  SELECT 'Dingsbums'::text AS name, json_build_array('{"id" : 101}'::json, '{"id" : 201}'::json, '{"id" : 301}'::json) myjson
) t
  cross join lateral json_array_elements(myjson) with ordinality as x(element, idx)