Complex postgres json order by clause - sql

Schema:
TABLE field (
field_id serial NOT NULL,
section_id integer,
title text,
type text,
default_val json,
rank integer,
tiny integer,
"values" json,
grp_id integer,
aggregate integer DEFAULT 1,
enabled integer,
deleted integer DEFAULT 0,
"desc" text
)
TABLE entry
(
entry_id serial NOT NULL,
section_id integer,
deleted integer DEFAULT 0,
grp_id integer,
data json,
last_edited bigint,
last_editor_id integer
)
The column field.values might look like this: {0: {"rank" : 2, "title" : "asdf"}, 1: {"rank" : 1}}
The column entry.data might look like this: {250: 1, 251: 0}
What I'd like to query:
SELECT entry.*
FROM entry
LEFT JOIN field ON field.field_id = 31
WHERE entry.deleted = 0 AND section_id = $1 AND grp_id = $2
ORDER BY cast(field.values#>>'{**entry.data->>250**, rank}' as numeric) ASC
This is the part that I don't know how to do: **entry.data->>250**
Is this even possible? Basically I'm trying to sort by the rank value of an index within a field that corresponds to an entry.
EDIT:
Attemped:
(field.values->(cast(coalesce(e.data->>'f4', '0') as numeric)))->>'rank'
Error: operator does not exist: json -> numeric

You don't try to write a string which some how resolves the embedded functions. You concatenate strings to build a larger one...
field.values#>>('{' || entry.data->>250 || ', rank}')
Or, maybe easier to read?
(field.values->(entry.data->>250))->>'rank'

Related

SQL get the value of a nested key in a jsonb field

Let's suppose I have a table my_table with a field named data, of type jsonb, which thus contains a json data structure.
let's suppose that if I run
select id, data from my_table where id=10;
I get
id | data
------------------------------------------------------------------------------------------
10 | {
|"key_1": "value_1" ,
|"key_2": ["value_list_element_1", "value_list_element_2", "value_list_element_3" ],
|"key_3": {
| "key_3_1": "value_3_1",
| "key_3_2": {"key_3_2_1": "value_3_2_1", "key_3_2_2": "value_3_2_2"},
| "key_3_3": "value_3_3"
| }
| }
so in pretty formatting, the content of column data is
{
"key_1": "value_1",
"key_2": [
"value_list_element_1",
"value_list_element_2",
"value_list_element_3"
],
"key_3": {
"key_3_1": "value_3_1",
"key_3_2": {
"key_3_2_1": "value_3_2_1",
"key_3_2_2": "value_3_2_2"
},
"key_3_3": "value_3_3"
}
}
I know that If I want to get directly in a column the value of a key (of "level 1") of the json, I can do it with the ->> operator.
For example, if I want to get the value of key_2, what I do is
select id, data->>'key_2' alias_for_key_2 from my_table where id=10;
which returns
id | alias_for_key_2
------------------------------------------------------------------------------------------
10 |["value_list_element_1", "value_list_element_2", "value_list_element_3" ]
Now let's suppose I want to get the value of key_3_2_1, that is value_3_2_1.
How can I do it?
I have tryed with
select id, data->>'key_3'->>'key_3_2'->>'key_3_2_1' alias_for_key_3_2_1 from my_table where id=10;
but I get
select id, data->>'key_3'->>'key_3_2'->>'key_3_2_1' alias_for_key_3_2_1 from my_table where id=10;
^
HINT: No operators found with name and argument types provided. Types may need to be converted explicitly.
what am I doing wrong?
The problem in the query
select id, data->>'key_3'->>'key_3_2'->>'key_3_2_1' alias_for_key_3_2_1 --this is wrong!
from my_table
where id=10;
was that by using the ->> operand I was turning a json to a string, so that with the next ->> operand I was trying to get a json key object key_3_2 out of a string object, which makes no sense.
Thus one has to use the -> operand, which does not convert json into string, until one gets to the "final" key.
so the query I was looking for was
select id, data->'key_3'->'key_3_2'->>'key_3_2_1' alias_for_key_3_2_1 --final ->> : this gets the value of 'key_3_2_1' as string
from my_table
where id=10;
or either
select id, data->'key_3'->'key_3_2'->'key_3_2_1' alias_for_key_3_2_1 --final -> : this gets the value of 'key_3_2_1' as json / jsonb
from my_table
where id=10;
More info on JSON Functions and Operators can be find here

Querying based on JSON array sub element

Tried multiple answers from here and elsewhere and couldn't find the right answer yet.
create table mstore (
muuid uuid PRIMARY KEY,
msid text,
m_json JSONb[] not NULL
);
inserted first row:
insert into mstore (muuid, msid, m_json) values (
'3b691440-ee54-4d9d-a5b3-5f1863b78755'::uuid,
'<163178891004.4772968682254423915#XYZ-73SM>',
(array['{"m": 123, "mts": "2021-09-16T10:53:43.599012", "dstatus": "Dropped", "rcpt": "abc1#xyz.com"}']::jsonb[])
);
inserted second row:
insert into mstore (muuid, msid, m_json) values (
'3b691440-ee54-4d9d-a5b3-5f1863b78757'::uuid,
'<163178891004.4772968682254423915#XYZ-75SM>',
(array['{"m": 125, "mts": "2021-09-16T10:53:43.599022", "dstatus": "Dropped", "rcpt": "abc3#xyz.com"}']::jsonb[])
);
updated the first row:
update mstore
set m_json = m_json || '{"m": 124, "mts": "2021-09-16T10:53:43.599021", "dstatus": "Delivered", "rcpt": "abc2#xyz.com"}'::jsonb
where muuid = '3b691440-ee54-4d9d-a5b3-5f1863b78755';
Now table looks like:
muuid | msid | m_json
--------------------------------------+----------------------------------------------------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
3b691440-ee54-4d9d-a5b3-5f1863b78757 | <163178891004.4772968682254423915#XYZ-75SM> | {"{\"mid\": 125, \"rcpt\": \"abc3#xyz.com\", \"msg_ts\": \"2021-09-16T10:53:43.599022\", \"dstatus\": \"Dropped\"}"}
3b691440-ee54-4d9d-a5b3-5f1863b78755 | <163178891004.4772968682254423915#XYZ-73SM> | {"{\"mid\": 123, \"rcpt\": \"abc1#xyz.com\", \"msg_ts\": \"2021-09-16T10:53:43.599012\", \"dstatus\": \"Dropped\"}","{\"mid\": 124, \"rcpt\": \"abc2#xyz.com\", \"msg_ts\": \"2021-09-16T10:53:43.599021\", \"dstatus\": \"Delivered\"}"}
Now, I need to query based on the status. I tried few but most relevant one was
select * from mstore,jsonb_array_elements(m_json) with ordinality arr(item_object, position) where item_object->>'{"dstatus": "Delivered"}';
and
select * from mstore where m_json #> '[{"dstatus": "Delivered"}]';
Neither work, as they have syntax errors. How to run this query with dstatus values?
Please note that mstore.m_json is a Postgres array of JSONB elements and not a JSONB array and therefore unnest must be used rather than jsonb_array_elements. Also have a look at ->> operator in the documentation.
The same applies to your second example. It would work if mstore.m_json is a JSONB array and not a Postgres array of JSONB elements.
select m.muuid, m.msid, l.item_object, l.pos
from mstore m
cross join lateral unnest(m.m_json) with ordinality l(item_object, pos)
where l.item_object ->> 'dstatus' = 'Delivered';
It would be better to use JSONB data type for column mstore.m_json rather than JSONB[] or - much better - normalize the data design.

Presto how to directly replace an item in array

I have a database that one of the columns is an array containing many information, see below:
{"2":"KJH78","4":"CL","10":"Sell"}
The index 2 stands for the ID.
Now I would like to hash the ID, and directly replace the ID value in the array, using Presto SELECT statement.
I have not found any useful Presto commands that do the job.
What I have
acc_id
acc_id_hash
flds
KJH78
2bjkse879sdf2kk7
{"2":"KJH78", ......}
What I want
acc_id
acc_id_hash
flds
KJH78
2bjkse879sdf2kk7
{"2":"2bjkse879sdf2kk7 ", ......}
Failed attempts
Ignore the result of hash function as the value was manually changed to protect data
1. Using replace function
SELECT element_at(flds, 2) AS acc_id,
xxhash64(to_utf8(element_at(flds, 2))) as acc_id_hash,
replace(flds[2], cast(from_utf8(xxhash64(to_utf8(element_at(flds, 2)))) as varchar)),
flds
FROM mscs_feed
WHERE date_string = '2021-05-26'
and element_at(flds, 2) is not null
limit 10
Result:
acc_id
acc_id_hash
_col2
flds
KJH78
2bjkse879sdf2kk7
KJH78
{"2":"KJH78", ......}
Problem: The flds[2] is varchar, hash is varbinary. The replace function needs both parameters to be the same type. After converting the types, the hash becomes meaningless.
2. Trim the array, then add the hash into it
SELECT element_at(flds, 2) AS acc_id,
xxhash64(to_utf8(element_at(flds, 2))) as acc_id_hash,
slice(flds, 1, cardinality(flds) - 1),
flds
FROM mscs_feed
WHERE date_string = '2021-05-26'
and element_at(flds, 2) is not null
limit 10
Problem: Error line 3: Unexpected parameters (map(integer,varchar), integer, bigint) for function slice. Expected: slice(array(E), bigint, bigint) E
Please let me know if the question is unclear, or more information is needed.
Thanks in advance!

Exclude "grouped" data from query

I have a table that looks like this (simplified):
CREATE TABLE IF NOT EXISTS records (
user_id uuid NOT NULL
ts timestamptz NOT NULL,
op_type text NOT NULL,
PRIMARY KEY (user_id, ts, op_type)
);
I cannot for practical purposes change the PRIMARY KEY.
I'm trying to write a query that gets all records for a given user_id where, for a specific record, the ts and the op_type don't match an array of exclusions.
I'm not exactly sure of the right postgres terminology so let me see if this example makes my constraint clearer:
This array looks something like this (in JavaScript):
var excludes = [
[DATE1, 'OP1'],
[DATE2, 'OP2']
]
If, for a given user id, there are rows that look like this in the database:
ts | op_type
----------------------------+-------------
DATE1 | OP1
DATE2 | OP2
DATE1 | OP3
DATE2 | OP1
OTHER DATE | OP1
OTHER DATE | OP2
Then, with the excludes from above, I'd like to run a query that returns everything EXCEPT or the first two rows since they match exactly.
My attempt was to do this:
client.query(`
SELECT * FROM records
WHERE
user_id = $1
AND (ts, op_type) NOT IN ($2)
`, [userId, excluding])
But I get "input of anonymous composite types is not implemented". I'm not sure how to properly type excluding or if this is even the right way to do this.
The query may look like this
SELECT *
FROM records
WHERE user_id = 'a0eebc999c0b4ef8bb6d6bb9bd380a11'
AND (ts, op_type) NOT IN (('2016-01-01', 'OP1'), ('2016-01-02', 'OP2'));
so if you want to pass the conditions as a single parameter then excluding should be a string in the format:
('2016-01-01', 'OP1'), ('2016-01-02', 'OP2')
It seems that there is no simple way to pass the condition string into query() as a parameter. You can try to write a function to get the string in the correct format (I'm not a JS developer but this piece of code seems to work well):
excluding = function(exc) {
var s = '(';
for (var i = 0; i < exc.length; i++)
s = s+ '(\''+ exc[i][0]+ '\',\''+ exc[i][1]+ '\'),';
return s.slice(0, -1)+ ')';
};
var excludes = [
['2016-01-01', 'OP1'],
['2016-01-02', 'OP2']
];
// ...
client.query(
'SELECT * FROM records '+
'WHERE user_id = $1 '+
'AND (ts, op_type) NOT IN ' + excluding(excludes),
[userId])

JSONStore difference between 'number' and 'integer' in searchFields

I have a question about JSONStore searchFields.
If I use number as the searchFields key and try to find data by WL.JSONStore.find method with 0 as the query, It will hit all data (not filtered).
With the integer of the case above works fine.
What's the difference between number and integer?
JSONStore uses SQLite to persist data, you can read about SQLite Data Types here. The short answer is number will store data as REAL while integer will store data as INTEGER.
If you create a collection called nums with one searchField called num of type number
var nums = WL.JSONStore.initCollection('nums', {num: 'number'}, {});
and add some data:
var len = 5;
while (len--) {
nums.add({num: len});
}
then call find with the query: {num: 0}
nums.find({num: 0}, {onSuccess: function (res) {
console.log(JSON.stringify(res));
}})
you should get back:
[{"_id":1,"json":{"num":4}},{"_id":2,"json":{"num":3}},{"_id":3,"json":{"num":2}},{"_id":4,"json":{"num":1}},{"_id":5,"json":{"num":0}}]
Notice that you got back all the documents you stored (num = 4, 3, 2, 1, 0).
If you look at the .sqlite file:
$ cd ~/Library/Application Support/iPhone Simulator/6.1/Applications/[id]/Documents
$ sqlite3 jsonstore.sqlite
(The android file should be under /data/data/com.[app-name]/databases/)
sqlite> .schema
CREATE TABLE nums ( _id INTEGER primary key autoincrement, 'num' REAL, json BLOB, _dirty REAL default 0, _deleted INTEGER default 0, _operation TEXT);
Notice the data type for num is REAL.
Running a query the same query used in the find function:
sqlite> SELECT * FROM nums WHERE num LIKE '%0%';
1|4.0|{"num":4}|1363326259.80431|0|add
2|3.0|{"num":3}|1363326259.80748|0|add
3|2.0|{"num":2}|1363326259.81|0|add
4|1.0|{"num":1}|1363326259.81289|0|add
5|0.0|{"num":0}|1363326259.81519|0|add
Notice 4 is stored as 4.0 and JSONStore's queries always use LIKE, any num with a 0 will match the query.
If you use integer instead:
var nums = WL.JSONStore.initCollection('nums', {num: 'integer'}, {});
Find returns:
[{"_id":5,"json":{"num":0}}]
The schema shows that num has an INTEGER data type:
sqlite> .schema
CREATE TABLE nums ( _id INTEGER primary key autoincrement, 'num' INTEGER, json BLOB, _dirty REAL default 0, _deleted INTEGER default 0, _operation TEXT);
sqlite> SELECT * FROM nums WHERE num LIKE '%0%';
5|0|{"num":0}|1363326923.44466|0|add
I skipped some of the onSuccess and all the onFailure callbacks for brevity.
The actual difference between a JSON number and integer is
defining {age: 'number'} indexes 1 as 1.0,
while defining{age: 'integer'} indexes 1 as 1.
Hope you understand