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

Posted

技术标签:

【中文标题】在 Postgres 9.4 中查找 JSON 数组中的最后一项【英文标题】:Find the last item in a JSON array in Postgres 9.4 【发布时间】:2015-02-17 13:35:43 【问题描述】:

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

我们有一个名为“versions”的列,它包含一个数组,特定文档的每个保存版本都存储在数组中,因此查询如下:

SELECT _data_as_json FROM measurements WHERE id = 3307551

这样返回 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
 ]     

我们(尝试)将数据按时间顺序存储在“版本”中,但 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)

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

理想情况下,我们可以在“时间戳”上执行 ORDER BY。那可能吗?

【问题讨论】:

您可以使用 Postgres 9.4 吗?今天已经发布了,赞!除了 json(和 jsonb)的更多功能之外,它还提供了一个非常适合您的新功能:WITH ORDINALITY。另外,请定义“最后一个文档”:最后是根据数组位置还是根据时间戳值? 【参考方案1】:

Postgres 9.5+

现在工作很简单,quoting the manual:

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

我的大胆强调。所以对于jsonjsonb

SELECT data->'versions'->>-1
FROM   measurements m
WHERE  id = 3307551;

Postgres 9.4

您可能想使用jsonb instead of json。相应地使用jsonb_array_elements() or 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;

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

PostgreSQL unnest() with element number

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;

“last”根据json数组中的序号位置(更快):

SELECT data->'versions'->(json_array_length(data->'versions') - 1)
FROM   measurements
WHERE  id = 3307551;

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

db小提琴hereSQL Fiddle.

【讨论】:

以上是关于在 Postgres 9.4 中查找 JSON 数组中的最后一项的主要内容,如果未能解决你的问题,请参考以下文章

在 Postgres JSON 数组中查询

如何在 postgres 9.4 中删除复制槽

在 postgres 9.4 中使用触发器执行外部程序

在较旧的 Postgres (9.4) 上使用 Django 3+

在 Postgres 9.4+ 中索引 JSONB 嵌入式 Ecto2 模型

如何安装/更新到 Postgres 9.4?