How to flatten multiple row values into concatenated string in postgres? - postgresql

I have a simple set of three tables . 2 "source" tables and a "join" table that allows one to many relationship.
I have this query:
select data.mph1.data ->> 'name' as name, data.mph1.data ->> 'tags' as Tags,
data.mph2.data ->> 'School' as school from data.mph1
join data.mph1tomph2 on data.mph1tomph2.mph1 = data.mph1.id
join data.mph2 on data.mph2.id = data.mph1tomph2.mph2
The output appears as:
Name Tags School
"Steve Jones" "["tag1", "tag2"]" "UMass"
"Steve Jones" "["tag1", "tag2"]" "Harvard"
"Gary Summers" "["java", "postgres", "flutter"]" "Yale"
"Gary Summers" "["java", "postgres", "flutter"]" "Harvard"
"Gary Summers" "["java", "postgres", "flutter"]" "UMass"
What I'm looking for is
Name Tags School
"Steve Jones" "["tag1", "tag2"]" "UMass", "Harvard"
"Gary Summers" "["java", "postgres", "flutter"]" "Yale, Harvard, UMass"
How would I get this result in single query? Possible?

Use the aggregate function string_agg()
select
data.mph1.data ->> 'name' as name,
data.mph1.data ->> 'tags' as tags,
string_agg(data.mph2.data ->> 'School', ', ') as school
from data.mph1
join data.mph1tomph2 on data.mph1tomph2.mph1 = data.mph1.id
join data.mph2 on data.mph2.id = data.mph1tomph2.mph2
group by data.mph1.id -- if id is a primary key
-- group by 1, 2 -- otherwise

Related

LEFT JOIN on table returns array with one element instead of empty array, when using json_build_object

I have the following query:
SELECT
u.*,
array_agg(
json_build_object(
'id', usn.id,
'schemaName', usn.schema_name
)
) AS schemas
FROM dev.users u
FULL OUTER JOIN dev.user_schema_names usn ON u.id = usn.user_id
WHERE u.email = $1
GROUP BY u.id
But for some odd reason, this returns the following:
{
id: 1,
email: 'test#test.com',
schemas: [ { id: null, schemaName: null } ]
}
The dev.user_schema_names has no records in it. I am expecting schemas to be an empty array []. If I insert records in dev.user_schema_names, then it works just fine, however.
What am I doing wrong? Should I be using something else instead of json_build_object?

Convert individual postgres jsonb array elements to row elements

I have to query a table with 2 columns, id and content. Id is just a uuid and the content column looks like
{
"fields": [
{
"001": "mig00004139229"
},
{
"856": {
"ind1": " ",
"ind2": " ",
"subfields": [
{
"u": "https://some.domain.com"
},
{
"z": "some text"
}
]
}
},
{
"999": {
"subfields": [
{
"i": "81be1acf-11df-4d13-a5c6-4838e3a808ee"
},
{
"s": "3a6aa357-8fd6-4451-aedc-13453c1f2296"
}
]
}
}
]
}
I need to select the id, 001, and 856 elements where the subfield "u" domain matches a string "domain.com" so the output would be
id
001
856
81be1acf-11df-4d13-a5c6-4838e3a808ee
mig00004139229
https://some.domain.com
If this were a flat table, the query would correspond with "select id, 001, 856 from table where 856 like '%domain.com%'"
I can select individual columns based on the criteria I need, but they appear in separate rows except the id which appears with any other individual field in a regular select statement. How would I get the other fields to appear in the same row since it's part of the same record?
Unfortunately, my postgres version doesn't support jsonb_path_query, so I've been trying something along the lines of:
SELECT id, jsonb_array_elements(content -> 'fields') -> '001',
jsonb_array_elements(content -> 'fields') -> '856' -> 'subfields'
FROM
mytable
WHERE....
This method returns the data I need, but the individual elements arrive on separate rows with the with the id in the first column and nulls for every element that is neither the 001 nor 856 e.g.
id
001
856
id_for_first_record
001_first_record
null
id_for_first_record
null
null
id_for_first_record
null
null
id_for_first_record
null
856_first_record
id_for_second_record
001_second_record
null
id_for_second_record
null
null
id_for_second_record
null
null
id_for_second_record
null
856_second_record
Usable, but clunky so I'm looking for something better
I think my query can help you. There are different ways to resolve this, I am not sure if this is the best approach.
I use jsonb_path_query() function with the path for the specified JSON value.
SELECT
id,
jsonb_path_query(content, '$.fields[*]."001"') AS "001",
jsonb_path_query(content, '$.fields[*]."856".subfields[*].u') AS "856"
FROM t
WHERE jsonb_path_query_first(content, '$.fields[*]."856".subfields[*].u' )::text ilike '%domain%';
Output:
id
001
856
81be1acf-11df-4d13-a5c6-4838e3a808ee
"mig00004139229"
"https://some.domain.com"
UPDATED: because of Postgresql version is prior to 12.
You could try something like this, but I think there must be a better approach:
SELECT
t.id,
max(sq1."001") AS "001",
max(sq2."856") AS "856"
FROM t
INNER JOIN (SELECT id, (jsonb_array_elements(content -> 'fields') -> '001')::text AS "001" FROM t) AS sq1 ON t.id = sq1.id
INNER JOIN (SELECT id, (jsonb_array_elements(jsonb_array_elements(content -> 'fields') -> '856' -> 'subfields') -> 'u')::text AS "856" FROM t) AS sq2 ON t.id = sq2.id
WHERE sq2."856" ilike '%domain%'
GROUP BY t.id;

