使用JSON数据类型的嵌套记录数组查询组合

pac*_*aco 5 sql postgresql json postgresql-9.3

我正在开发一个利用Postgres JSON数据类型的Rails应用程序.我在一个名为data的表中调用了一个JSON列reports.假设我有多个这样的条目:

Entry 1: {"objects":[{"album": 1, "src":"fooA.png", "pos": "top"},   {"album": 2, "src":"barB.png", "pos": "top"}],   "background":"background.png"}
Entry 2: {"objects":[{"album": 1, "src":"fooA.png", "pos": "top"},   {"album": 2, "src":"barC.png", "pos": "top"}],   "background":"bacakground.png"}
Entry 3: {"objects":[{"album": 1, "src":"fooA.png", "pos": "middle"},{"album": 2, "src":"barB.png", "pos": "middle"}],"background":"background.png"}
Entry 4: {"objects":[{"album": 1, "src":"fooA.png", "pos": "top"},   {"album": 3, "src":"barB.png", "pos": "top"}],   "background":"backgroundA.png"}
Run Code Online (Sandbox Code Playgroud)

我想要做的是返回具有相同相册,src和背景的条目的不同组合(注意:在objects节点内,数组元素的顺序无关紧要).例如,查询应将条目1,3作为一个组匹配,条目2作为另一个组,等等.目标是找到前3个最常见的组合.我知道如何使用Ruby来做这件事,但我必须查询大量的条目样本,然后遍历所有条目.如果它可以处理这个任务,使用Postgres似乎更有效.我不足以让SQL专家知道这是否可行.

这是我正在寻找的结果.在objects条目中,条目1和3都包含{"album": 1, "src":"fooA.png"}, {"album": 2, "src":"barB.png"},以及两者都匹配backgrounds.我想将它们组合为一个计数为2的组合.

由于条目2与此条件下的任何条目都不匹配,因此它是另一个计数为1的组合.条目4也被认为是另一个计数为1的组合.因此,我所追求的结果将是:

ids  |  count
--------------
1,3  | 2
2    | 1
4    | 1
Run Code Online (Sandbox Code Playgroud)

要么

combinations                                                                                                                               | count
---------------------------------------------------------------------------------------------------------------------------------------------------
{"objects":[{"album": 1, "src":"fooA.png", "pos": "top"},  {"album": 2, "src":"barB.png", "pos": "top"}],  "background":"background.png"}  | 2
{"objects":[{"album": 1, "src":"fooA.png", "pos": "top"},  {"album": 2, "src":"barC.png", "pos": "top"}],  "background":"bacakground.png"} | 1
{"objects":[{"album": 1, "src":"fooA.png", "pos": "top"},  {"album": 3, "src":"barB.png", "pos": "top"}],  "background":"backgroundA.png"} | 1
Run Code Online (Sandbox Code Playgroud)

无论哪个更容易实现.

在我的实际数据,我还有其他的价值不仅仅是albumsrcJSON的内阵列中的objects节点.你会注意到我已经包括pos了这个案例.我只关心使用album,srcbackground值相匹配的连击.我希望忽略任何其他价值观.

注意

当我测试Erwin的解决方案时,我不断收到此错误,我知道原因:

ERROR:  cannot call json_populate_recordset on a nested object
Run Code Online (Sandbox Code Playgroud)

我的json值实际上有点复杂.例如:

{"objects":[{"album": 1, "src":"fooA.png", "pos": "top", filters: []},  {"album": 2, "src":"barB.png", "pos": "top", filters: []}
Run Code Online (Sandbox Code Playgroud)

显然,它filters是一个嵌套对象,不受支持json_populate_recordset.但是,如果没有简单的替代方案,我想我可以解决这个问题.再说一次,我认为这是可能的吗?

UPDATE

由于上面我的样本数据中的拼写错误(这是我的错),这个解决方案有点不完整.当错字被修复时,它的解决方案不起作用.在这里找到这种情况的答案.但是Erwin的解决方案仍然是对类似于上述情况的案例的回答.

Erw*_*ter 8

鉴于此表(您应该以这样的形式提供):

CREATE TABLE reports (rep_id int primary key, data json);
INSERT INTO reports (rep_id, data)
VALUES 
  (1, '{"objects":[{"album": 1, "src":"fooA.png", "pos": "top"},   {"album": 2, "src":"barB.png", "pos": "top"}],   "background":"background.png"}')
, (2, '{"objects":[{"album": 1, "src":"fooA.png", "pos": "top"},   {"album": 2, "src":"barC.png", "pos": "top"}],   "background":"bacakground.png"}')
, (3, '{"objects":[{"album": 1, "src":"fooA.png", "pos": "middle"},{"album": 2, "src":"barB.png", "pos": "middle"}],"background":"background.png"}')
, (4, '{"objects":[{"album": 1, "src":"fooA.png", "pos": "top"},   {"album": 3, "src":"barB.png", "pos": "top"}],   "background":"backgroundA.png"}')
;
Run Code Online (Sandbox Code Playgroud)

JSON记录众所周知,可翻译类型

使用json_populate_recordset()了unnesting记录"objects".该函数需要注册的行类型来定义结果列的名称和数据类型.出于本演示的目的或通常用于即席查询,模拟的临时表"objects"提供相同的:

CREATE TEMP TABLE obj(album int, src text, pos text);
Run Code Online (Sandbox Code Playgroud)

the top 3 most common combinations... of entries that have the same album, src, and background:

SELECT array_agg(r.rep_id) AS ids, count(*) AS ct
FROM   reports r
     , json_populate_recordset(null::obj, r.data->'objects') o
GROUP  BY r.data->>'background'
        , o.album
        , o.scr
ORDER  BY count(*) DESC
LIMIT  3;
Run Code Online (Sandbox Code Playgroud)

无论是否来自同一行,每个对象都是重要的.您没有定义如何准确处理它.因此,rep_id可以在阵列中多次弹出ids.添加DISTINCTarray_agg()折叠可能的重复项.在这种情况下,计数ct可以大于数组的长度ids.

需要Postgres 9.3用于JSON函数和运算符以及隐式JOIN LATERAL.

未知或不可翻译类型的JSON记录

json_array_elements()只是不需要json数组而不将结果转换为SQL行.相应地使用JSON运算符访问各个字段.

SELECT array_agg(r.rep_id) AS ids, count(*) AS ct
FROM   reports r
     , json_array_elements(r.data->'objects') o
GROUP  BY r.data->>'background'
        , o->>'album'
        , o->>'scr'
ORDER  BY count(*) DESC
LIMIT  3;
Run Code Online (Sandbox Code Playgroud)