我正在使用Postgres JSON
数据类型的Rails应用程序。我在名为data
的表中有一个名为reports
的JSON列。让我们说我有多个这样的条目:
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"}
我想要做的是返回具有相同相册,src和背景的条目的不同组合(注意:在objects
节点内,数组元素的顺序无关紧要)。例如,查询应将条目1,3作为一个组匹配,条目2作为另一个组,等等。目标是找到前3个最常见的组合。我知道如何使用Ruby来做这件事,但我必须查询大量的条目样本,然后遍历所有条目。如果它可以处理这个任务,使用Postgres似乎更有效。我不足以知道这是否可行。
这是我正在寻找的结果。在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
或
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
无论哪个更容易实现。
在我的实际数据中,我在album
节点内的JSON数组中只有src
和objects
以外的值。您会注意到我已将pos
包括在内以显示此案例。我只关心使用album
,src
和background
值来匹配组合。我希望忽略任何其他价值观。
注意
当我测试Erwin's solution时,我一直收到此错误,我知道原因:
ERROR: cannot call json_populate_recordset on a nested object
我的json值实际上有点复杂。例如:
{"objects":[{"album": 1, "src":"fooA.png", "pos": "top", filters: []}, {"album": 2, "src":"barB.png", "pos": "top", filters: []}
显然,filters
是嵌套对象,json_populate_recordset
不支持。但是,如果没有简单的替代方案,我想我可以解决这个问题。我再次假设这是可能的吗?
更新
由于上面的示例数据中存在拼写错误(这是我的错),此解决方案有点不完整。当错字被修复时,它的解决方案不起作用。找到那种情况的答案here。但Erwin's solution仍然是对类似于上述情况的回答。
答案 0 :(得分: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"}')
;
使用json_populate_recordset()
取消记录集"objects"
。
该函数需要注册的行类型来定义结果列的名称和数据类型。出于本演示的目的或通常用于即席查询,在"objects"
之后建模的临时表提供相同的内容:
CREATE TEMP TABLE obj(album int, src text, pos text);
要查找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;
无论是否来自同一行,每个对象都很重要。您没有定义如何准确处理它。因此,rep_id
可以在数组ids
中多次弹出。将DISTINCT
添加到array_agg()
以折叠可能的重复项。在这种情况下,计数ct
可以大于数组ids
的长度。
要求Postgres 9.3用于JSON函数和运算符以及implicit JOIN LATERAL
。
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;