Null objects while using Coalesce and duplicate values while joining - mysql

[
{
"permissions": [
{
"name": "CREATE",
"id": 1
},
{
"name": "DELETE",
"id": 4
}
],
"roles": [
{
"name": "ADMIN",
"permission": [
{
"name": "CREATE",
"id": 1
},
{
"name": "UPDATE",
"id": 2
},
{
"name": "GET",
"id": 3
},
{
"name": "DELETE",
"id": 4
}
],
"id": 1
},
{
"name": "ADMIN",
"permission": [
{
"name": "CREATE",
"id": 1
},
{
"name": "UPDATE",
"id": 2
},
{
"name": "GET",
"id": 3
},
{
"name": "DELETE",
"id": 4
}
],
"id": 1
}
],
"id": 1,
"username": "raj#100"
},
{
"permissions": [
{
"name": null,
"id": null
}
],
"roles": [
{
"name": "USER",
"permission": [
{
"name": "GET",
"id": 3
}
],
"id": 3
}
],
"id": 2,
"username": "ram145"
}
]
As you can see from the above output the in roles the ADMIN is repeated twice and in the second users has no permissions so he should have an empty array but the output is with the permission object with all its values empty
This is the jooq statement which is executed :
public Object findAllUsers(String role, String permission) {
SelectOnConditionStep<Record1<JSON>> query = dslContext.select(
jsonObject(
key("id").value(USER.ID),
key("fullName").value(USER.FULL_NAME),
key("username").value(USER.USERNAME),
key("email").value(USER.EMAIL),
key("mobile").value(USER.MOBILE),
key("isActive").value(USER.IS_ACTIVE),
key("lastLoggedIn").value(USER.LAST_LOGGED_IN),
key("profileImage").value(USER.PROFILE_IMAGE),
key("roles").value(
coalesce(
jsonArrayAgg(
jsonObject(
key("id").value(ROLE.ID),
key("name").value(ROLE.NAME),
key("permission").value(
coalesce(
select(
jsonArrayAgg(
jsonObject(
key("id").value(PERMISSION.ID),
key("name").value(PERMISSION.NAME)
)
)
).from(ROLE_PERMISSION)
.leftJoin(PERMISSION)
.on(PERMISSION.ID.eq(ROLE_PERMISSION.PERMISSION_ID))
.where(ROLE_PERMISSION.ROLE_ID.eq(ROLE.ID))
.orderBy(PERMISSION.NAME.asc()),
jsonArray()
)
)
)
),
jsonArray()
)
),
key("permissions").value(
coalesce(
jsonArrayAgg(
jsonObject(
key("id").value(PERMISSION.ID),
key("name").value(PERMISSION.NAME)
)
),
jsonArray()
)
)
)
).from(USER)
.leftJoin(USER_ROLE).on(USER.ID.eq(USER_ROLE.USER_ID))
.leftJoin(ROLE).on(USER_ROLE.ROLE_ID.eq(ROLE.ID))
.leftJoin(USER_PERMISSION).on(USER.ID.eq(USER_PERMISSION.USER_ID))
.leftJoin(PERMISSION).on(USER_PERMISSION.PERMISSION_ID.eq(PERMISSION.ID));
if (role != null) {
query.where(ROLE.NAME.eq(role));
}
if (permission != null) {
query.where(PERMISSION.NAME.eq(role));
}
return query.groupBy(USER.ID)
.orderBy(USER.ID.asc())
.fetch().into(JSONObject.class);
}
Is there any way to fix this problem?

