JPA: How to get the sum of a nested jsonb entity - postgresql

I am trying to return an object the sum of points, which is an integer, which belongs to an object, foo of type jsonb, which is being referenced by the main object user, however, the sum keeps ending up as a null value, even though I can see that is not the case when i just return everything from the table.
Here is what I have:
#Query(
nativeQuery = true,
value =
"SELECT u.user_id, "
+ "SUM(CAST(u.foo->>'points' AS INTEGER)) AS totalActivePoints "
+ "FROM banpoint.user u "
+ "WHERE u.user_id = :userId "
+ "AND (u.end_date IS NULL OR u.end_date > now()) "
+ "GROUP BY u.user_id "
)
Optional<Map<String, Object>> getPointSum(long userId);
My tables look like
CREATE TABLE IF NOT EXISTS foo
(
type TEXT NOT NULL PRIMARY KEY,
points INTEGER NOT NULL,
...
);
CREATE TABLE IF NOT EXISTS user
(
id BIGSERIAL NOT NULL PRIMARY KEY,
user_id BIGINT NOT NULL,
start_date TIMESTAMP DEFAULT NULL,
end_date TIMESTAMP DEFAULT NULL,
foo JSONB NOT NULL
);
Sample data from user table as json:
{
"id" : 1,
"userId" : 1234,
"startDate" : 1656668960145,
"endDate" : 1657273760146,
"foo" : {
"type" : "BOTS",
"points" : 23
}
}
}
{
"id" : 2,
"userId" : 1234,
"startDate" : 1656668960204,
"endDate" : 1657878560146,
"foo" : {
"type" : "TRIGGERBOT",
"points" : 14
}
}
}
How can I properly get the sum of this field properly, which belongs to the JSONB object?
Im using PSQL 14
EDIT:
also have tried it the following way, but the result is still null
#Query(
nativeQuery = true,
value =
"SELECT SUM(CAST(u.foo->'foo'->>'points' AS INTEGER)) AS totalActivePoints "
+ "FROM banpoint.user u "
+ "WHERE u.user_id = :userId "
+ "AND (u.end_date IS NULL OR u.end_date > now()) "
+ "GROUP BY u.user_id ")
Integer getPointSum(long userId);
Additionally, returning a List<Object> instead of getting the sum ALSO returns a list with null values in it as well...

Related

How do I access the VALUE using CosmosQueryableExtensions

