Counting words in JSON array mySQL - mysql

I need to count true and false words in a JSON datatype.
I have this JSON in the cell:
{"1": true, "2": false, "3": true}
The number of values may vary. I realize that I can count the total number of values in the array but how can I count true and false separately?
For total count I used JSON_LENGTH()

One option would be using below approach containing JSON_LENGTH(), JSON_EXTRACT() and JSON_SEARCH() functions together even for the version 5.7 (5.7.13+) where an array(js) extracted in the subquery and they're splitted to individual array for each values (true and false) by using JSON_SEARCH() function containing all as the second argument, and then counted by JSON_LENGTH() function :
SELECT ID,
JSON_LENGTH( JSON_SEARCH(js, 'all', 'true') ) AS Cnt_True,
JSON_LENGTH( JSON_SEARCH(js, 'all', 'false') ) AS Cnt_False
FROM ( SELECT *, JSON_EXTRACT(jsdata, '$.*') AS js
FROM tab ) t
provided JSON field has quoted values such as "true" and "false"
JSON_EXTRACT(jsdata, '$.*') still can be used in case that the boolean values are unquoted as in your case. But, this time some string operations would be needed. Here, I preferred using CHAR_LENGTH() function :
SELECT ID,
CHAR_LENGTH(js) - CHAR_LENGTH(REPLACE(js, 'true', SPACE(LENGTH('true')-1)))
AS Cnt_True,
CHAR_LENGTH(js) - CHAR_LENGTH(REPLACE(js, 'false', SPACE(LENGTH('false')-1)))
AS Cnt_False
FROM
( SELECT *, JSON_EXTRACT(jsdata, '$.*') AS js
FROM tab ) t
Demo

Here is one option using json_table(), availabe in MySQL 8.0.
You can first turn each json object to an array of values using path $.*'. Then, you can pass the resulting json array to json_table(), which will put each value on a separate row. The final step is conditional aggregation.
Assuming that the json column is called js, that would be:
select sum(x.val = 'true') cnt_true, sum(x.val = 'false') cnt_false
from mytable t
cross join json_table(js -> '$.*', '$[*]' columns (val varchar(5) path '$')) x
Demo on DB Fiddle
Sample data (I added another row to make this more meaningful):
| js |
| :--------------------------------- |
| {"1": true, "2": false, "3": true} |
| {"bar": false, "foo": true} |
Results:
cnt_true | cnt_false
-------: | --------:
3 | 2

Related

Can't get through mysql JSON_CONTAINS to show the correcct data. Getting null everytime i try

The data in the table events with column attributes is in json and looks like this -
{"event_volunteer_requests":{"1":{"volunteertypeid":250,"volunteer_needed_count":50,"cc_email":""},"2":{"volunteertypeid":249,"volunteer_needed_count":30,"cc_email":""}}}
So the context is that i want to show the events with volunteertypeid present in the attributes. There are many volunteers., this is just sample data.
I've been using the query below
SELECT
*
FROM `events`
WHERE `zoneid` = 27
AND `isactive` = 1
AND JSON_CONTAINS(`attributes` -> '$.event_volunteer_requests[*].volunteertypeid', '249');
I just can't make this work and i've scorched the internet for hours. Any guidance would be appreciated.
Your JSON structure uses object syntax ({"key": "value"}), but you seem to want to use it as a JSON array (["value", "value", ...]).
The [*] syntax in JSON paths works for JSON arrays, not for JSON objects.
If your JSON document must be nested object syntax:
{
"event_volunteer_requests": {
"1": {
"cc_email": "",
"volunteertypeid": 250,
"volunteer_needed_count": 50
},
"2": {
"cc_email": "",
"volunteertypeid": 249,
"volunteer_needed_count": 30
}
}
}
Then you can do what you want this way:
SELECT t.id, t.volunteertypeid
FROM (
SELECT j.id, JSON_EXTRACT(a.attributes, CONCAT('$.event_volunteer_requests."', j.id, '".volunteertypeid')) AS volunteertypeid
FROM (SELECT '{"event_volunteer_requests":{"1":{"volunteertypeid":250,"volunteer_needed_count":50,"cc_email":""},"2":{"volunteertypeid":249,"volunteer_needed_count":30,"cc_email":""}}}' as attributes) AS a
CROSS JOIN JSON_TABLE(JSON_KEYS(a.attributes, '$.event_volunteer_requests'), '$[*]' COLUMNS (id INT PATH '$')) AS j
) AS t
WHERE t.volunteertypeid = 249;
Result:
+------+-----------------+
| id | volunteertypeid |
+------+-----------------+
| 2 | 249 |
+------+-----------------+
The JSON_TABLE() function requires MySQL 8.0. If you use MySQL 5.7, you must upgrade.