Why the duplicates?
Your join graph creates a cartesian product between the two "nested collections" ROLE and PERMISSION. You can't remove that cartesian product with GROUP BY alone, that works only if you join a single to-many relationship.
Instead, you can write subqueries like this (you already did this correctly for the ROLE_PERMISSION relationship):
dslContext.select(jsonObject(
key("id").value(USER.ID),
key("username").value(USER.USERNAME),
key("roles").value(coalesce(field(
select(jsonArrayAgg(jsonObject(
key("id").value(ROLE.ID),
key("name").value(ROLE.NAME),
key("permission").value(coalesce(field(
select(coalesce(jsonArrayAgg(jsonObject(
key("id").value(PERMISSION.ID),
key("name").value(PERMISSION.NAME)
)), jsonArray()))
.from(ROLE_PERMISSION)
.join(PERMISSION)
.on(PERMISSION.ID.eq(ROLE_PERMISSION.PERMISSION_ID))
.where(ROLE_PERMISSION.ROLE_ID.eq(ROLE.ID))
.orderBy(PERMISSION.NAME.asc())
), jsonArray()))
)))
.from(USER_ROLE)
.join(ROLE)
.on(USER_ROLE.ROLE_ID.eq(ROLE.ID))
.where(USER_ROLE.USER_ID.eq(USER.ID))
), jsonArray())),
key("permissions").value(coalesce(field(
select(coalesce(jsonArrayAgg(jsonObject(
key("id").value(PERMISSION.ID),
key("name").value(PERMISSION.NAME)
)))
.from(USER_PERMISSION)
.join(PERMISSION)
.on(USER_PERMISSION.PERMISSION_ID.eq(PERMISSION.ID))
.where(USER_PERMISSION.USER_ID.eq(USER.ID))
), jsonArray()))
))
.from(USER)
.orderBy(USER.ID.asc())
.fetch().into(JSONObject.class);
Join vs semi join
After you edited your question to become a slightly different question, the point you were trying to make is that you want to filter the USER table by some ROLE or PERMISSION that they must have. You can't achieve this with JOIN alone (unless you're happy with the duplicates). The answer I gave doesn't change. If you're joining multiple to-many relationships, you'll get cartesian products.
So, instead, why not semi join them? Either with jOOQ's synthetic SEMI JOIN syntax, or manually using EXISTS or IN, e.g.
.where(role != null
? exists(selectOne()
.from(USER_ROLE)
.where(USER_ROLE.role().NAME.eq(role))
)
: noCondition()
)
.and(permission != null
? exists(selectOne()
.from(USER_PERMISSION)
.where(USER_PERMISSION.permission().NAME.eq(permission))
)
: noCondition()
)
This is using the implicit join syntax, which is optional, but I think it does simplify your query.

Related

N1QL Query to join array fields with an array in another document

I have 3 documents types :
Data
{
"formId": "7508e7b2-bcf7-437b-a206-9fee87256d01",
"dataValues": [
{
"questionId": "Someguid123",
"questionValue": "Question1"
},
{
"questionId": "Someguid",
"questionValue": "Question2"
},
{
"questionId": "AnotherGuid",
"questionValue": "Question3"
}
],
"lastUpdateDateTime": "2023-01-04T10:56:49Z",
"type": "Data",
"templateId": "41e4cc2c-e9fb-4bdc-9dc2-af19e5988984",
"creationDateTime": "2022-12-28T11:20:46Z"
}
AttachedDocuments
{
"id": "AttachedDocuments::77961b70-2071-4410-837a-436c908a4fa5",
"lastUpdateDateTime": "2023-01-05T11:47:17Z",
"documents": [
{
"isUploaded": false,
"id": "DocumentMetadata::001",
"isDeleted": false,
"type": "photo",
"parentId": "Someguid123"
},
{
"isUploaded": false,
"id": "DocumentMetadata::002",
"isDeleted": false,
"type": "photo",
"parentId": "Someguid123"
}
],
"type": "AttachedDocuments",
"parentDocId": "MyFormData::7508e7b2-bcf7-437b-a206-9fee87256d01",
"creationDateTime": "2022-12-28T11:20:46Z"
}
DocumentMetaData
{
"id": "DocumentMetadata::001",
"type": "DocumentMetadata",
"name": "MyForm_001.png",
"documentId": "549c4da2-ad3a-4f92-bfa2-019750a11007",
"contentType": "FILE",
"parentDocumentId": "AttachedDocuments::77961b70-2071-4410-837a-436c908a4fa5",
"creationDateTime": "2023-01-04T10:56:49Z"
},
{
"id": "DocumentMetadata::002",
"type": "DocumentMetadata",
"name": "MyForm_002.png",
"documentId": "549c4da2-ad3a-4f92-bfa2-019750a11007",
"contentType": "FILE",
"parentDocumentId": "AttachedDocuments::77961b70-2071-4410-837a-436c908a4fa5",
"creationDateTime": "2023-01-04T10:56:49Z"
}
Every Data type document has only one AttachedDocuments document with parentDocId* field set to formId field of Data document.
If items in Data.dataValues has a document attached to it, AttachedDocuments.documents array have items with parentId field set to Data.dataValues[i].questionId.
Also every AttachedDocuments.documents[i] item has a DocumentMetadata document with id of AttachedDocuments.documents[i].id field.
I want to have a query which returns all Data.dataValues as an array but containing a field links that contains the DocumentMetadata.name field like below :
[
{
"questionId": "Someguid123",
"questionValue": "Question1",
"links": ["MyForm_001.png", "MyForm_002.png"]
},
{
"questionId": "Someguid",
"questionValue": "Question2"
},
{
"questionId": "AnotherGuid",
"questionValue": "Question3"
}
]
I tried unnest clause but couldn't output datavalues items without documents. How should I write the query to include those also?
Thank you
Assuming you have a 1:1 relationship between Data & AttachedDocuments, you can try:
CREATE SCOPE default.f;
CREATE COLLECTION default.f.Data;
CREATE COLLECTION default.f.AttachedDocuments;
CREATE COLLECTION default.f.DocumentMetaData;
CREATE INDEX ix1 ON default.f.DocumentMetaData(id);
SELECT dataValues.questionId, dataValues.questionValue, links
FROM default.f.Data join default.f.AttachedDocuments ON "MyFormData::"||Data.formId = AttachedDocuments.parentDocId
UNNEST Data.dataValues AS dataValues
LET links = (SELECT RAW DocumentMetaData.name
FROM default.f.DocumentMetaData
WHERE DocumentMetaData.parentDocumentId = AttachedDocuments.id
AND id IN ARRAY a.id FOR a IN AttachedDocuments.documents WHEN a.parentId = dataValues.questionId END
)
;
If you have a 1:n relationship between Data & AttachedDocuments but the attachments for a single question are wholly in a single attached document:
CREATE INDEX ix2 ON default.f.AttachedDocuments(parentDocId);
CREATE INDEX ix3 ON default.f.AttachedDocuments(id);
SELECT dataValues.questionId, dataValues.questionValue, links
FROM default.f.Data join default.f.AttachedDocuments ON "MyFormData::"||Data.formId = AttachedDocuments.parentDocId
UNNEST Data.dataValues as dataValues
LET links = (SELECT RAW md.name
FROM default.f.AttachedDocuments ad JOIN default.f.DocumentMetaData md ON ad.id = md.parentDocumentId
UNNEST ad.documents d
WHERE ad.parentDocId = "MyFormData::"||Data.formId
AND d.id = md.id
AND d.parentId = dataValues.questionId
)
WHERE ANY dv IN AttachedDocuments.documents SATISFIES dv.parentId = dataValues.questionId END
;
If attachments for a single question can be spread over multiple attached documents, add a DISTINCT to the above statement.
HTH.
(You can use the same logic without collections adding appropriate aliasing and type field filtering.)

