1

I have a postgres table something like this.

+----+-----------+----------------------+--------+
| id |    Key    |        Value         | userId |
+----+-----------+----------------------+--------+
|  1 | email     | [email protected]     |      1 |
|  2 | firstName | thomas               |      1 |
|  3 | lastName  | reggi                |      1 |
|  4 | email     | [email protected] |      1 |
+----+-----------+----------------------+--------+

I'm looking for a way to "reduce" this table down to a json object.

{
   "email": "[email protected]",
   "firstName": "thomas",
   "lastName": "reggi"
}

How close can I get to this with just using postgres?

2 Answers 2

4

If the table is called data, try this (jsonb_pretty is just for display purposes):

SELECT jsonb_pretty(
          jsonb_object_agg(key, value ORDER BY id)
       )
FROM data
WHERE userid = 1;

┌──────────────────────────────────────┐
│             jsonb_pretty             │
├──────────────────────────────────────┤
│ {                                   ↵│
│     "email": "[email protected]",↵│
│     "lastName": "reggi",            ↵│
│     "firstName": "thomas"           ↵│
│ }                                    │
└──────────────────────────────────────┘
(1 row)

This relies on the feature that jsonb does not keep duplicate keys.

It also relies on the fact that jsonb will always retain the last added key/value pair.

Sign up to request clarification or add additional context in comments.

4 Comments

It can be controlled, which keys to kept: jsonb_object_agg (just like any other aggregate function) accepts an ORDER BY clause, f.ex. jsonb_object_agg(key, value ORDER BY id) to preserve the key with highest id (the last added key-value pair will be kept)
Yes, but that means that you have to rely on an undocumented implementation detail in jsonb, namely that the last added pair will be kept. Maybe I am overly anxious. I'll add a few ideas to the answer to address the problem.
It is documented: If duplicate keys are specified in the input, only the last value is kept. -- even the json type have similar handling: if a JSON object within the value contains the same key more than once, all the key/value pairs are kept. (The processing functions consider the last value as the operative one.)
Great, thanks. I have improved the answer accorsing to that.
1

If you want to always have the latest value for a key, you could use a CTE and the RANK() window function:

SELECT * FROM p;
┌────┬───────────┬──────────────────────┬────────┬────────────────────────────┐
│ id │    key    │        value         │ userid │     modification_time      │
├────┼───────────┼──────────────────────┼────────┼────────────────────────────┤
│  1 │ email     │ [email protected]     │      1 │ 2016-10-05 12:53:32.936704 │
│  2 │ firstName │ thomas               │      1 │ 2016-10-05 12:53:32.936704 │
│  3 │ lastName  │ reggi                │      1 │ 2016-10-05 12:53:32.936704 │
│  4 │ email     │ [email protected] │      1 │ 2016-11-06 15:53:48.025775 │
└────┴───────────┴──────────────────────┴────────┴────────────────────────────┘
(4 rows)

WITH info_with_rank_for_user AS (
  SELECT userId,
         modification_time,
         value,
         key,
         RANK() OVER (PARTITION BY userId, key ORDER BY id DESC)
  FROM p
)
SELECT userId,
       json_object_agg(key, value),
       MAX(modification_time) AS last_settings_modification_time
FROM info_with_rank_for_user
WHERE rank = 1
GROUP BY userId
;
┌────────┬────────────────────────────────────────────────────────────────────────────────────┬─────────────────────────────────┐
│ userid │                                  json_object_agg                                   │ last_settings_modification_time │
├────────┼────────────────────────────────────────────────────────────────────────────────────┼─────────────────────────────────┤
│      1 │ { "email" : "[email protected]", "firstName" : "thomas", "lastName" : "reggi" } │ 2016-11-06 15:53:48.025775      │
└────────┴────────────────────────────────────────────────────────────────────────────────────┴─────────────────────────────────┘
(1 row)

2 Comments

This is perfect! I have another column on my table, any idea how to get that in as well? So the columns sho up userId, otherColumn, json_object_agg?
@ThomasReggi: It sort of depends on what otherColumn represents. I added an example where it's also something you want to aggregate (here using MAX()). If that's not the case add it to your question, it's a bit hard to help otherwise :)

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.