How to count instances inside json object array? - sql

I have some table that includes an array of jsonb objects as a column:
| event_id | attendees |
|----------|----------------------------------------------------------------------------------------------------------------------------------------------|
| 1 | [{"name": "john smith", "username": "jsmith"}, {"name": "jeff jones", "username": "jjones"}, {"name": "steve woods", "username": "swoods"}] |
| 2 | [{"name": "al williams", "username": "awilliams"}, {"name": "james lee", "username": "jlee"}, {"name": "bob thomas", "username": "bthomas"}] |
| 3 | [{"name": "doug hanes", "username": "dhanes"}, {"name": "stan peters", "username": "speters"}, {"name": "jane kay", "username": "jkay"}] |
I would like to get the count of all attendees whose username matches some condition (let's say whose username starts with "j") for each event.
Looking at the documentation, I couldn't really find anything that I could use for jsonb object arrays. The closest thing I could see was the jsonb_array_elements function, but that returns a set and not individual values. so something like:
select event_id, count(jsonb_array_elements(attendees) ->> 'username')
from events
where jsonb_array_elements(attendees) ->> 'username' like 'a%'
group by event_id
would obviously not work. Is there something that would return this output (count of usernames that begin with j for each event):
| event_id | count |
|----------|-------|
| 1 | 2 |
| 2 | 1 |
| 3 | 1 |

Well, just split your SQL logic to two part.
As below, you can get the all username for each event_id,
select
event_id,
jsonb_array_elements(attendees) ->> 'username' as user_name
from
events;
event_id | user_name
----------+-----------
1 | jsmith
1 | jjones
1 | swoods
2 | awilliams
2 | jlee
2 | bthomas
3 | dhanes
3 | speters
3 | jkay
(9 rows)
And then we can calculate some statistics data of json elements for event_id dimension,for example, you want to get the username's number of each event_id whose username started with some character such as 'j', the complete SQL would be:
with tmp as (
select
event_id,
jsonb_array_elements(attendees) ->> 'username' as user_name
from
events
)
select
event_id,
count(1)
from
tmp
where
user_name like 'j%'
group by
event_id
order by
event_id;
event_id | count
----------+-------
1 | 2
2 | 1
3 | 1
(3 rows)

Related

Postgres SQL: How to query jsonb column having data in array

I have a table customrule with a structure
id. - int
name - varchar
actions. - jsonb
I already have read about the -> operator. But it seems not working in my case as I have data stored in as an array.
+-----------------------------------------------------------------------------------+
| Name | Id | Actions |
+-----------------------------------------------------------------------------------+
| CR-1 | 1 | [{"name": "Action1", "count": "1"},{"name": "Action2", "count": "2"}] |
+-------------------+---------------------------------------------------------------+
| CR-2 | 2 | [{"name": "Action5", "count": "1"},{"name": "Action4", "count": "2"}] |
+-----------------------------------------------------------------------------------+
| CR-3 | 3 | [{"name": "Action1", "count": "1"},{"name": "Action1", "count": "2"}] |
+-----------------------------------------------------------------------------------+
I want to query this data and get all records which have Action1 used in the actions column. Which should return row 1 and 3rd as a result.
You need to use the contains operator with an array parameter
select id, name, actions
from customrule
where actions #> '[{"name": "Action1"}]'
Online example

Postgresql: select rows by OR condition - including going through json array

I have a table created by following query:
create table data
(
id integer not null unique,
owner text,
users jsonb not null
);
The table looks like this:
+----+-------+---------------------------------------------+
| id | owner | users |
+----+-------+---------------------------------------------+
| 1 | alice | [] |
| 2 | bob | [{"accountId": "alice", "role": "manager"}] |
| 3 | john | [{"accounId": "bob", "role": "guest"}] |
+----+-------+---------------------------------------------+
I need to get rows 1 and 2 on behalf of Alice.
Getting owner-based rows works perfect:
SELECT *
FROM data
WHERE owner = 'alice'
Getting jsonb-based rows is a little trickier though managable:
SELECT *
FROM data, jsonb_array_elements(users) x
WHERE (x ->> 'accountId') = 'alice'
But getting them together gets me just the jsonb-based ones:
SELECT *
FROM data, jsonb_array_elements(users) x
WHERE owner = 'alice' OR (x ->> 'accountId') = 'alice'
How do I get the selection that looks like following?
+----+-------+---------------------------------------------+
| id | owner | users |
+----+-------+---------------------------------------------+
| 1 | alice | [] |
| 2 | bob | [{"accountId": "alice", "role": "manager"}] |
+----+-------+---------------------------------------------+
Even better if I can get a selection that looks like this
+----+----------+
| id | role |
+----+----------+
| 1 | owner |
| 2 | manager |
+----+----------+
The problem is with the empty json array, which evicts the corresponding row from the result set when cross joined with jsonb_array_elements(). Instead, you can make a left join lateral:
select d.*
from data d
left join lateral jsonb_array_elements(d.users) as x(js) on 1 = 1
where 'alice' in (d.owner, x.js ->> 'accountId')
Note that, if your array always contains 0 or 1 element, tyou don't need the lateral join - your query would be simpler phrased as:
select d.*
from data d
where 'alice' in (d.owner, d.data -> 0 ->> 'accountId')
Demo on DB Fiddle - both queries return:
id | owner | users
-: | :---- | :------------------------------------------
1 | alice | []
2 | bob | [{"role": "manager", "accountId": "alice"}]

SQL Rows to Json Array with grouping and aggregation

My goal is to take data from a row with a specific ID and convert it into a JSON object to insert into another table. What I'm starting with looks like this
Event_Details
-----------------------------------
ID | ID2 | First_Name| Last_Name |
-----------------------------------
1X | 2B | John | Smith |
2X | 2B | Adam | John |
3X | 2B | Sarah | Jones |
1X | 5C | Joe | Rob |
What I want looks like this:
[
{
"id2": "2B",
"event": {
"ID": "1X",
"First_Name": "John",
"Last_Name": "Smith"
}
},
{
"id2": "5C",
"event": {
"ID": "1X",
"First_Name": "Joe",
"Last_Name": "Rob"
}
}
]
I need to group the items into a single JSON object by "ID" but I want the id2 outside of the "Event" array.
This is what I have so far which does the first thing, I'm just having trouble nesting the query for the array inside of it:
select json_agg (b)
from (select ID2 as "ID2"
from event_details
)b
I believe this is what you are looking for:
select json_agg(jsonb_build_object('id2', id2,
'event', jsonb_build_object('ID', id,
'First_Name', first_name,
'Last_Name', last_name
)))
from event_details group by id;

PostgreSQL set field of JSON object in JSON array

I have a table like this:
| id (SERIAL) | game (TEXT) | players (JSONB) |
+-------------+-------------+-----------------+
| 1 | chess | [{name: Joe, role: admin}, {name: Mike, role: user}] |
| 2 | football | [{name: Foo, role: user}, {name: Bar, role: user}] |
+-------------+-------------+-----------------+
I want to set the role of a player (Joe) to a certain value (user) in a certain game (chess), so the result should look like this:
| id (SERIAL) | game (TEXT) | players (JSONB) |
+-------------+-------------+-----------------+
| 1 | chess | [{name: Joe, role: user}, {name: Mike, role: user}] |
| 2 | football | [{name: Foo, role: user}, {name: Bar, role: user}] |
+-------------+-------------+-----------------+
Is it possible to achieve this with a single query?
This is possible by recreating the json array on each update.
SQL for table creation and example data insertion:
CREATE TABLE test_table(
id BIGSERIAL PRIMARY KEY ,
game TEXT,
players JSONB
);
INSERT INTO test_table(game, players)
VALUES
('chess', '[{"name": "Joe", "role": "admin"}, {"name": "Mike", "role": "user"}]'),
('football', '[{"name": "Foo", "role": "user"}, {"name": "Bar", "role": "user"}]');
The inserted data:
+----+----------+----------------------------------------------------------------------+
| id | game | players |
+----+----------+----------------------------------------------------------------------+
| 1 | chess | [{"name": "Joe", "role": "admin"}, {"name": "Mike", "role": "user"}] |
| 2 | football | [{"name": "Foo", "role": "user"}, {"name": "Bar", "role": "user"}] |
+----+----------+----------------------------------------------------------------------+
Update query:
WITH json_rows AS
(SELECT id, jsonb_array_elements(players) as json_data FROM test_table
WHERE game = 'chess'),
updated_rows AS (
SELECT
id,
array_to_json(array_agg(
CASE WHEN json_data -> 'name' = '"Joe"'
THEN jsonb_set(json_data, '{role}', '"user"')
ELSE json_data END)) as updated_json
FROM json_rows
GROUP BY id
)
UPDATE test_table SET players = u.updated_json
FROM updated_rows u
WHERE test_table.id = u.id;
Results of the query:
+----+----------+---------------------------------------------------------------------+
| id | game | players |
+----+----------+---------------------------------------------------------------------+
| 2 | football | [{"name": "Foo", "role": "user"}, {"name": "Bar", "role": "user"}] |
| 1 | chess | [{"name": "Joe", "role": "user"}, {"name": "Mike", "role": "user"}] |
+----+----------+---------------------------------------------------------------------+
The query works in the following way:
Convert the json array to json rows and filter them by the game property. This is done by creating the json_rows CTE.
Update the json data in the json rows where the user "Joe" is found.
Once you have the new json values, just do an update based on the id.
Note: As you can see, in the current implementation the json array gets recreated (only in the rows that need to be updated). This may cause a change in the order of the elements inside the array.

How to modify the following cypher syntax in AgensGraph?

MATCH (wu:wiki_user)
OPTIONAL MATCH (n:wiki_doc{author:wu.uid}), (o:wiki_doc{editor:wu.uid})
RETURN wu.uid AS User_id, wu.org AS Organization, wu.email AS email, wu.token AS balance,
count(n) AS Writing, count(o) AS Modifying;
user_id | organization | email | balance | writing | modifying
--------------------------------------------------------------------------
"ailee" | "Org2" | "hazel#gbc.com" | 5 | 0 | 0
"hazel" | "Org1" | "hazel#gbc.com" | 5 | 2 | 2
match (n:wiki_doc{editor:'hazel'}) return n;
n
wiki_doc[9.11]
{"bid": "hazel_doc1", "cid": "Basic", "org": "Org1", "title": "Hello world!",
"author": "hazel", "editor": "hazel", "revnum": 1, "created": "2018-09-25
09:00:000", "hasfile": 2, "contents": "I was wrong", "modified": "2018-09-25
10:00:000"}
(1 row)
In fact, the number of updates in the case of hazel is 1, and 2
queries are used when the above query is used.
How to modify the query so that only one can be normally viewed.
MATCH( wu:wiki_user )
OPTIONAL MATCH (n:wiki_doc{author:wu.uid}), (o:wiki_doc{editor:wu.uid})
RETURN wu.uid AS User_id, wu.org AS Organization, wu.email AS email, wu.token AS balance,
count(distinct id(n)) as Writing, count(distinct id(o)) as Modifying;
user_id | organization | email | balance | writing | modifying
+----------------------------------------------------------+
"ailee" | "Org2" | "hazel#gbc.com" | 5 | 0 | 0
"hazel" | "Org1" | "hazel#gbc.com" | 5 | 2 | 1
(2 rows)