MySQL CASE IGNORE command if condition is false

I'm trying to extract a JSON from a table, my code is something like this:
SELECT
JSON_ARRAY(
JSON_OBJECT(
'id', studant.id,
'student', student.name,
'best_grades',
JSON_ARRAYAGG(
CASE
WHEN class.test_grade = A
THEN
JSON_OBJECT(
'id', class.id,
'class', class.name,
'test_grade', class.test_grade
)
END
)))
It works pretty well, it adds a new object every time the condition is true, but the problem is, when test_grade is different than A it still adding an object, but with null as value, it gets like this:
[{
"id": 1,
"student": "Adam",
"best_grades": [
{
"id": 1,
"class": "Math",
"test_grade": "A"
},
{
"id": 2,
"class": "Biology",
"test_grade": "A"
},
null,
null
]
},
{
"id": 2,
"student": "Susy",
"best_grades": [
{
"id": 1,
"class": "Math",
"test_grade": "A"
},
null,
{
"id": 3,
"class": "History",
"test_grade": "A"
},
null
]
},
{
"id": 3,
"student": "Max",
"best_grades": [
null,
{
"id": 2,
"class": "Biology",
"test_grade": "A"
},
{
"id": 3,
"class": "History",
"test_grade": "A"
},
null
]
}
]
What I need is, in case the condition is not true the program just skip to the next object instead of returning null and creating a new object like that.
Basically I want it to IGNORE, SKIP, PASS or DO NOTHING when the grade is different than A, someone told me to do something like
CASE
WHEN class.test_grade = A
THEN
JSON_OBJECT(
'id', class.id,
'class', class.name,
'test_grade', class.test_grade
)
ELSE
BEGIN
END
END CASE
But it doesn't work and says that "'END' is not valid in this position";
Is there a way to make MYSQL ignore the command if the condition is false?

jslt access parent field in for expression