How to use wildcard in the path to search jsonb values for postgres?

Using postgres version 10.13
This is my datatable jsongraphs
id
jsongraph
1
{ "data": {"scopes_by_id": { "121": { "id": 121, "pk": 121, "name": "Prework" } }, "commonsites_by_id": {"123": {"id": 123, "pk": 123, "name": "Somewhere over the rainbow"}}}}
2
{ "data": {"scopes_by_id": { "156": { "id": 156, "pk": 156, "name": "ABC" } }, "commonsites_by_id": {"123": {"id": 123, "pk": 123, "name": "Somewhere over the rainbow"}}}}
I want the distinct values of scope id and site id which should be (121, 123), (156,123)
So I tried
SELECT DISTINCT
jsongraph->'data'->'scopes_by_id'->>'pk' ,
jsongraph->'data'->'commonsites_by_id'->>'pk' from jsongraphs;
This won't work because the path should be like data->scopes_by_id->121->>pk but I cannot know beforehand the value of 121 in between.
Is there a way to get the values of what I need by filling in some kind of wildcard in the path?
E.g.data->scopes_by_id->{*}->>pk like that?
ANd because this is legacy data, it's also hard to change the data itself.
As the nesting level seems to be fixed, you could do something like this:
select j.id, scopes.*, commonsites.*
from jsongraphs j
cross join lateral (
select jsonb_agg(j.jsongraph #> array['data','scopes_by_id', t1.scope_id, 'pk']) as scope_ids
from jsonb_each_text(j.jsongraph #> '{data,scopes_by_id}') as t1(scope_id)
) scopes
cross join lateral (
select jsonb_agg(j.jsongraph #> array['data','commonsites_by_id', t2.site_id, 'pk']) as common_ids
from jsonb_each_text(j.jsongraph #> '{data,commonsites_by_id}') as t2(site_id)
) commonsites
order by id;
The sub-queries extract all key below the respective part (e.g. scopes_by_id) and then uses the #>' operator to access the path for each id inside the original JSON value. And finally all PK values are aggregated back into a single array.
This returns the PK values from each part separately as an array in order to handle the situation where you have a different number of "scope ids" and "commonsite ids"
If you just want "the first" id from each section, you can remove the aggregation and use a LIMIT clause:
select j.id, scopes.*, commonsites.*
from jsongraphs j
cross join lateral (
select j.jsongraph #> array['data','scopes_by_id', t1.scope_id, 'pk'] as scope_id
from jsonb_each_text(j.jsongraph #> '{data,scopes_by_id}') as t1(scope_id)
limit 1
) scopes
cross join lateral (
select j.jsongraph #> array['data','commonsites_by_id', t2.site_id, 'pk'] as common_id
from jsonb_each_text(j.jsongraph #> '{data,commonsites_by_id}') as t2(site_id)
limit 1
) commonsites
order by id;
Not sure on which level you want to apply the "distinct" part for this.
In Postgres 12 or later, you could achieve the same with:
select id,
jsonb_path_query_array(j.jsongraph, 'strict $.data.scopes_by_id.**.pk') as scopes,
jsonb_path_query_array(j.jsongraph, 'strict $.data.commonsites_by_id.**.pk') as common
from jsongraphs ;
order by id;
Online example

POSTGRESQL extract keys from jsonb

I have a table with a jsonb column that contains a lot of external data and I need to extract some keys, but I'm having a hard time doing it.
The table is like that:
code title external_data
G20540 Data Analysis
The external data is the jsonb column, organized with a dict inside an array and it has these infos:
[
{
"DESCR": "Requisito RJ_GRD_GCSOSRJ",
"ORDERNO": "10",
"ACAD_PLAN": "",
"ACAD_PROG": "",
"DESCR254A": "Requisito RJ_GRD_GCSOSRJ_Plan. de Comunicação I",
"DESCRSHORT": "Requisito",
"EFF_STATUS": "A",
"RQ_CONNECT": "",
"ACAD_CAREER": "",
"INSTITUTION": "X",
"PARENTHESIS": "",
"SAA_DESCR80": "Requisito RJ_GRD_GCSOSRJ_Plan. de Comunicação I",
"RQRMNT_GROUP": "000312",
"ACAD_SUB_PLAN": "",
"CREATION_DATE": "2020-04-13T21:26:51.923",
"RQRMNT_USEAGE": "ENR",
"CONDITION_CODE": "CRS",
"CONDITION_DATA": "003130",
"REQUISITE_TYPE": "PRE",
"CONDITION_DESCR": "ID Curso",
"RQRMNT_LIST_SEQ": "1",
"RQ_GRP_LINE_NBR": "0010",
"RQ_LINE_KEY_NBR": "0001",
"RQ_GRP_LINE_TYPE": "CRSE",
"CONDITION_OPERATOR": "EQ"
}
]
I need to extract "DESCR", "ORDENO", "DESCR254A", "SAA_DESCR80", "RQRMNT_GROUP", "RQRMNT_USEAGE", "EFF_STATUS".
I tried with this query but I get only null results for the external_data columns:
SELECT codes.external_id as "code"
,codes.title as "title"
,requirements.external_data ->> 'RQRMNT_GROUP' as "RQRMNT_GROUP"
,requirements.external_data ->> 'EFF_STATUS' as "EFF_STATUS"
,requirements.external_data ->> 'RQRMNT_USEAGE' as "RQRMNT_USEAGE"
,requirements.external_data ->> 'DESCR' as "DESCR"
,requirements.external_data ->> 'SAA_DESCR80' as "SAA_DESCR80"
,requirements.external_data ->> 'DESCR254A' as "DESCR254A"
,requirements.external_data ->> 'ORDERNO' as "ORDERNO"
FROM requirements
LEFT JOIN codes ON codes.id = requirements.code_id
How can I do that?
You'll need to use a WITH clause to identify the table in the array and select from that:
WITH external_codes AS (
SELECT r.code_id,
jsonb_array_elements(r.external_data) AS external_data
FROM requirements r
)
SELECT codes.external_id as "code"
,codes.title as "title"
,external_codes.external_data ->> 'RQRMNT_GROUP' as "RQRMNT_GROUP"
,external_codes.external_data ->> 'EFF_STATUS' as "EFF_STATUS"
,external_codes.external_data ->> 'RQRMNT_USEAGE' as "RQRMNT_USEAGE"
,external_codes.external_data ->> 'DESCR' as "DESCR"
,external_codes.external_data ->> 'SAA_DESCR80' as "SAA_DESCR80"
,external_codes.external_data ->> 'DESCR254A' as "DESCR254A"
,external_codes.external_data ->> 'ORDERNO' as "ORDERNO"
FROM external_codes
LEFT JOIN codes ON codes.id = external_codes.code_id

org.postgresql.util.PSQLException: ERROR: set-returning functions are not allowed in WHERE

I need a help for the below mentioned detail,
I am using Postgres + Spring-Data-JPA. Moreover, I have used the jsonb data type for storing data.
I am trying to execute a query, but it gives me the following error:
ERROR: set-returning functions are not allowed in WHERE
The cause here is that I have added a jsonb condition in the WHERE clause (kindly refer to the below query for more detail).
Query (I have renamed column name just because of hiding the actual column name):
select distinct
jsonb_array_elements(initiated_referral_detail->'listOfAttribue')::jsonb
->> 'firstName' as firstName,
jsonb_array_elements(initiated_referral_detail->'listOfAttribue')::jsonb
->> 'lastName' as lastName,
jsonb_array_elements(initiated_referral_detail->'listOfAttribue')::jsonb
->> 'country' as country
from
tale1 table10_
left outer join
table2 table21_
on table10_.end_user_id=table21_.end_user_id
left outer join
table3 table32_
on table10_.manufacturer_id=table32_.manufacturer_id
where
table21_.end_user_uuid=(
?
)
and table21_.is_active=true
and table32_.manufacturer_uuid=(
?
)
and table32_.is_active=true
and table10_.is_active=true
and table32_.is_active=true
and jsonb_array_elements(initiated_referral_detail->'listOfAttribue')::jsonb
->> 'action' = ('PENDING')
order by
jsonb_array_elements(initiated_referral_detail->'listOfAttribue')::jsonb
->> 'firstName',
jsonb_array_elements(initiated_referral_detail->'listOfAttribue')::jsonb
->> 'lastName'
limit ?
The following line in the above query is causing the error:
and jsonb_array_elements(initiated_referral_detail->'listOfAttribue')::jsonb
->> 'action' = ('PENDING')
Can anyone please guide me about how do fetch data from the inner JSON? Especially in my case I have an inner List and a few elements inside.
I recommend a lateral join with jsonb_array_elements for cases like that. Here is an example:
CREATE TABLE tale1 (
id integer PRIMARY KEY,
initiated_referral_detail jsonb NOT NULL
);
INSERT INTO tale1 VALUES
(1, '{
"name": "one",
"listOfAttribue": [
{ "id": 1, "action": "DONE"},
{ "id": 2, "action": "PENDING" },
{ "id": 3, "action": "ACTIVE" }
]
}');
INSERT INTO tale1 VALUES
(2, '{
"name": "two",
"listOfAttribue": [
{ "id": 1, "action": "DONE"},
{ "id": 2, "action": "ACTIVE" }
]
}');
To find all ids where the associated JSON contains an array element with action = PENDING, you can query like this:
SELECT DISTINCT id
FROM tale1 CROSS JOIN LATERAL
jsonb_array_elements(initiated_referral_detail -> 'listOfAttribue') AS attr
WHERE attr ->> 'action' = 'PENDING';