Count the number of arrays in json with a MySQL select statement

How can I count the number of arrays in json with a MySQL select statement?
For example, in the following case, I want 2 to be returned.
sample
+-----------+-----------+----------------------------------+
| id | json |
+-----------+-----------+----------------------------------+
| 1 | { items: [{name: a, age: 20}, {name: b, age: 30}] } |
...
I was able to get the contents with json_extract.
but I want count the number.
select
json_extract(json, '$.items')
from
sample
where
id = 1
select
json_array_length(json_extract(json, '$.items')) as size
from
sample
where
id = 1
json_array_length() is use to count size of json array
You can use JSON_LENGTH function, which is compatible with MySQL 5.7:
SELECT JSON_EXTRACT(json, '$.items'),
JSON_LENGTH(json, '$.items')
FROM sample
WHERE id = 1
Check the demo here.
Here is a trick to count, you can use a combination of LENGTH() and REPLACE() functions.
db<>fiddle
SELECT id, json, ROUND((LENGTH(json)- LENGTH(REPLACE(json, 'name', '')))/4,0) AS array_count
FROM (
SELECT 1 AS id, '{ items: [{name: a, age: 20}, {name: b, age: 30}] }' AS json
) tmp

MySQL Parse and Split JSON value

I have a column which contains a JSON value of different lengths
["The Cherries:2.50","Draw:3.25","Swansea Jacks:2.87"]
I want to split them and store into a JSON like so:
[
{
name: "The Cherries",
odds: 2.50
},
{
name: "Draw",
odds: 3.25
},
{
name: "Swansea",
odds: 2.87
},
]
What I did right now is looping and splitting them in the UI which to me is quite heavy for the client. I want to parse and split them all in a single query.
If you are running MySQL 8.0, you can use json_table() to split the original arrayto rows, and then build new objects and aggregate them with json_arrayagg().
We need a primary key column (or set of columns) so we can properly aggreate the generated rows, I assumed id:
select
t.id,
json_arrayagg(json_object(
'name', substring(j.val, 1, locate(':', j.val) - 1),
'odds', substring(j.val, locate(':', j.val) + 1)
)) new_js
from mytable t
cross join json_table(t.js, '$[*]' columns (val varchar(500) path '$')) as j
group by t.id
Demo on DB Fiddle
Sample data:
id | js
-: | :-------------------------------------------------------
1 | ["The Cherries:2.50", "Draw:3.25", "Swansea Jacks:2.87"]
Query results:
id | new_js
-: | :----------------------------------------------------------------------------------------------------------------------
1 | [{"name": "The Cherries", "odds": "2.50"}, {"name": "Draw", "odds": "3.25"}, {"name": "Swansea Jacks", "odds": "2.87"}]
You can use json_table to create rows from the json object.
Just replace table_name with your table name and json with the column that contains json
SELECT json_arrayagg(json_object('name',SUBSTRING_INDEX(person, ':', 1) ,'odds',SUBSTRING_INDEX(person, ':', -1) ))
FROM table_name,
JSON_TABLE(json, '$[*]' COLUMNS (person VARCHAR(40) PATH '$') people;
Here is a Db fiddle you can refer
https://dbfiddle.uk/?rdbms=mysql_8.0&fiddle=801de9f067e89a48d45ef9a5bd2d094a

How to select query from json in Postgres

I have JSON data in field hotel_data like this:
{
"title":"foo",
"description":[
{
"locale":"pt",
"content":"pt text"
},
{
"locale":"fr",
"content":"fr text"
}
]
}
I would like to select description only fr description. It is possible using Postgres and how?
I was trying use ->> but it is not working...
SELECT
hotel_data->'description'->>'locale' = 'fr' AS description
FROM hotel LIMIT 1;
Note:
I don't want to use SELECT *...
Excepted output: {description: "fr text"}
You can use a lateral join and json_to_recordset to expand the json array as a set of records. Then, you can filter on column locale in the generated records, and finally recompose a new json object with your expected result:
select json_build_object('description', d.content) hotel_data_descr_fr
from
mytable,
json_to_recordset(hotel_data->'description') as d("locale" text, "content" text)
where d.locale = 'fr'
Demo on DB Fiddle:
with mytable as (
select '{
"title":"foo",
"description":[
{
"locale":"pt",
"content":"pt text"
},
{
"locale":"fr",
"content":"fr text"
}
]
}'::json hotel_data
)
select json_build_object('description', d.content) hotel_data_descr_fr
from
mytable,
json_to_recordset(hotel_data->'description') as d("locale" text, "content" text)
where d.locale = 'fr'
| hotel_data_descr_fr |
| :------------------------- |
| {"description": "fr text"} |
The filtering can be done using the #> operator which can use a GIN index on the hotel_data column. This is typically faster than expanding the array.
select ...
from hotel
where hotel_data #> '{"description": [{"locale":"fr"}] }';
This can also be extended to include more properties:
select ...
from hotel
where hotel_data #> '{"description": [{"locale":"fr", "headline": "nice view'}] }';
But you can only express equality conditions on the key/value pairs with that. Using LIKE is not possible. You will have to expand the array if you want to do that and apply the condition in the WHERE clause - see GMB's answer.
To extract that description, I would use a scalar sub-query:
select (select jsonb_build_object('description', t.descr ->> 'content')
from jsonb_array_elements(h.hotel_data -> 'description') as t(descr)
where t.descr ->> 'locale' = 'fr'
limit 1)
from hotel h
where h.hotel_data #> '{"description": [{"locale":"fr"}] }';
That way you don't need to expand the array for filtering which I expect to be faster if only a few hotels qualify for that condition. But it has the drawback that you need to repeat the condition on the locale in the sub-select.
The limit 1 is only a safety net in case you have more than one french description. If you never have that, it doesn't hurt either
With Postgres 12 this is easier:
select jsonb_build_object(
'description',
jsonb_path_query_first(hotel_data, '$.description ? (#.locale == "fr")') -> 'content'
)
from hotel
where hotel_data #> '{"description": [{"locale":"fr"}] }'
All of the above assumes hotel_data is a jsonb column, if it's not (which it should be) you need to cast it: hotel_data::jsonb

