PostgreSQL - transform varchar set to json - postgresql

One of the columns from my table contains varchar data in the form:
{"item1", "item2", "item3"}. The number of elements may vary, the column is also nullable.
I would like to change the type of this column to json. After the change, the data should be in the format:
[{"name": "SOME_NAME", "item": "item1"}, {"name": "SOME_NAME", "item": "item2"}, {"name": "SOME_NAME", "item": "item3"}]
Value SOME_NAME is fixed and will be the same for all elements.
So having:
+-----------------+
| items (varchar) |
+-----------------+
| {item1,item2} |
| null |
| {item3,item4} |
+-----------------+
I'd like to have a new column with:
+----------------------------------------------------------------------------------+
| new_items (json) |
+----------------------------------------------------------------------------------+
| [{"name": "SOME_NAME", "item": "item1"}, {"name": "SOME_NAME", "item": "item2"}] |
| null |
| [{"name": "SOME_NAME", "item": "item3"}, {"name": "SOME_NAME", "item": "item4"}] |
+----------------------------------------------------------------------------------+
I tried with json_build_object() and similar json functions, but I didn't get the intended effect.

I believe you can get your data back as array of text with
with first_sel as (
select '{"item1", "item2", "item3"}' col_text
)
select col_text::text[] from first_sel;
result
col_text
---------------------
{item1,item2,item3}
Get them in different rows with the unnest
with first_sel as (
select '{"item1", "item2", "item3"}' col_text
)
select unnest(col_text::text[]) from first_sel;
Result
unnest
--------
item1
item2
item3
Then you can build the single JSON entries with json_build_object
with first_sel as (
select '{"item1", "item2", "item3"}' col_text
),
list_of_rows as(
select unnest(col_text::text[]) items from first_sel
)
select
json_build_object('SOME_NAME',items) from list_of_rows
Result
json_build_object
-------------------------
{"SOME_NAME" : "item1"}
{"SOME_NAME" : "item2"}
{"SOME_NAME" : "item3"}
(3 rows)
and then aggregate them with json_agg
with first_sel as (
select '{"item1", "item2", "item3"}' col_text
),
list_of_rows as(
select unnest(col_text::text[]) items from first_sel
),
single_entries as (
select json_build_object('SOME_NAME',items) json_obj from list_of_rows)
select
json_agg(json_obj)
from single_entries;
Result
json_agg
-----------------------------------------------------------------------------
[{"SOME_NAME" : "item1"}, {"SOME_NAME" : "item2"}, {"SOME_NAME" : "item3"}]
Edit: since the subject of the original question now spans more rows. If you have a column id for which you can group by, then use that, otherwise you can:
Create a similar table with the following
create table test (col_text text);
insert into test values ('{"item1", "item2", "item3"}');
insert into test values (NULL);
insert into test values ('{"item4", "item5", "item6"}');
Use the ctid as group by information
with list_of_rows as(
select ctid id, unnest(col_text::text[]) items from test
),
single_entries as (
select id, json_build_object('SOME_NAME',items) json_obj from list_of_rows)
select
id,
json_agg(json_obj)
from single_entries
group by id;
result
id | json_agg
-------+-----------------------------------------------------------------------------
(0,1) | [{"SOME_NAME" : "item1"}, {"SOME_NAME" : "item2"}, {"SOME_NAME" : "item3"}]
(0,3) | [{"SOME_NAME" : "item4"}, {"SOME_NAME" : "item5"}, {"SOME_NAME" : "item6"}]
(2 rows)

Related

Postgres JSONB, query first appearance of inner field