EFCore Cosmos provider does not implement subquery yet and so I have implemented the query using the following FromRawSql as per this post:
SqlParameter userMasterGuidParam = new("userMasterGuid", userMasterGuid);
SqlParameter statusNewParam = new("statusNew", CaseStatusGuids.New);
SqlParameter statusInProgressParam = new("statusInProgress", CaseStatusGuids.InProgress);
SqlParameter statusOnHoldParam = new("statusOnHold", CaseStatusGuids.OnHold);
const string TICKET_SQL =
#"SELECT * FROM c " +
"WHERE c.StatusGuid IN (#statusNewParam, #statusInProgress, #statusOnHold) " +
"AND EXISTS ( " +
"SELECT VALUE n FROM n IN c.caseservicepartner_collection " +
"WHERE n.PartnerAssignedUserGuid = #userMasterGuid) ";
// Use CosmosQueryableExtensions instead of _context.Cases.FromSqlRaw to avoid ambiguous namespace.
// https://github.com/dotnet/efcore/issues/26502
return CosmosQueryableExtensions
.FromSqlRaw(_contextCosmos.Tickets, TICKET_SQL, statusNewParam, statusInProgressParam, statusOnHoldParam, userMasterGuidParam)
.OrderByDescending(t => t.CreatedDateTime)
.ToListAsync();
When I execute this query in the Cosmos Data Explorer I get a valid result - an array of items.
SELECT * FROM c WHERE c.StatusGuid IN ('63295b5e-de34-4555-b736-408dae18aaa0', '55d05dde-6b71-475f-8ee5-5549e2187423', 'e5267754-d416-4d1f-b42f-700dc5bb13d3') AND EXISTS ( SELECT VALUE n FROM n IN c.caseservicepartner_collection WHERE n.PartnerAssignedUserGuid = 'f3e9dd05-c580-4390-8998-61ce915d2da3')
[
{
"CreatedDateTime": "2022-08-17T08:22:54.017000+00:00",
"CaseNumber": 111,
"AssignedTeamGuid": null,
"TicketTypeGuid": "18ba2bba-557f-4bbd-9b45-029194761980",
...
},
{
...
}
]
However, when I execute this using EFCore, it returns no data. Looking at the EFCore log, it seems to wrap this query in an outer select, as follows:
-- EFCore adds this
SELECT c
FROM (
-- My Query
SELECT * FROM c WHERE c.StatusGuid IN (#statusNewParam, #statusInProgress, #statusOnHold) AND EXISTS ( SELECT VALUE n FROM n IN c.caseservicepartner_collection WHERE n.PartnerAssignedUserGuid = #userMasterGuid)
) c
...which when I plug into the Data Explorer, returns a nested structure like this:
[
{
"c": {
"CreatedDateTime": "2022-08-17T08:22:54.017000+00:00",
"CaseNumber": 111,
"AssignedTeamGuid": null,
"TicketTypeGuid": "18ba2bba-557f-4bbd-9b45-029194761980",
...
}
},
]
I suspect this is why the data is not being returned, perhaps due to a type mismatch.
Is there a way to fix this so the array is returned at the root, rather than nested within the c value?
Thanks
UPDATE
I removed the SqlParameters and instead used the string format-like option to pass parameters. That sorted out my issue and date is being returned now.
string TICKET_SQL =
"SELECT * FROM c " +
"WHERE c.StatusGuid IN ({0}, {1}, {2}) " +
"AND EXISTS (SELECT VALUE n FROM n IN c.caseservicepartner_collection WHERE n.PartnerAssignedUserGuid = {3})";
return CosmosQueryableExtensions
.FromSqlRaw(contextCosmos.Tickets, TICKET_SQL, CaseStatusGuids.New, CaseStatusGuids.InProgress, CaseStatusGuids.OnHold, userMasterGuid)
.OrderByDescending(t => t.CreatedDateTime);
.ToList();

UPDATE SET with different value for each row

I have python dict with relationship between elements and their values. For example:
db_rows_values = {
<element_uuid_1>: 12,
<element_uuid_2>: "abc",
<element_uuid_3>: [123, 124, 125],
}
And I need to update it in one query. I made it in python through the query generation loop with CASE:
sql_query_elements_values_part = " ".join([f"WHEN '{element_row['element_id']}' "
f"THEN '{ujson.dumps(element_row['value'])}'::JSONB "
for element_row in db_row_values])
query_part_elements_values_update = f"""
elements_value_update AS (
UPDATE m2m_entries_n_elements
SET value =
CASE element_id
{sql_query_elements_values_part}
ELSE NULL
END
WHERE element_id = ANY(%(elements_ids)s::UUID[])
AND entry_id = ANY(%(entries_ids)s::UUID[])
RETURNING element_id, entry_id, value
),
But now I need to rewrite it in plpgsql. I can pass db_rows_values as array of ROWTYPE or as json but how can I make something like WHEN THEN part?
Ok, I can pass dict as JSON, convert it to rows with json_to_recordset and change WHEN THEN to SET value = (SELECT.. WHERE)
WITH input_rows AS (
SELECT *
FROM json_to_recordset(
'[
{"element_id": 2, "value":"new_value_1"},
{"element_id": 4, "value": "new_value_2"}
]'
) AS x("element_id" int, "value" text)
)
UPDATE table1
SET value = (SELECT value FROM input_rows WHERE input_rows.element_id = table1.element_id)
WHERE element_id IN (SELECT element_id FROM input_rows);
https://dbfiddle.uk/?rdbms=postgres_14&fiddle=f8b6cd8285ec7757e0d8f38a1becb960

Convert individual postgres jsonb array elements to row elements

I have to query a table with 2 columns, id and content. Id is just a uuid and the content column looks like
{
"fields": [
{
"001": "mig00004139229"
},
{
"856": {
"ind1": " ",
"ind2": " ",
"subfields": [
{
"u": "https://some.domain.com"
},
{
"z": "some text"
}
]
}
},
{
"999": {
"subfields": [
{
"i": "81be1acf-11df-4d13-a5c6-4838e3a808ee"
},
{
"s": "3a6aa357-8fd6-4451-aedc-13453c1f2296"
}
]
}
}
]
}
I need to select the id, 001, and 856 elements where the subfield "u" domain matches a string "domain.com" so the output would be
id
001
856
81be1acf-11df-4d13-a5c6-4838e3a808ee
mig00004139229
https://some.domain.com
If this were a flat table, the query would correspond with "select id, 001, 856 from table where 856 like '%domain.com%'"
I can select individual columns based on the criteria I need, but they appear in separate rows except the id which appears with any other individual field in a regular select statement. How would I get the other fields to appear in the same row since it's part of the same record?
Unfortunately, my postgres version doesn't support jsonb_path_query, so I've been trying something along the lines of:
SELECT id, jsonb_array_elements(content -> 'fields') -> '001',
jsonb_array_elements(content -> 'fields') -> '856' -> 'subfields'
FROM
mytable
WHERE....
This method returns the data I need, but the individual elements arrive on separate rows with the with the id in the first column and nulls for every element that is neither the 001 nor 856 e.g.
id
001
856
id_for_first_record
001_first_record
null
id_for_first_record
null
null
id_for_first_record
null
null
id_for_first_record
null
856_first_record
id_for_second_record
001_second_record
null
id_for_second_record
null
null
id_for_second_record
null
null
id_for_second_record
null
856_second_record
Usable, but clunky so I'm looking for something better
I think my query can help you. There are different ways to resolve this, I am not sure if this is the best approach.
I use jsonb_path_query() function with the path for the specified JSON value.
SELECT
id,
jsonb_path_query(content, '$.fields[*]."001"') AS "001",
jsonb_path_query(content, '$.fields[*]."856".subfields[*].u') AS "856"
FROM t
WHERE jsonb_path_query_first(content, '$.fields[*]."856".subfields[*].u' )::text ilike '%domain%';
Output:
id
001
856
81be1acf-11df-4d13-a5c6-4838e3a808ee
"mig00004139229"
"https://some.domain.com"
UPDATED: because of Postgresql version is prior to 12.
You could try something like this, but I think there must be a better approach:
SELECT
t.id,
max(sq1."001") AS "001",
max(sq2."856") AS "856"
FROM t
INNER JOIN (SELECT id, (jsonb_array_elements(content -> 'fields') -> '001')::text AS "001" FROM t) AS sq1 ON t.id = sq1.id
INNER JOIN (SELECT id, (jsonb_array_elements(jsonb_array_elements(content -> 'fields') -> '856' -> 'subfields') -> 'u')::text AS "856" FROM t) AS sq2 ON t.id = sq2.id
WHERE sq2."856" ilike '%domain%'
GROUP BY t.id;

Postgres json select

Let's say that I have an incoming JSON message to my app:
{
line_nbr : 1,
value: 10
},
{
line_nbr : 2,
value: 30
},
]
Is it possible to perform the following selection in postgres :
SELECT JsonObject.value, qty from table_x where id in JsonObjects.line_nbr
In other words join on the incoming JSON object
with your_js as (
select (value->>'line_nbr')::int as line_nbr
from jsonb_array_elements('JsonObjects'::jsonb) as je
)
select line_nbr, qty
from table_x
join your_js on line_nbr = table_x.id
check here for detail

Convert date in jsonb (Postgres)

I have jsonb column data =
"{"history": [{"endDate": "30.06.2015", "classname": "Class A", "startDate": "2010-04-01", "numberAction": "0016", "positionName": "Teacher"},
{"endDate": "31.06.2010", "classname": "Class A", "startDate": "2005-08-10", "numberAction": "0015", "positionName": "Student"},
{"endDate": "2005.08.09", "classname": "Class B", "startDate": "2005-02-21", "numberAction": "0014", "positionName": " Student "}]}"
As you can see, the dates of the "endDate" in the array are not correct. Please tell me how they can be converted to the format YYYY-MM-DD?
My idle attempt:
UPDATE table
SET data = jsonb_set(data, '{endDate}', to_date('{endDate}', 'YYYY-MM-DD'), false)
WHERE id = 'UUID';
Answer =
update table
set data = data - 'history' ||
jsonb_build_object
( 'history'
, ( select jsonb_agg
( case when aa.value ->> 'endDate' like '%.%' then
aa.value - 'endDate' || jsonb_build_object
( 'endDate'
, to_date(aa.value ->> 'endDate','dd.mm.yyyy')
)
else
aa.value
end
)
from jsonb_array_elements(data -> 'history') as aa
)
)
WHERE uuid = 'UUID'
and exists ( select *
from jsonb_array_elements(data -> 'history') as aa
where aa.value ->> 'endDate' like '%.%'
);