Hi, I want to use jslt to transform json , but happen an unsolvable problem.
The input json data like this
{
"user_id": "001",
"friends": [{
"friend_id": "002"
}, {
"friend_id": "003"
}, {
"friend_id": "004"
}]
}
Then , what output json data I expected like the follow :
[{
"user_id": "001",
"friend_id": "002"
}, {
"user_id": "001",
"friend_id": "003"
}, {
"user_id": "001",
"friend_id": "004"
}]
In jslt expression , I use expression of for to traverse the array field friends :
[
for (.friends) {
"user_id": .user_id,
"friend_id": .friend_id
}
]
However , the treansform result can't get field user_id
[{
"friend_id": "002"
}, {
"friend_id": "003"
}, {
"friend_id": "004"
}]
How can I access field user_id out of the scope related array field friends ?
Looking forward for your help, thanks !
The other answer is correct, but more complex than it needs to be. This is enough:
let user_id = (.user_id)
[ for (.friends) { "user_id": $user_id , "friend_id" : .friend_id } ]
Note that if you really want to report errors you could do it like this:
if (.user_id and .friends)
let user_id = (.user_id)
[ for (.friends) { "user_id": $user_id , "friend_id" : .friend_id } ]
else if (not(.user_id))
error("user_id field missing")
else
error("friends field missing")
Using error turns this into an exception at the Java level.
The reason the parent operator is not supported is that Jackson doesn't have a parent pointer in its nodes. That's a performance feature, because it means the node can be reused several places, saving CPU and memory.
You'll need to use a variable for the user_id, which you can then reference in the loop.
The following should fit your requirement:
if (.user_id)
let user = .user_id
if (.friends)
[
for (.friends) {
"user_id": $user,
"friend_id": .friend_id
}
]
else
error("missing key 'friends'")
else
error("missing key 'user_id'")

sub document under main document how to get a listing with pagination using couchbase(N1QL) query

any one can help me how to get the sub document List with pagination
i just give a sample example :
{
"accessories": [`
{
"data": {
"name": "TEST",
"updated_at": "2020-03-27T16:16:20.818Z"
},
"id": "56e83ea1-042e-47e0-85f8-186189c37426"
}
],
"calibration_reports": [`
{
"data": {
"deleted_at": "",
"frm27_equipment": [
"test_cat1"
],
"frm27_link": [
"yes"
],
"frm27_submit": null,
"updated_at": "2020-03-30T10:24:52.703Z"
},
"id": "e4c8b1b4-7f37-46db-a49d-bca74482b968"
},
{
"data": {
"deleted_at": "",
"frm27_equipment": [
"test_cat1"
],
"frm27_link": [
"no"
],
"frm27_submit": null,
"updated_at": "2020-03-30T10:34:37.615Z"
},
"id": "445854d6-66bf-4e33-b620-05a5053119a8"
}
],
}
]
}
Here i want to get a calibration_reports list with pagination is it possible ? using couchbase (N1ql Query)
please if any one know, what is the process how to get the list of result with pagination using couchbase(N1QL) query. please help me.
One possible way to go about this is to use UNNEST.
For instance:
SELECT calreports.id
FROM utpal u
UNNEST u.calibration_reports calreports
This would return something like:
[
{ "id": "aaa" },
{ "id": "bbb" },
{ "id": "ccc" },
... etc ...
]
And then you can use normal LIMIT/OFFSET for pagination, like so:
SELECT calreports.id
FROM utpal u
UNNEST u.calibration_reports calreports
LIMIT 50
OFFSET 150;

Issue with cts.jsonPropertyScopeQuery and cts.jsonPropertyValueQuery with data types and field order

I have MarkLogic 9 on my database.
I have created the following documents in my database:
test1.json
{
"users": [
{
"userId": "A",
"value": 0
}
]
}
test2.json
{
"users": [
{
"userId": "A",
"value": "0"
}
]
}
test3.json
{
"users": [
{
"value": 0,
"userId": "A"
}
]
}
test4.json
{
"users": [
{
"value": "0",
"userId": "A"
}
]
}
I have run the following codes and have recorded the results:
cts.uris(“”, null, cts.jsonPropertyScopeQuery(
"users",
cts.andQuery(
[
cts.jsonPropertyValueQuery('userId', "A"),
cts.jsonPropertyValueQuery('value', "0"),
]
)
))
Result: test2.json, test4.json
cts.uris(“”, null, cts.jsonPropertyScopeQuery(
"users",
cts.andQuery(
[
cts.jsonPropertyValueQuery('userId', "A"),
cts.jsonPropertyValueQuery('value', 0),
]
)
))
Result: test3.json
I was wondering why test1.json did not return in the 2nd query while test3.json did. They both had the same values for fields but in different order. The order of the fields are different in test2.json and test4.json, however, the query returned both documents. The only difference between the 2 pairs that I can think of is that there are 2 data types for the field “value”, integer and string.
How would I go about resolving this issue?
https://docs.marklogic.com/cts.jsonPropertyValueQuery shows the value to match as an array.
If you want to keep the variants in data, maybe you can try something on the query side like cts.jsonPropertyValueQuery('value', ["0", 0])