query to Extract from Json in Postgres

I've a json object in my postgres db, which looks like as given below
{"Actor":[{"personName":"Shashi Kapoor","characterName":"Prem"},{"personName":"Sharmila Tagore","characterName":"Preeti"},{"personName":"Shatrughan Sinha","characterName":"Dr. Amar"]}
Edited (from editor: left the original because it is an invalid json, in my edit I fixed it)
{
"Actor":[
{
"personName":"Shashi Kapoor",
"characterName":"Prem"
},
{
"personName":"Sharmila Tagore",
"characterName":"Preeti"
},
{
"personName":"Shatrughan Sinha",
"characterName":"Dr. Amar"
}
]
}
the name of the column be xyz and I've a corresponding content_id.
I need to retrieve content_ids that have Actor & personName = Sharmila Tagore.
I tried many queries, among those these two where very possible query to get but still i didn't get.
SELECT content_id
FROM content_table
WHERE cast_and_crew #>> '{Actor,personName}' = '"C. R. Simha"'
.
SELECT cast_and_crew ->> 'content_id' AS content_id
FROM content_table
WHERE cast_and_crew ->> 'Actor' -> 'personName' = 'C. R. Simha'
You should use jsonb_array_elements() to search in a nested jsonb array:
select content_id, value
from content_table,
lateral jsonb_array_elements(cast_and_crew->'Actor');
content_id | value
------------+-----------------------------------------------------------------
1 | {"personName": "Shashi Kapoor", "characterName": "Prem"}
1 | {"personName": "Sharmila Tagore", "characterName": "Preeti"}
1 | {"personName": "Shatrughan Sinha", "characterName": "Dr. Amar"}
(3 rows)
Column value is of the type jsonb so you can use ->> operator for it:
select content_id, value
from content_table,
lateral jsonb_array_elements(cast_and_crew->'Actor')
where value->>'personName' = 'Sharmila Tagore';
content_id | value
------------+--------------------------------------------------------------
1 | {"personName": "Sharmila Tagore", "characterName": "Preeti"}
(1 row)
Note, if you are using json (not jsonb) use json_array_elements() of course.