mysql 5.7.12 json extract sum - mysql

I need the sum of the "total" value. But my query always returns 0
My json field:
{'damage_budget': [{"id": 1, "name": "test-one", "value": 131, "quantity": 1}, {"id": 10, "name": "test-two", "value": 460, "quantity": 1}]}
My query:
select id, sum(column ->>'$.damage_budget[*].value') from table

Your extract returns a JSON array, not a scalar.
mysql> set #j = '{"damage_budget": [{"id": 1, "name": "test-one", "value": 131, "quantity": 1}, {"id": 10, "name": "test-two", "value": 460, "quantity": 1}]}';
mysql> select json_unquote(json_extract(#j, '$.damage_budget[*].value')) as v;
+------------+
| v |
+------------+
| [131, 460] |
+------------+
The argument to SUM() must be a scalar.
There's no simple way to do a SUM() against a JSON array in MySQL 5.7. Solutions exist, but they involve coding complex procedures.
You should either upgrade to MySQL 8.0 so you can use JSON_TABLE() (for example see my answer to MySql join by json key and multiply quantity for each order item and get total price), or else store your data in normal rows and columns, not JSON.
In fact, there's no reason to store the data you show in JSON regardless of which version of MySQL you use. You should just store it in normal rows and columns.

Related

How to search within MySQL JSON object array?

Consider the following JSON object,
[
{
"id": 5964460916832,
"name": "Size",
"value": "Small",
"position": 1,
"product_id": 4588516409440
},
{
"id": 5964460916833,
"name": "Size",
"value": "Medium",
"position": 2,
"product_id": 4588516409440
},
{
"id": 5964460916834,
"name": "Size",
"value": "Large",
"position": 3,
"product_id": 4588516409440
}
]
This is a value present in a table field called custom_attrs of JSON data type in a MySQL 8.0 table. I wanted to search the JSON data to match with multiple fields in the same object.
For example,
I wanted to see if there's a match for name "Size" and value "Medium" within the same object. It should not match the name in the first object and the value in the second object.
While we can always use JSON table, I don't prefer that due to the complexities it brings during the JOINs.
JSON_SEARCH supports LIKE operator, but it cannot ensure if it's from the same object
JSON_CONTAINS supports multiple fields but not LIKE as follows,
SET #doc = CAST('[{"id":5964460916832,"name":"Size","value":"Small","position":1,"product_id":4588516409440},{"id":5964460916833,"name":"Size","value":"Medium","position":2,"product_id":4588516409440},{"id":5964460916834,"name":"Size","value":"Large","position":3,"product_id":4588516409440}]' AS JSON);
SELECT JSON_CONTAINS(#doc, '{"name":"Size", "value":"Small"}')
Is there any way to get the same JSON_CONTAINS like functionality with partial search like, {"name":"Size", "value":"%sma%"}
Any help on this would be greatly helpful.
JSON_CONTAINS() only works with equality, not with pattern matching.
The JSON_TABLE() function is the solution intended to address the task you are trying to do. But you said you don't want to use it.
You can simulate JSON_TABLE() using other functions.
select * from (
select
json_unquote(json_extract(col, concat('$[',n.i,'].id'))) as `id`,
json_unquote(json_extract(col, concat('$[',n.i,'].name'))) as `name`,
json_unquote(json_extract(col, concat('$[',n.i,'].value'))) as `value`
from (select #doc as col) j
cross join (select 0 as i union select 1 union select 2 union select 3 union select 4 union select 5 ...) as n
) as t
where t.`id` is not null
order by id, `name`;
Output:
+---------------+------+--------+
| id | name | value |
+---------------+------+--------+
| 5964460916832 | Size | Small |
| 5964460916833 | Size | Medium |
| 5964460916834 | Size | Large |
+---------------+------+--------+
You could then easily add a condition like AND value LIKE '%sma%'.
As you can see, this query is even more complex than if you had used JSON_TABLE().
Really, any solution is going to be complex when you store your data in JSON format, then try to use SQL expressions and relational operations to query them as if they are normalized data. This is because you're practically implementing a mini-database within the functions of a real database. This is sometimes called the Inner-Platform Effect:
The inner-platform effect is the tendency of software architects to create a system so customizable as to become a replica, and often a poor replica, of the software development platform they are using. This is generally inefficient and such systems are often considered to be examples of an anti-pattern.
If you want simple queries, you should store data in normal rows and columns, not in JSON. Then you could get your result using quite ordinary SQL:
SELECT id, name, value FROM MyTable WHERE name = 'Size' AND value LIKE '%sma%';

psql equivalent of pandas .to_dict('index')

I want to return a psql table, but I want to return it in json format.
Let's say the table looks like this...
id
name
value
1
joe
6
2
bob
3
3
joey
2
But I want to return it as an object like this...
{
"1": {
"name": "joe",
"value": 6
},
"2": {
"name": "bob",
"value": 3
},
"3": {
"name": "joey",
"value": 2
}
}
So if I were doing this with pandas and the table existed as a dataframe, I could transform it like this...
df.set_index('id').to_dict('index')
But I want to be able to do this inside the psql code.
The closest I've gotten is by doing something like this
select
json_build_object (
id,
json_build_object (
'name', name,
'value', value
)
)
from my_table
But instead of aggregating this all into one object, the result is a bunch of separate objects separated by rows at the key level... that being said, it's kinda the same idea...
Any ideas?
You want jsonb_object_agg() to get this:
select jsonb_object_agg(id, jsonb_build_object('name', name, 'value', value))
from my_table
But this is not going to work well for any real-world sized tables. There is a limit of roughly 1GB for a single value. So this might fail with an out-of-memory error with larger tables (or values inside the columns)

How to aggregate array values in JSONB?

I have the following PostgreSQL table:
CREATE TABLE orders
(
id uuid NOT NULL,
order_date timestamp without time zone,
data jsonb
);
Where data contains json documents like this:
{
"screws": [
{
"qty": 1000,
"value": "Wood screw"
},
{
"qty": 500,
"value": "Drywall screw"
},
{
"qty": 500,
"value": Concrete screw"
}
],
"nails": [
{
"qty": 1000,
"value": "Round Nails"
}
]
}
How do I can get an overall quantity for all types of screws across all orders? Something like this :)
select value, sum(qty) from orders where section = 'screws' group by value;
I am not quite sure why you are trying to sum up the qty values, because the GROUP BY value makes only sense if there would be several times the same value which can be summed, e.g. if you would have twice the value Wood screw.
Nevertheless, this would be the query:
step-by-step demo:db<>fiddle
SELECT
elems ->> 'value' AS value,
SUM((elems ->> 'qty')::int) AS qty
FROM
orders,
jsonb_array_elements(data -> 'screws') elems
GROUP BY 1
Expand the screw array into one row per array element by jsonb_array_elements()
Get the qty value by the ->> operator (which gives out a text type) and cast this into type int
If really necessary, aggregate these key/value pairs.

MySQL JSON: How to select MIN() value

I have a MySQL table:
The combo column is a JSON datatype
id | combo
1 | {"qty": "2", "variations": [{"name": "Cover", "value": "Paperback"}], "price": "14.00"}
2 | {"qty": "1", "variations": [{"name": "Cover", "value": "Hardback"}], "price": "7.00"}
3 | {"qty": "1", "variations": [{"name": "Cover", "value": "Paperback"}], "price": "15.00"}
I'm trying to get the MIN() price of 7.00 but as they're strings, it returns 14.00.
Can this be done? Here's what I tried:
SELECT
JSON_UNQUOTE(MIN(combo->'$.price')) AS min_price
FROM itemListings
GROUP BY id
I also tried removing the quotes around the stored prices but it gave the same results.
Your code is giving you the lexicographical minimum; when sorting strings a "1" comes before a "7", despite the strings being "14.00" and "7:00", just like "apple" comes before "bat", despite "apple" being longer than "bat".
You want the numerical minimum, so cast the value to a decimal number:
SELECT
id, -- you probably want the select the grouped by value too
MIN(CAST(combo->'$.price' AS DECIMAL(10,2))) AS min_price
FROM itemListings
GROUP BY id

Retrieve first N records of a JSON array with a Postgresql query

PostgreSQL has some native JSON operations since verison 9.3. Suppose you have a table, my_table, with a json column, my_json_col, structured as follows:
[
{ "id": 1, "some_field": "blabla" },
{ "id": 2, "some_field": "foo" }
...
]
To retrieve the n-th element of my_json_col, you would execute something like: SELECT my_json_col->n FROM my_table WHERE .... So if n = 1, the query would return the "id": 2 record in my example.
I want to retrieve the first n elements, e.g. if n = 2 the query should return the first two records in my example. Is this possible?
In PostgreSQL 12, you can do:
SELECT jsonb_path_query_array('["a","b","c","d","e","f"]', '$[0 to 3]');
jsonb_path_query_array
------------------------
["a", "b", "c", "d"]
(1 row)
I think you need to convert the JSON array to a regular Postgres array, then take a slice of it:
select (array_agg(e))[2:3]
from (select json_array_elements('[{"id":1},{"id":2},{"id":3},{"id":4}]'::json)) x(e);
If you need the result to be JSON, you can use array_to_json:
select array_to_json((array_agg(e))[2:3])
from (select json_array_elements('[{"id":1},{"id":2},{"id":3},{"id":4}]'::json)) x(e);
For anyone who will stumble here trying the same thing. Here is what I faced
I had a similar problem, I needed the N result from jsonb field which was containing an array. The result I needed was all the fields of the table and N number of data from the data field where the condition satisfies.
After going through this again and again I found out that the accepted answer aggregates the rows. So if you have a table
id
type
data
status
1
employee
{{"age": 29, "name": "EMP 1"}, {"age": 30, "name": "EMP 2"},...}
Active
2
manager
{{"age": 28, "name": "MNG 1"}, {"age": 30, "name": "MNG 2"},...}
Active
and you run the query
select (array_agg(e))[2:3]
from (select json_array_elements(data::json) from <table>) x(e);
then the output will be
data
{{"age": 30, "name": "EMP 2"}, {"age": 28, "name": "MNG 1"}}
which was not needed in my case, What I needed was n data for each individual row where the condition satisfies e.g.
data
{{"age": 29, "name": "EMP 1"},{"age": 30, "name": "EMP 2"}}
{{"age": 28, "name": "MNG 1"},{"age": 30, "name": "MNG 2"}}
so after searching a little and going through the link provided by #Paul A Jungwirth
in the accepted answer. I found out that this can also be achieved by
select (ARRAY(select json_array_elements_text(data::json)))[0:2]
and the result will give you the n number of data from the jsonb field where the condition satisfies, you can also access other fields with it as well. Like let's say you want id and n number of data out of this table then you can do that just by adding id in the select query. (I was unable to get "id" in the query in the accepted answer)
select id, (ARRAY(select json_array_elements(data::json)))[0:2] from table where condition
will give output
id
data
1
{{"age": 29, "name": "EMP 1"},{"age": 30, "name": "EMP 2"}}
2
{{"age": 28, "name": "MNG 1"},{"age": 30, "name": "MNG 2"}}
Hope this will be helpful to someone.