I'm working on a Rails application that utilizes the Postgres JSON
data type. I have a JSON column called data
in a table called reports
. Let's say I have multiple entries like this:
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"}
What I would like to do is return the different combinations of entries that have the same album, src, and background (NOTE: within the objects
node, order of array elements does not matter). For instance, the query should match entries 1,3 as one group, entry 2, as another, and etc. The goal is to find the top 3 most common combinations. I know how to do this using Ruby, but I would have to query a large sample of entries, then iterate over all of them. It seems more efficient to use Postgres if it can handle this task. I'm not enough of a SQL expert to know if this is possible.
This is the result I am looking for. Within objects
, entries 1 and 3 both contain {"album": 1, "src":"fooA.png"}, {"album": 2, "src":"barB.png"}
, as well as both have matching backgrounds
. I would like to group them as one combination with a count of 2.
Since entry 2 does not match any entries under this criteria, then it is another combination with a count of 1. Entry 4 is also considered another combination with a count of 1. So the result I'm after would be:
ids | count
--------------
1,3 | 2
2 | 1
4 | 1
or
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
Whichever is easier to achieve.
In my actual data, I have values other than just album
and src
in the array of JSON within the objects
node. You'll notice that I've included pos
to show this case. I only care about using the album
, src
, and background
values to match the combos. I was hoping to ignore any other values.
Note
When I was testing Erwin's solution, I kept getting this error and I know why:
ERROR: cannot call json_populate_recordset on a nested object
My json values are are actually a little more complex. For example:
{"objects":[{"album": 1, "src":"fooA.png", "pos": "top", filters: []}, {"album": 2, "src":"barB.png", "pos": "top", filters: []}
Obviously, filters
is a nested object and is not supported by json_populate_recordset
. However, I think I can work around this if there is no simple alternative. Again, I assume this is possible?
UPDATE
Due to a typo in my sample data above (which was my fault), this solution is a bit incomplete. When the typo is fixed, it solution does not work. Find the answer to that situation here. But Erwin's solution is still an answer to cases similar to what was described above.
See Question&Answers more detail:
os