Trying to query product_id and inner JSONB value "quantity_on_hand" with "unit:one". Below is example table
Products table
| product_id | data |
| -------- | -------------|
| 00445 | {"storage"...}| - rest of data specified right below
{
"storage": [
{
"unit": "one",
"quantity": 3,
},
{
"unit": "two",
"quantity": 2,
}
}
I found a query:
SELECT product_id, e -> 'quantity' as quant
FROM Products t,
jsonb_array_elements(t.value->'storage') e
WHERE (product_id) IN (('00445'));
The query returns following output:
product_id | quant
00445 | 3
00445 | 2
Please advise how to set rule: "quantity_on_hand" with "unit:one" to return only:
product_id | quant
00445 | 3
Thanks
You can add a clause for filtering the result of the jsonb_array_elements to only include elements where the JSON key "unit"'s value is "one":
SELECT product_id,
e -> 'quantity' AS quant
FROM Products t,
JSONB_ARRAY_ELEMENTS(t.value -> 'storage') e
WHERE (product_id) IN (('00445'))
AND e ->> 'unit' = 'one';
This should give:
product_id | quant
------------+-------
1 | 3
(1 row)
See https://www.postgresql.org/docs/14/functions-json.html for more information on JSONB operators and functions in Postgres.

how to get result as 'ARRAY of objects' in postgresql query?

I am selecting rows as
`SELECT
jsonb_build_object('platform_id',pl.id,'platform_Name',pl."name") as "platform" from
"platforms" as pl ON pl.id = up."platform_id"
WHERE usrs.id = ${userid}`
I am getting result as JSON
"platform": {
"platform_id": 5,
"platform_Name": "Snapchat"
}
but I want the platform result as "array of object " like below
DESIRED RESULT :
"platform": [{
"platform_id": 5,
"platform_Name": "Snapchat"
}]
How to make write query to get it as array ?
I recreated a similar example with:
create table platforms (id int, name text, user_id int);
insert into platforms values (1, 'snapchat',100),(2, 'fb', 100),(3, 'instagram', 200), (4, 'twitter',100);
The platforms table contains:
id | name | user_id
----+-----------+---------
1 | snapchat | 100
2 | fb | 100
3 | instagram | 200
4 | twitter | 100
(4 rows)
You can solve the problem in 2 steps:
create the json object with id and name using jsonb_build_object
aggregate over user_id using jsonb_agg
the query is
with create_doc as (
SELECT
user_id,
jsonb_build_object('platform_id',id,'platform_Name',name) as platform
from platforms
)
select user_id, jsonb_agg(platform) from create_doc group by user_id;
result
user_id | json_agg
---------+----------------------------------------------------------------------------------------------------------------------------------------------
200 | [{"platform_id": 3, "platform_Name": "instagram"}]
100 | [{"platform_id": 1, "platform_Name": "snapchat"}, {"platform_id": 2, "platform_Name": "fb"}, {"platform_id": 4, "platform_Name": "twitter"}]
(2 rows)

fetch 1 0r 0 for each records passed in where clause

I have a table with jsonb column datatype
+-----+--------------------------------------------------------------------------------+
| id | value |
+-----+--------------------------------------------------------------------------------+
| 1 | {"t1": "val", "value": [{"id": "1", "name": "abc"},{"id": "2", "name": "xyz"}] |
| 2 | {"t1": "val", "value": [{"id": "2", "name": "xyz"},{"id": "3", "name": "pqr"}] |
+-----+--------------------------------------------------------------------------------*
SELECT 'True' as status
FROM table t
where t.value->'value'->-1 -> 'id' IN ('"2"','"1"')
I want to fetch Ture or false along with each id checked in where clause.
Kindly help me in this query
You need to move the condition into the SELECT list, but you have to iterate over all array elements:
select t.id,
exists(select *
from jsonb_array_elements(t.value -> 'value') as v(item)
where item ->> 'id' in ('1','2')) as status
from the_table t;
With Postgres 12 or later, you can extract all IDs into an array and then use the ?| operator:
select t.id,
jsonb_path_query_array(t.value -> 'value', '$[*].id') ?| array['1','2'] as status
from the_table t;

How to remove element from postgres jsonb by key value?

I have a jsonb column with this format.
{
"categoryList": [{
"category_menu_id": "51",
"is_featured_product": 0
}, {
"category_menu_id": "54",
"is_featured_product": 1
}]
}
How to remove category by category_menu_id?
This select query is working fine by category_menu_id.
select product_category
from product
where product_category->'categoryList' #> '[{"category_menu_id": "51"}]';
Example data (note that I have added a primary key id to enable updating the table):
create table product(id int primary key, product_category jsonb);
insert into product values
(1,
'{
"categoryList": [{
"category_menu_id": "51",
"is_featured_product": 0
}, {
"category_menu_id": "54",
"is_featured_product": 1
}]
}');
This query skips the element with "category_menu_id": "51" from the json array:
select jsonb_build_object('categoryList', jsonb_agg(value))
from product,
jsonb_array_elements(product_category->'categoryList')
where value->>'category_menu_id' <> '51';
jsonb_build_object
--------------------------------------------------------------------------
{"categoryList": [{"category_menu_id": "54", "is_featured_product": 1}]}
(1 row)
Use the above query to update the table:
update product p
set product_category = (
select jsonb_build_object('categoryList', jsonb_agg(value))
from product ps,
jsonb_array_elements(product_category->'categoryList')
where ps.id = p.id -- important! primary key to identify a row
and value->>'category_menu_id' <> '51')
returning *;
id | product_category
----+--------------------------------------------------------------------------
1 | {"categoryList": [{"category_menu_id": "54", "is_featured_product": 1}]}
(1 row)

How do I return a custom array of objects and join some other tables?

I have asked way too many questions on jsonb and still feel a bit lost.
I have the following tables:
CREATE TABLE _data (
id serial PRIMARY KEY
, data jsonb
);
--with the following rows:
pk | data
---|------------------------
1 | {"year": 2012, "model": "honda"}
2 | {"year": 2014, "model": "toyota"}
CREATE TABLE _people (
pk serial PRIMARY KEY
user integer
, data integer
, updated timestamp without time zone
, documents jsonb
);
-- with the following rows:
pk | user | data| updated | documents
----|--------|-----|--------------------------|---------------
1 | 1 | 1 | 2015-08-22 16:05:40.76 | [{"type": "spreadsheet", "title": "mySpreadsheet", "length": 1278, "ignoredKey": "ignoreme"}, {"type": "document", "title": "My Nice Title"}]
2 | 1 | 1 | 2015-08-24 16:03:00 | [{"type": "spreadsheet", "title": "anothersheet", "length": 1400, "ignoredKey": "ignoreme"}, {"type": "document", "title": "here's another document"}]
CREATE TABLE _users (
pk serial PRIMARY KEY
, name text
);
-- with the following example row:
pk | name
-----|------
1 | Jim Bob
I am trying to get the following output (notice I am ignoring some keys in my documents...specifically "ignoredKey":
User | Data | Updated |Documents
---------|----------------------------------|---------------------------|------------
Jim Bob | {"year": 2012, "model": "honda"} |2015-08-22 16:05:40.764122 | [{"type": "spreadsheet", "title": "mySpreadsheet", "length": 1278}, {"type": "document", "title": "My Nice Title"}]
Jim Bob | {"year": 2014, "model": "toyota"} |2015-08-24 16:03:00 | [{"type": "spreadsheet", "title": "anothersheet", "length": 1400}, {"type": "document", "title": "here's another document"}]
I have:
SELECT p.pk, u.name, custom_docs, d.data FROM _people p,
jsonb_to_recordset(p.documents) doc(type text, title text, length numeric)
LEFT JOIN _data d ON p.data = d.pk
LEFT JOIN _users u ON p.user = u.pk
Which gives me the error:
ERROR: invalid reference to FROM-clause entry for table "p"
LINE 3: LEFT JOIN _data d ON p.data = d.pk
^
HINT: There is an entry for table "p", but it cannot be referenced from this part of the query.
EDIT #1:
As pointed below I need the "Documents" column that is created to be an array of objects...in the form of:
[{"type": "spreadsheet", "title": "mySpreadsheet", "length": 1278}, {"type": "document", "title": "My Nice Title"}]
Any other format is hard to use once it is retrieved.
EDIT #2:
Dmitry's answer helped get me further along but the "Documents" column contains ALL of the documents from all rows rather than those I need:
WITH docs AS (
SELECT array_agg(to_json(changed_structure)) as changed_json_array
FROM _people p,jsonb_to_recordset(p.documents) AS changed_structure(type text, title text, length numeric)
)
SELECT u.name,d.data,p.updated,docs.changed_json_array FROM docs,_people p
LEFT JOIN _users u ON u.pk = p.user
LEFT JOIN _data d ON d.pk = p.data;
This gives me:
name | data | updated | documents
------- | ----------------------------------| ---------------------------|
Jim Bob | {"year": 2012, "model": "honda"} | 2015-08-22 16:05:40.764122 | {"{\"type\":\"spreadsheet\",\"title\":\"mySpreadsheet\",\"length\":1278}","{\"type\":\"document\",\"title\":\"My Nice Title\",\"length\":null}","{\"type\":\"spreadsheet\",\"title\":\"anothersheet\",\"length\":1400}","{\"type\":\"document\",\"title\":\"here's another document\",\"length\":null}"}
Jim Bob | {"year": 2014, "model": "toyota"} | 2015-08-24-16:03:00 | {"{\"type\":\"spreadsheet\",\"title\":\"mySpreadsheet\",\"length\":1278}","{\"type\":\"document\",\"title\":\"My Nice Title\",\"length\":null}","{\"type\":\"spreadsheet\",\"title\":\"anothersheet\",\"length\":1400}","{\"type\":\"document\",\"title\":\"here's another document\",\"length\":null}"}
The key motive here is to use json_agg(to_json(doc)) from jsonb_to_recordset(p.documents):
select pk, json_agg(to_json(doc)) doc
from _people p, jsonb_to_recordset(p.documents) doc(type text, title text, length numeric)
group by 1
pk | doc
----+-----------------------------------------------------------------------------------------------------------------------------------
1 | [{"type":"spreadsheet","title":"mySpreadsheet","length":1278}, {"type":"document","title":"My Nice Title","length":null}]
2 | [{"type":"spreadsheet","title":"anothersheet","length":1400}, {"type":"document","title":"heres another document","length":null}]
(2 rows)
and appropriate use of the join:
select u.name, d.data, p.updated, s.doc
from _people p
left join _users u on u.pk = p.auser
left join _data d on d.pk = p.data
left join (
select pk, json_agg(to_json(doc)) doc
from _people p, jsonb_to_recordset(p.documents) doc(type text, title text, length numeric)
group by 1
) s on s.pk = p.pk
name | data | updated | doc
---------+----------------------------------+------------------------+-----------------------------------------------------------------------------------------------------------------------------------
Jim Bob | {"year": 2012, "model": "honda"} | 2015-08-22 16:05:40.76 | [{"type":"spreadsheet","title":"mySpreadsheet","length":1278}, {"type":"document","title":"My Nice Title","length":null}]
Jim Bob | {"year": 2012, "model": "honda"} | 2015-08-24 16:03:00 | [{"type":"spreadsheet","title":"anothersheet","length":1400}, {"type":"document","title":"heres another document","length":null}]
(2 rows)
Note: I had to change _users.user to _users.auser.
[changed]
WITH docs AS (
SELECT array_agg(to_json(changed_structure)) as changed_json_array
FROM _people p,jsonb_to_recordset(p.documents) AS changed_structure(type text, title text, length numeric)
)
SELECT u.name,d.data,p.updated,docs.changed_json_array FROM docs,_people p
LEFT JOIN _users u ON u.pk = p.user
LEFT JOIN _data d ON d.id = p.data;