在Postgres 9.4中查找JSON数组中的最后一项

cer*_*ice 7 postgresql json postgresql-9.3

我们有一个遗留系统,试图跟踪特定文档保存的所有数据版本.我们最初将JSON作为字符串存储在一些旧版本的Postgres中,但最近我们升级到Postgres 9.3并且我们开始使用JSON列类型.

我们有一个名为"versions"的列,它有一个数组,每个保存的特定文档版本都存储在数组中,所以这样的查询:

SELECT _data_as_json FROM measurements WHERE id = 3307551
Run Code Online (Sandbox Code Playgroud)

像这样返回JSON:

 {"reports": {}, "versions": [
 {"timestamp": "2014-04-28T19:12:31.567415", "user": 11327, "legacy": {}, "vd_version": 1}, 
 {"timestamp": "2014-05-12T18:03:24.417029", "user": 11331, "legacy": {"lengthmoment": {"moment": {"size": 130}, "length": {"in": 64.0}}, "comments": "", "custom": null}, "vd_version": 1}, 
 {"timestamp": "2014-05-12T21:52:50.045758", "user": 10373, "legacy": {"lengthmoment": {"moment": {"size": 130}, "length": {"in": 64.0}}, "comments": "", "custom": null}, "vd_version": 1}, 
 {"timestamp": "2014-05-14T23:34:37.797822", "user": 10380, "legacy": {"lengthmoment": {"moment": {"size": 130}, "length": {"in": 64.0}}, "comments": "", "custom": null}, "vd_version": 1}, 
 {"timestamp": "2014-07-16T14:56:38.667363", "user": 10374, "legacy": {"lengthmoment": {"moment": {"size": 130}, "length": {"in": 64.0}}, "comments": "", "custom": null}, "vd_version": 1}, 
 {"timestamp": "2014-07-16T14:57:47.341541", "user": 10374, "legacy": {"lengthmoment": {"moment": {"size": 130}, "length": {"in": 64.0}}, "comments": "", "custom": null}, "vd_version": 1}, 
 {"timestamp": "2014-07-17T16:32:09.067026", "user": 11331, "legacy": {"lengthmoment": {"moment": {"size": 130}, "length": {"in": 64.0}}, "comments": "", "custom": null}, "vd_version": 1}, 
 {"timestamp": "2014-09-11T14:35:44.436886", "user": 11331, "legacy": {"lengthmoment": {"moment": {"size": 130}, "length": {"in": 64.0}}, "comments": "", "custom": null}, "vd_version": 1}, 
 {"timestamp": "2014-10-15T14:30:50.554932", "user": 10383, "legacy": {"lengthmoment": {"moment": {"size": 130}, "length": {"in": 64.0}}, "comments": "", "custom": null}, "vd_version": 1}, 
 {"timestamp": "2014-10-29T15:36:35.183787", "user": 11331, "legacy": {"lengthmoment": {"moment": {"size": 130}, "length": {"in": 64.0}}, "comments": "", "custom": null}, "vd_version": 1}, 
 {"timestamp": "2014-11-12T22:22:03.892484", "user": 10373, "legacy": {"lengthmoment": {"moment": {"size": 130}, "length": {"in": 64.0}}, "comments": "", "custom": null}, "vd_version": 1}
 ]}     
Run Code Online (Sandbox Code Playgroud)

我们(尝试)按时间顺序将数据存储在"版本"中,但99%的时间,我们只需要最后一个文档.在Postgres 9.3中,我们想出了这个查询来获取最后一项:

SELECT json_array_elements(_data_as_json->'versions')
FROM measurements
WHERE id = 3307551
LIMIT 1 OFFSET (SELECT json_array_length(_data_as_json->'versions') - 1 FROM measurements WHERE id = 3307551)
Run Code Online (Sandbox Code Playgroud)

这基本上有效,但有点脆弱.如果我们无法在版本数组中正确排序,那么我们会找回错误的文档版本.我很好奇是否有更好的方法来做到这一点?我已经读过Postgres 9.4提供了更多处理JSON的功能.

理想情况下,我们可以在"时间戳"上进行ORDER BY.那可能吗?

Erw*_*ter 18

Postgres 9.5+

现在这项工作很简单,引用手册:

接受整数JSON数组下标的字段/元素/路径提取运算符都支持从数组末尾开始的负数下标.

大胆强调我的.所以对于json或者jsonb:

SELECT data->'versions'->>-1
FROM   measurements m
WHERE  id = 3307551;
Run Code Online (Sandbox Code Playgroud)

Postgres 9.4

您可能想要使用jsonb而不是json.使用jsonb_array_elements()jsonb_array_length()相应.

有一种通用方法可以使用原始排序顺序获取最后一个元素WITH ORDINALITY(稍微慢一点):

SELECT v.ver
FROM   measurements m
     , jsonb_array_elements(m.data->'versions') WITH ORDINALITY v(ver, ord)
WHERE  m.id = 3307551
ORDER  BY v.ord DESC
LIMIT  1;
Run Code Online (Sandbox Code Playgroud)

WITH ORDINALITY(以及JOIN LATERAL两个版本中隐含的)的详细信息:

Postgres 9.3

根据时间戳值"last":

SELECT v.ver
FROM   measurements m
     , json_array_elements(m.data->'versions') v(ver)
WHERE  m.id = 3307551
ORDER  BY  (v.ver->>'timestamp')::timestamp DESC
LIMIT  1;
Run Code Online (Sandbox Code Playgroud)

根据json数组中的序号位置"更新" (更快):

SELECT data->'versions'->(json_array_length(data->'versions') - 1)
FROM   measurements
WHERE  id = 3307551;
Run Code Online (Sandbox Code Playgroud)

我们需要- 1因为JSON数组从偏移量0开始.

db <> 在这里摆弄
SQL小提琴.