Couchbase N1QL query sum from sub document array - couchbase

I have the following document model in my couchbase db
{
type:"account"
id : "123",
transactions: [
{
type : "credit",
value : 100
},
{
type : "debit",
value : 10
}
]
}
How do i query all the account Ids and their sum of all credits ?

Using AS ARRAY functions https://docs.couchbase.com/server/6.0/n1ql/n1ql-language-reference/arrayfun.html
SELECT d.id,
ARRAY_SUM(ARRAY v.`value` FOR v IN d.transactions WHEN v.type = "credit" END) AS s
FROM default AS d
WHERE d.type = "account";
OR
Using subquery expression https://docs.couchbase.com/server/6.0/n1ql/n1ql-language-reference/subqueries.html
SELECT d.id,
(SELECT RAW SUM(d1.`value`)
FROM d.transactions AS d1
WHERE d1.type = "credit")[0] AS s
FROM default AS d
WHERE d.type = "account";

Related

JSON object: Query a value from unkown node based on a condition

I'm trying to query two values (DISCOUNT_TOTAL and ITEM_TOTAL) from a JSON object in a PostgreSQL database. Take the following query as reference:
SELECT
mt.customer_order
totals -> 0 -> 'amount' -> centAmount DISCOUNT_TOTAL
totals -> 1 -> 'amount' -> centAmount ITEM_TOTAL
FROM
my_table mt
to_jsonb(my_table.my_json -> 'data' -> 'order' -> 'totals') totals
WHERE
mt.customer_order in ('1000001', '1000002')
The query code works just fine, the big problem is that, for some reason out of my control, the values DISCOUNT_TOTAL and ITEM_TOTAL some times change their positions in the JSON object from one customer_order to other:
JSON Object
So i cannot aim to totals -> 0 -> 'amount' -> centAmount assuming that it contains the value related to type : DISCOUNT_TOTAL (same for type: ITEM_TOTAL). Is there any work around to get the correct centAmount for each type?
Use a path query instead of hardcoding the array positions:
with sample (jdata) as (
values (
'{
"data": {
"order": {
"email": "something",
"totals": [
{
"type": "ITEM_TOTAL",
"amount": {
"centAmount": 14990
}
},
{
"type": "DISCOUNT_TOTAL",
"amount": {
"centAmount": 6660
}
}
]
}
}
}'::jsonb)
)
select jsonb_path_query_first(
jdata,
'$.data.order.totals[*] ? (#.type == "DISCOUNT_TOTAL").amount.centAmount'
) as discount_total,
jsonb_path_query_first(
jdata,
'$.data.order.totals[*] ? (#.type == "ITEM_TOTAL").amount.centAmount'
) as item_total
from sample;
db<>fiddle here
EDIT: In case your PostgreSQL version does not support json path queries, you can do it by expanding the array into rows and then doing a pivot by case and sum:
with sample (order_id, jdata) as (
values ( 1,
'{
"data": {
"order": {
"email": "something",
"totals": [
{
"type": "ITEM_TOTAL",
"amount": {
"centAmount": 14990
}
},
{
"type": "DISCOUNT_TOTAL",
"amount": {
"centAmount": 6660
}
}
]
}
}
}'::jsonb)
)
select order_id,
sum(
case
when el->>'type' = 'DISCOUNT_TOTAL' then (el->'amount'->'centAmount')::int
else 0
end
) as discount_total,
sum(
case
when el->>'type' = 'ITEM_TOTAL' then (el->'amount'->'centAmount')::int
else 0
end
) as item_total
from sample
cross join lateral jsonb_array_elements(jdata->'data'->'order'->'totals') as a(el)
group by order_id;
db<>fiddle here

How do I most optimally do this N1QL SELECT query?

