MariaDB JSON Query - json

I have a table with the following structure:
|id | json |
------------
| | |
------------
The JSON structure is as follows:
{
"roomType": "Deluxe",
"package": "Full-Board +",
"comb": {
"adult": "1",
"infant": "0",
"child": "0",
"teen": "0"
},
"rates": [
{
"rateFrom": "2021-02-11",
"rateTo": "2021-02-20",
"ratePrice": "6000"
}, {
"rateFrom": "2021-02-21",
"rateTo": "2021-02-26",
"ratePrice": "6500"
}]
}
There can be many entries in attribute rates.
Now, I need to return the rows where any of the attribute rateTo from rates is greater than today's date.
That is, if today's date is less than at least one rateTo of the entries of rates, then return that row.
This is the first time I am querying JSON and I am not sure if the structure of my JSON is correct for the type of querying I want to do.

This would be much easier if you abandoned JSON as a datatype and used properly normalised tables of rooms, packages, comb (might be part of packages?) and rates. If you are stuck with JSON though, one way to get the data you want is to extract all the rateTo values from each JSON into a comma separated (and ended) list of dates (for example, for your sample data, this would be 2021-02-20,2021-02-26,; then split that into individual dates 2021-02-20 and 2021-02-26, then SELECT rows from the original table if one of the associated dates is after today. You can do this with a couple of recursive CTEs:
WITH RECURSIVE toDate AS (
SELECT id, CONCAT(REGEXP_REPLACE(JSON_EXTRACT(`json`, '$.rates[*].rateTo'), '[ "\\[\\]]', ''), ',') AS toDates
FROM rooms
),
Dates AS (
SELECT id, SUBSTRING_INDEX(toDates, ',', 1) AS toDate, REGEXP_REPLACE(toDates, '^[^,]+,', '') AS balance
FROM toDate
UNION ALL
SELECT id, SUBSTRING_INDEX(balance, ',', 1), REGEXP_REPLACE(balance, '^[^,]+,', '')
FROM Dates
WHERE INSTR(balance, ',') > 0
)
SELECT *
FROM rooms r
WHERE EXISTS (SELECT *
FROM Dates d
WHERE d.id = r.id AND d.toDate > CURDATE())
Demo on dbfiddle

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%';

Query to count all records without certain key in the json column of the snowflake table

I am trying to fetch the count the number of records from a Snowflake without certain keys in the json column of that particular record.
Here’s how the snowflake table looks like :
EMP_ID|DEPARTMENT_NAME|EID|DETAILS
EMP10001 | Finance |10008918 |{
"name": "Alec George",
"Year_Joined": "2013",
"Ready_to_transfer": "no",
"Ready_to_permanently_WFH": "yes",
}
Now I want to count records that doesn’t have have the keys that start with Ready_ in the details column of the snowflake table and group counts by the Department_Name.
Note : There can be multiple keys that start with Ready_ in the details.
Currently what’s happening is my count query is returning records where keys start with Ready_ is also listed.
You can flatten to get all the keys, then for each record you can count the number of keys that start with your desired string:
with data as (
select $1 emp_id, $2 dep, $3 eid, parse_json($4) details
from (values
('EMP10001','Finance', 10008918, '{
"name": "Alec George",
"Year_Joined": "2013", "Ready_to_transfer": "no", "Ready_to_permanently_WFH": "yes", }')
,('EMP10002','Finance', 10008918, '{
"name": "Alex George",
"Year_Joined": "2013", }')
)
)
select seq, count_if(detail.key like 'Ready_%') how_many_ready
from data, table(flatten(details)) detail
group by 1
;
Then you only need to count the # of elements that have a count > 0.

Postgres: How to returns json type projected data

It might be a noob question, but would like to know the capabilities of postgres json output.
For the table below:
id | seconds | datetime
1 | 10 | 2020-08-21 08:42:58.26+08
2 | 20 | 2020-08-21 10:20:00.01+08
3 | 10 | 2020-08-22 08:00:00.10+08
Is this possible to output in json like so?
[{
"date" : "2020-08-21",
"seconds_1" : 10,
"seconds_2" : 20,
},
{
"date" : "2020-08-22",
"seconds_1" : 10
}]
I can manipulate the table result thru php/javascript, but just wondering if this is possible in postgres.
This requires a multi step aggregation:
select jsonb_agg(item)
from (
select jsonb_build_object('date', dt)|| jsonb_object_agg(concat('seconds_', rn), seconds) item
from (
select datetime::date as dt,
row_number() over (partition by datetime::date) as rn,
seconds
from the_table
) t
group by dt
) r
The inner most query is used to number the rows per date, this can't be done at the same level where the grouping by date is done, because then the numbers would be wrong (as window functions are evaluated after grouping)
The second level aggregates all "seconds" for each date and builds a JSON value from that. The last level then aggregates everything into a JSON array.
Online example
If you don't care about the numbers that make the "seconds" key unique, you can use the id column and simplify the query a bit:
select jsonb_agg(item)
from (
select jsonb_build_object('date', datetime::date)|| jsonb_object_agg(concat('seconds_', id), seconds) item
from the_table
group by datetime::date
) r

How to search JSON data in MySQL by key and value?

I am inserting my data in a database with json_encoded. Now I want to search in "feature", but the result is not exactly true.
MySQL query:
select *
from `modul_69`
where `extmod` like '%"68":%'
and `extmod` like '%"4"%'
and `extmod` not like '%"4":%'
Results:
row1 data:
{"68":["1","4","7"],"67":["14"],"75":["28"]} - true
row2 data:
{"68":["59"],"67":["1","11","13"],"75":["3","4","5","27"]} - false
I want select only row1 by key:68 and value:4
Please help
Here is one way to do it using MySQL JSON functions, available since version 5.7:
select *
from t
where json_search(js -> '$."68"', 'one', '4') is not null
What this does is get the array that correspond to outer key '68' (using ->, which is a syntactic sugar for json_extract()), and then search its content with json_search(); if a non-null value is returned, we have a match.
To find if the value '"4"' is contained in the member '"68"', you can first extract the array using JSON_EXTRACT() :
SELECT JSON_EXTRACT(m.extmod, '$."68"')
FROM modul_69 m;
This outputs
["1", "4", "7"]
["59"]
To search in a JSON array if it contains a specific value, you can use JSON_CONTAINS() :
SELECT JSON_CONTAINS('["1", "4", "7"]', '"4"', '$'); -- output is 1
SELECT JSON_CONTAINS('["59"]', '"4"', '$'); -- output is 0
Now you can combine both functions to get the rows that contains the expected value :
Schema (MySQL v5.7)
CREATE TABLE modul_69
(
extmod JSON
);
INSERT INTO modul_69 VALUES ('{"68":["1","4","7"],"67":["14"],"75":["28"]}'), ('{"68":["59"],"67":["1","11","13"],"75":["3","4","5","27"]}');
Query #1
SELECT *
FROM modul_69 m
WHERE JSON_CONTAINS(JSON_EXTRACT(m.extmod, '$."68"'),
'"4"',
'$') = 1;
Output
| extmod |
| --------------------------------------------------- |
| {"67": ["14"], "68": ["1", "4", "7"], "75": ["28"]} |
View on DB Fiddle

How to perform order by limit 1 inside a jsonb cell in postgres?

I have a table in postgres
CREATE TABLE my_table (
id serial primary key,
col_1 jsonb,
....
Inside col_1, I have a structure like this
[{"date": "2018-10-13", "val_1": 90.8, "val_2": 87.9},
{"date": "2018-10-03", "val_1": 90.2, "val_2": 83.2},
{"date": "2018-10-11", "val_1": 92.8, "val_2": 88.9},
...
]
Now I need to query something like this
SELECT "latest date from the jsonb" WHERE id = {some_id};
In order to do that, I should be able to order/sort the array in col_1 by date in descending order (convert the date string first using the to_date function) then get the first element of that sorted array. How can I do this in postgres?
You shoud unnest the json array using the function jsonb_array_elements():
select (jsonb_array_elements(col_1)->>'date')::date as date
from my_table
where id = 1
order by date desc
limit 1
date
------------
2018-10-13
(1 row)