Excluding null values when building JSON objects in Postgres - json

I want to build a json object where only non-null values are included. For example:
SELECT
id,
jsonb_build_object(
'price', price,
'item_returned_date', item_returned_date
) as data
from table
would return the following if the first row had null item_returned date:
id | data
1 {"price": 50}
2 {"price": 100, "item_returned_date: "2022-10-01"}
Is this possible without writing a complex function?

Related

MariaDB JSON Query

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

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

Map two columns into one on Athena using SerDe properties

I'm trying to map two columns into one on Athena using JsonSerDe properties.
In this case, I want to map both columns conversionsRate and cr from jsons 1 and 2 to column cr_new (doing like a coalesce).
json1
{
"deviceType": "TABLET",
"day": "2018-10-27",
"conversionsRate": 0,
"clicksCount": 3
}
json2
{
"deviceType": "TABLET",
"day": "2018-10-29",
"cr": 2,
"clicksCount": 5
}
The expected result on Athena:
|deviceType|day |cr_new|clicksCount|
|TABLET |2018-10-27|0 |3 |
|TABLET |2018-10-29|2 |5 |
Is it possible to achieve such a result on athena table mapping using SerDe?
JSON SerDe does not mandate (restrict) that column defined in the table DDL should exist in the JSON record. If there is no such attribute, JSONSerDe will return NULL. So, you can define both columns and apply coalesce in the query:
CREATE EXTERNAL TABLE json_table (
devicetype string,
`day` date,
cr int,
conversionsrate int,
clickscount int
)
ROW FORMAT SERDE 'org.openx.data.jsonserde.JsonSerDe'
LOCATION 's3://bucket/path/'; --JSON files location
--make sure JSONs are in separate lines each
select deviceType, `day`, coalesce(conversionsRate ,cr) as cr_new, clicksCount
from json_table ;

How to search multiple items in JSON array in Postgres 9.3

I have scenario where i need to search multiple values in a JSON array. Below is my schema.
ID DATA
1 {"bookIds" : [1,2,3,5], "storeIds": [2,3]}
2 {"bookIds" : [1,2], "storeIds": [1,3]}
3 {"bookIds" : [11,12,10,9], "storeIds": [4,3]}
I want all the rows with value 1,2. Below is query i am using (This is query is written by one of fellow stackoverflow user Mr. klin credit to him).
select t.*
from JSONTest t, json_array_elements(data->'bookIds') books
where books::text::int in (1, 2);
However output I am duplicate rows in output, below is my output.
id data
1 {"bookIds" : [1,2,3,5], "storeIds": [2,3]}
1 {"bookIds" : [1,2,3,5], "storeIds": [2,3]}
2 {"bookIds" : [1,2], "storeIds": [1,3]}
2 {"bookIds" : [1,2], "storeIds": [1,3]}
I want only two rows in output that is id 1,2. How can i do that? I don't want use Distinct due to other constraints,
SQL Fiddle : http://sqlfiddle.com/#!15/6457a/2
Unfortunately there is no direct conversion function from a JSON array to a "real" Postgres array. (data ->'bookIds')::text returns something that is nearly a Postgres array literal: e.g. [1,2,3,5]. If you replace the [] with {} the value can be cast to an integer array. Once we have a proper integer array we can use the #> to test if it contains another array:
select *
from jsontest
where translate((data ->'bookIds')::text, '[]', '{}')::int[] #> array[1,2];
translate((data ->'bookIds')::text, '[]', '{}') will convert [1,2,3,5] to {1,2,3,5} which then is converted to an array using ::int[]
SQLFiddle: http://sqlfiddle.com/#!15/6457a/4