Lets say I have a bunch of documents of type CONTACT:
{
"contactId": "V1234",
"contactFirstName": "A",
"contactLastName": "N",
"emailAddress": "12345",
"invitationId": "IVT:123",
"documentType": "CONTACT"
}
And for each contact document there is a EMAIL document and an INVITATION document:
EML:12345
{
"emailId": "12345",
"emailAddress" = ["abc#gmail.com"]
}
IVT:123
{
"invitationId": "IVT:123",
"invitationStatus": ["REGISTERED"]
}
Lets say I have an array of contactIds : ["V1234", "V2345" ... ]
How would I write a SELECT query for each contactId in that list and select contactFirstName, contactLastName, eml.emailAddress.[0], invitation.status.[0] (only if the status is of type "REGISTERED", or "INVITED".) and limit it to 10 values.
I was thinking something like:
SELECT DISTINCT
contact.contactFirstName as `contactFirstName`,
contact.contactLastName as `contactLastName`,
eml.emailAddress as `emailAddress`,
ivt.status[0].status AS invitationStatus
from
`contact-services` AS contact
INNER JOIN `contact-services` AS eml ON
CONCAT("EML",':',contact.contactEmailAddressIds[0]) =meta(eml).id
INNER JOIN `contact-services` AS ivt ON
contact.invitationId =meta(ivt).id
WHERE
contact.documentType="PERSON_CONTACT"
AND
ivt.status[0].status IN ["INVITED", "REGISTERED"]
and
contact.contactId IN ["V8459243"]
CREATE INDEX ix1 ON `contact-services` (contactId, emailAddress, invitationId, contactFirstName, contactLastName)
WHERE documentType = "CONTACT";
SELECT c.contactFirstName,
c.contactLastName,
(SELECT RAW e.emailAddress[0]
FROM contact-services` AS e USE KEYS ("EML:"||c.emailAddress))[0] AS emailAddress,
invt AS invitationStatus
FROM `contact-services` AS c
LET invt = (SELECT RAW i.invitationStatus
FROM `contact-services` AS i USE KEYS c.invitationId
WHERE ANY v IN i.invitationStatus SATISFIES v IN ["INVITED", "REGISTERED"] END)[0]
WHERE c.documentType = "CONTACT"
AND c.contactId IN ["V8459243"] AND
invt IS NOT MISSING
LIMIT 10;

How to query nested array with heterogeneous elements in PostgreSQL JSONB column

I have a JSONB field in PostgreSQL (12.5) table Data_Source with the data like that inside:
{
"C1": [
{
"id": 13371,
"class": "class_A1",
"inputs": {
"input_A1": 403096
},
"outputs": {
"output_A1": 403097
}
},
{
"id": 10200,
"class": "class_A2",
"inputs": {
"input_A2_1": 403096,
"input_A2_2": 403095
},
"outputs": {
"output_A2": [
[
403098,
{
"output_A2_1": 403101
},
{
"output_A2_2": [
403099,
403100
]
}
]
],
"output_A2_3": 403102,
"output_A2_4": 403103,
"output_A2_5": 403104
}
}
]
}
Could you please suggest me some SQL query to extract outputs from the JSONB field.
What I need to get as a results:
Output:
name
value
output_A1
403096
output_A2
403098
output_A2_1
403101
output_A2_2
403099
output_A2_2
403100
output_A2_3
403102
output_A2_4
403103
output_A2_5
403104
Any ideas?
Whenever an array is encountered, then JSONB_ARRAY_ELEMENTS(), or an object is encountered, then JSONB_EACH() functions might be applied, along with auxiliary JSONB_TYPEOF() function to determine respective types, consecutively. At the end, combine the results whether of type array or object or not through use of UNION ALL such as
WITH j AS
(
SELECT j2.*, JSONB_TYPEOF(j2.value) AS type
FROM t,
JSONB_EACH(jsdata) AS j0(k,v),
JSONB_ARRAY_ELEMENTS(v) AS j1,
JSONB_EACH((j1.value ->> 'outputs')::JSONB) AS j2
), jj AS
(
SELECT key,j1.*,JSONB_TYPEOF(j1.value::JSONB) AS type
FROM j,
JSONB_ARRAY_ELEMENTS(value) AS j0(v),
JSONB_ARRAY_ELEMENTS(v) AS j1
WHERE type = 'array'
), jjj AS
(
SELECT key,j0.v,JSONB_TYPEOF(j0.v::JSONB) AS type,k
FROM jj,
JSONB_EACH(value) AS j0(k,v)
WHERE type IN ('array','object')
)
SELECT key,value
FROM
(
SELECT key,value,type
FROM j
UNION ALL
SELECT key,value,type
FROM jj
UNION ALL
SELECT k,v,type
FROM jjj
) jt
WHERE type NOT IN ('array','object')
UNION ALL
SELECT k,value
FROM jjj,JSONB_ARRAY_ELEMENTS(v) AS j0
WHERE type IN ('array','object')
Demo

Postgres query on json with empty value

I have a query in JSON to filter out the data based on data present inside JSON field .
Table name: audit_rules
Column name: rule_config (json)
rule_config contains JSON which contain 'applicable_category' as an attribute in it.
Example
{
"applicable_category":[
{
"status":"active",
"supported":"yes",
"expense_type":"Meal",
"acceptable_variation":0.18,
"minimum_value":25.0
},
{
"status":"active",
"supported":"yes",
"expense_type":"Car Rental",
"acceptable_variation":0.0,
"minimum_value":25.0
},
{
"status":"active",
"supported":"yes",
"expense_type":"Airfare",
"acceptable_variation":0.0,
"minimum_value":75
},
{
"status":"active",
"supported":"yes",
"expense_type":"Hotel",
"acceptable_variation":0.0,
"minimum_value":75
}
],
"minimum_required_keys":[
"amount",
"date",
"merchant",
"location"
],
"value":[
0,
0.5
]
}
But some of the rows doesn't have any data or doesn't have the 'applicable_category' attribute in it.
So while running following query i am getting error:
select s.*,j from
audit_rules s
cross join lateral json_array_elements ( s.rule_config#>'{applicable_category}' ) as j
WHERE j->>'expense_type' in ('Direct Bill');
Error: SQL Error [22023]: ERROR: cannot call json_array_elements on a scalar
You can restrict the result to only rows that contain an array:
select j.*
from audit_rules s
cross join lateral json_array_elements(s.rule_config#>'{applicable_category}') as j
WHERE json_typeof(s.rule_config -> 'applicable_category') = 'array'
and j ->> 'expense_type' in ('Meal')

Postgresql json querying in 9.3+ on nested payloads

Using postgresql 9.3 (and the new json awesomness) if I have a simple table named 'races' with a two column description such as:
race-id integer,
race-data json
And the json is a payload for each race is something like
{ "race-time": some-date,
"runners": [ { "name": "fred","age": 30, "position": 1 },
{ "name": "john","age": 29, "position": 3 },
{ "name": "sam","age": 31, "position": 2 } ],
"prize-money": 200 }
How can I query the table for:
1) Races where sam has come 1st
2) Races where sam has come 1st and john has come 2nd
3) Where the number of runners with age greater than 30 is > 5 and prize-money > 5000
My experimentation (particularly in querying a nested array payload) so far has lead to further normalizing the data, i.e. creating a table called runners just to make such queries. Ideally I'd like to use this new fangled json query awesomeness but I can't seem to make heads or tails of it in respective to the 3 simple queries.
You can unwind json into one record and then do your queries as you want (see json functions):
with cte as (
select
race_id,
json_array_elements(r.race_data->'runners') as d,
(r.race_data->>'prize-money')::int as price_money
from races as r
), cte2 as (
select
race_id, price_money,
max(case when (d->>'position')::int = 1 then d->>'name' end) as name1,
max(case when (d->>'position')::int = 2 then d->>'name' end) as name2,
max(case when (d->>'position')::int = 3 then d->>'name' end) as name3
from cte
group by race_id, price_money
)
select *
from cte2
where name1 = 'sam' and name2 = 'john'
sql fiddle demo
It's a bit complicated because of your JSON structure. I think that if you change your structure a bit, your queries could be much simplier:
{
"race-time": some-date,
"runners":
{
"1": {"name": "fred","age": 30},
"2": {"name": "sam","age": 31},
"3": {"name": "john","age": 29}
},
"prize-money": 200
}
you can use ->> and -> operators or json_extract_path_text function to get data you need and then use it in the where clause:
select *
from races as r
where
r.race_data->'runners'->'1'->>'name' = 'sam';
select *
from races as r
where
json_extract_path_text(r.race_data, 'runners','1','name') = 'sam' and
json_extract_path_text(r.race_data, 'runners','2','name') = 'john';
select *
from races as r
where
(r.race_data->>'prize-money')::int > 100 and
(
select count(*)
from json_each(r.race_data->'runners')
where (value->>'age')::int >= 30
) >= 2
sql fiddle demo