Mongo query to split more than two arrays without duplicate result - java

I am trying to build a Mongo query for the below-mentioned record,
[
{
"_id": "1",
"source": "Source1",
"customer": "customer1",
"models": [
{ "modelid": "modelid123" },
{ "modelid": "modelid124" }
],
"accounts": [
{
"account": "acc1",
"models": [
{ "modelid": "modelid123" },
{ "modelid": "modelid124" }
],
"packages": [
{
"package": "p1",
"models": [
{ "modelid": "modelid123" },
{ "modelid": "modelid125" }
]
}
]
},
{
"account": "acc2",
"models": [
{ "modelid": "modelid123" },
{ "modelid": "modelid126" }
],
"packages": [
{
"package": "p2",
"models": [
{ "modelid": "modelid123" }
]
}
]
}
]
}
]
My expected o/p is
source customer account package
Source1 customer1
Source1 customer1 acc1
Source1 customer1 acc1 p1
Source1 customer1 acc2
Source1 customer1 acc2 p2
As modelid123 is present at all the above levels.
I tried using $unwind aggregate functions to achieve the above but I am getting duplicate rows.
db.collection.aggregate([
{ $unwind: { path: "$models", preserveNullAndEmptyArrays: true } },
{ $unwind: { path: "$accounts", preserveNullAndEmptyArrays: true } },
{ $unwind: { path: "$accounts.models", preserveNullAndEmptyArrays: true } },
{ $unwind: { path: "$accounts.packages.models", preserveNullAndEmptyArrays: true } },
{
$match: {
"$and": [ { "source": "Accurate" } ],
"$or": [
{ "models.modelId": { "$in": [ "model1234" ] } },
{ "accounts.models.modelId": { "$in": [ "model1234" ] } },
{ "accounts.packages.models.modelId": { "$in": [ "model1234" ] } }
]
}
}
])
`Problem here is that I can not restructure the data. I can do this on the code side using Java but I want to achieve pagination also. I am using Spring data mongodb to connect.

You can use $group to deal with duplicae. I think you can learn from my answer and correct your own one.
db.collection.aggregate([
{ $unwind: { path: "$models", preserveNullAndEmptyArrays: true } },
{ $unwind: { path: "$accounts", preserveNullAndEmptyArrays: true } },
{ $unwind: { path: "$accounts.models", preserveNullAndEmptyArrays: true } },
{ $unwind: { path: "$accounts.packages", preserveNullAndEmptyArrays: true } },
{ $unwind: { path: "$accounts.packages.models", preserveNullAndEmptyArrays: true } },
{
$project: {
_id: 0,
source: "$source",
customer: "$customer",
account: {
$cond: {
if: { $eq: [ "$models.modelid", "modelid123" ] },
then: "$accounts.account",
else: null
}
},
package: {
$cond: {
if: {
$and: [
{ $eq: [ "$models.modelid", "modelid123" ] },
{ $eq: [ "$accounts.packages.models.modelid", "modelid123" ] }
]
},
then: "$accounts.packages.package",
else: null
}
}
}
},
{
$group: {
_id: "$$ROOT",
count: { $sum: 1 }
}
},
{
$replaceWith: "$_id"
}
])
https://mongoplayground.net/p/u-cMvnuao1t

Related

MongoDB : fields are excluded when invoke multiple lookup stages

db={
"dashboard": [
{
"_id": "dashboard1",
"name": "test",
"user": 1
}
],
"templatefolders": [
{
"dashboardId": "dashboard1",
"folderId": "folder123",
"name": "folder",
"region": "XXX"
}
],
"folders": [
{
"_id": "folder123"
}
],
"user": [
{
"_id": 1,
"name": "alaa"
}
],
}
this is my function:
db.dashboard.aggregate([
{
"$lookup": {
"from": "templatefolders",
"localField": "_id",
"foreignField": "dashboardId",
"as": "joinDashboard"
}
},
{
"$lookup": {
"from": "folders",
"localField": "joinDashboard.folderId",
"foreignField": "_id",
"as": "joinDashboard.joinFolder"
}
},
])
Result :
[
{
"_id": "dashboard1",
"joinDashboard": {
"joinFolder": [
{
"_id": "folder123"
}
]
},
"name": "test",
"user": 1
}
]
[![enter image description here][1]][1]
Why the fields name and region in collection templatefolders are excluded ?
I want to know why this behavior ? I don't like to use unwind because i have multiple collections with multiple refrence relation.
Your second $lookup, is overriding the joinDashboard key completely. Since you want joinFolder to be within joinDashboard, you can try nested lookups like this:
db.dashboard.aggregate([
{
$lookup: {
from: "templatefolders",
let: {
"boardId": "$_id"
},
pipeline: [
{
$match: {
$expr: {
$eq: [
"$dashboardId",
"$$boardId"
]
}
}
},
{
$lookup: {
from: "folders",
let: {
"folderId": "$folderId"
},
pipeline: [
{
$match: {
$expr: {
$eq: [
"$_id",
"$$folderId"
]
}
}
},
],
as: "joinFolder"
},
},
],
as: "joinDashboard"
}
}
])
MongoPlayground link.

Mongodb - aggregate match within attribute value

MongoDB Data:
{
"_id" : ObjectId("123"),
"attr" : [
{
"nameLable" : "First Name",
"userEnteredValue" : [
"Amanda"
],
"rowNumber":"1"
},
{
"nameLable" : "Last Name",
"userEnteredValue" : [
"Peter"
],
"rowNumber":"1"
},
{
"nameLable" : "First Name",
"userEnteredValue" : [
"Sandra"
],
"rowNumber":"2"
},
{
"nameLable" : "Last Name",
"userEnteredValue" : [
"Peter"
],
"rowNumber":"2"
}
]
}
Matching (First Name equals "Amanda" && Last Name equals "Peter") -> Match should happen within rowNumber so that i will get rowNumber1 record but now i am getting both rows as "Peter" happens to be in both "rowNumber" attribute.
Criteria Code:
Criteria cr = Criteria.where("attr").elemMatch(Criteria.where("nameLable").is(map.get("value1")).and("userEnteredValue").regex(map.get("value2").trim(), "i"); //Inside loop
AggregationOperation match = Aggregation.match(Criteria.where("testId").is("test").andOperator(cr.toArray(new Criteria[criteria.size()])));
DB Query for above search Criteria Match:
db.Col1.aggregate([
{
"$match":{
"testId":"test",
"$and":[
{
"attr":{
"$elemMatch":{
"nameLable":"First Name",
"userEnteredValue":{
"$regex":"Amanda",
"$options":"i"
}
}
}
},
{
"attr":{
"$elemMatch":{
"nameLable":"Last Name",
"userEnteredValue":{
"$regex":"Peter",
"$options":"i"
}
}
}
}
]
}
}
]
)
Please let me know how can we do match within "rowNumber" attribute.
Let me start by recommending you reconsider your document structure, I do not know your product but this structure is very unique and definitely makes most "simple" access patterns I can think of to very cumbersome to execute. This will be noticeable in my answer.
So the current query you have just required 2 separate elements in the array exist, as you mentioned you want the same rowNumber, due to the document structure this isn't really queryable, we will have to first use your query to match "potential" matching documents. At that point we can filter our the matched rows and see if we have both a first name and a last name matching.
Finally we could filter out the none matching rows from the result, here is the pipeline:
db.collection.aggregate([
{
"$match": {
"testId": "test",
"$and": [
{
"attr": {
"$elemMatch": {
"nameLable": "First Name",
"userEnteredValue": {
"$regex": "Amanda",
"$options": "i"
}
}
}
},
{
"attr": {
"$elemMatch": {
"nameLable": "Last Name",
"userEnteredValue": {
"$regex": "Peter",
"$options": "i"
}
}
}
}
]
}
},
{
$addFields: {
goodRows: {
"$setIntersection": [
{
$map: {
input: {
$filter: {
input: "$attr",
cond: {
$and: [
{
$eq: [
"$$this.nameLable",
"First Name"
]
},
{
"$regexMatch": {
"input": {
"$arrayElemAt": [
"$$this.userEnteredValue",
0
]
},
"regex": "Amanda",
"options": "i"
}
}
]
}
}
},
in: "$$this.rowNumber"
}
},
{
$map: {
input: {
$filter: {
input: "$attr",
cond: {
$and: [
{
$eq: [
"$$this.nameLable",
"Last Name"
]
},
{
"$regexMatch": {
"input": {
"$arrayElemAt": [
"$$this.userEnteredValue",
0
]
},
"regex": "Peter",
"options": "i"
}
}
]
}
}
},
in: "$$this.rowNumber"
}
}
]
}
}
},
{
$match: {
$expr: {
$gt: [
{
$size: "$goodRows"
},
0
]
}
}
},
{
$addFields: {
attr: {
$filter: {
input: "$attr",
cond: {
$in: [
"$$this.rowNumber",
"$goodRows"
]
}
}
}
}
}
])
Mongo Playground

How to get single field in mongodb query?

I have data like this:
{ id : 1,
book: "Flash",
chapters: [
{
chap_no: "1",
sub_chapter: [
{sub_no: 1, description: "<description>"
},
{sub_no: 2, description: "<description>"
},
]
}
]
}
i want to show one field like this base on book -> chapter_no -> sub_no
{
sub_no: 2, description: "<description>"
}
in mongodb query.
$match
$unwind
$unwind
$match
$replaceRoot
db.collection.aggregate([
{
"$match": {
"chapters.sub_chapter.sub_no": 2
}
},
{
"$unwind": "$chapters"
},
{
"$unwind": "$chapters.sub_chapter"
},
{
"$match": {
"chapters.sub_chapter.sub_no": 2
}
},
{
"$replaceRoot": {
"newRoot": "$chapters.sub_chapter"
}
}
])
mongoplayground
you can make like this
db.collection.aggregate([
{
"$match": {
$and: [
{
"book": "Flash3"
},
{
"chapters.chap_no": "2"
},
{
"chapters.sub_chapter.sub_no": "1"
}
]
}
},
{
"$unwind": "$chapters"
},
{
"$unwind": "$chapters.sub_chapter"
},
{
"$match": {
$and: [
{
"book": "Flash3"
},
{
"chapters.chap_no": "2"
},
{
"chapters.sub_chapter.sub_no": "1"
}
]
}
},
{
"$replaceRoot": {
"newRoot": "$chapters.sub_chapter"
}
}
])

How to find distinct value with count of other array inside dhe document in MongoDB

I want to get distinct categoryCode and categoryName while this document also contains list of accessories I want to count of this accessories in the response.
Sample data:
[
{
"categoryCode":"categoryCode1",
"categoryName":"categoryName1",
"accessories":[{"a_id":1},{"a_id":2}]
},
{
"categoryCode":"categoryCod2",
"categoryName":"categoryName2",
"accessories":[{"a_id":1},{"a_id":2},{"a_id":3}]
},
{
"categoryCode":"categoryCode1",
"categoryName":"categoryNam1",
"accessories":[{"a_id":1},{"a_id":2}]
}
]
Expected result:
[
{
"categoryCode":"categoryCode1",
"categoryName":"categoryName1",
"accessoriesCount":2
},
{
"categoryCode":"categoryCod2",
"categoryName":"categoryName2",
"accessoriesCount":3
}
]
https://mongoplayground.net/p/q6AZOaTwo5a
db.collection.aggregate([
{
"$group": {
"_id": {
categoryCode: "$categoryCode",
"categoryName": "$categoryName"
},
"accessories": {
"$addToSet": "$accessories"
}
}
},
{
"$project": {
categoryCode: "$_id.categoryCode",
categoryName: "$_id.categoryName",
accessoriesCount: {
$size: "$accessories"
},
_id: 0
}
}
])
Query
group to have the distinct values
$push the accesories arrays (we dont have $concat accumulator)
reduce those arrays to union them, keep only the distinct members, and take the count.
Test code here
db.collection.aggregate([
{
"$group": {
"_id": {
"categoryCode": "$categoryCode",
"categoryName": "$categoryName"
},
"accessories": {
"$push": "$accessories"
}
}
},
{
"$set": {
"accessoriesCount": {
"$size": {
"$reduce": {
"input": "$accessories",
"initialValue": [],
"in": {
"$setUnion": [
"$$value",
"$$this"
]
}
}
}
}
}
},
{
"$project": {
"_id": 0,
"categoryCode": "$_id.categoryCode",
"categoryName": "$_id.categoryName",
"accessoriesCount": "$accessoriesCount"
}
}
])

Aggregation operation to modify the key of a property from a subdocument of documents

I have in mongo db a collection with documents like this:
{
"prop1": "val1",
"prop2": {
"prop21": {
"toBeChanged1_xyz": "val2",
"toBeChanged2_zyx": "val3",
},
"prop22": {
"whatever": "something"
}
}
}
Trying to create aggregation operation in Java Spring to remove from the keys of prop21's properties the substring after _. This if what I need to have:
{
"prop1": "val1",
"prop2": {
"prop21": {
"toBeChanged1": "val2",
"toBeChanged2": "val3",
},
"prop22": {
"whatever": "something"
}
}
}
Tried something like this:
List<AggregationOperation> operations = new ArrayList<AggregationOperation>();
operations.add( Aggregation.project( "code" ).and( "code" ).minus( "_"+"$entityType" ).as( "newId" ));
It didn't worked, of course. I have no idea how can it be done or can it be done easier in other ways.
You should update your structure to below.
{
"prop1": "val1",
"prop2": [
{
"prop": "prop21",
"value": [
{
"prop": "toBeChanged1_xyz",
"value": "val2"
},
{
"prop": "toBeChanged2_zyx",
"value": "val3"
}
]
},
{
"prop": "prop22",
"value": [
{
"prop": "whatever",
"value": "something"
}
]
}
]
}
You can try below query in 3.4. The query will iterate over property elements and locate prop2 and $split to modify the property name.
[
{
"$addFields": {
"prop2": {
"$map": {
"input": "$prop2",
"as": "prop_2",
"in": {
"prop": "$$prop_2.prop",
"value": {
"$cond": [
{
"$eq": [
"$$prop_2.prop",
"prop21"
]
},
{
"$map": {
"input": "$$prop_2.value",
"as": "prop_21",
"in": {
"prop": {
"$arrayElemAt": [
{
"$split": [
"$$prop_21.prop",
"_"
]
},
0
]
},
"value": "$$prop_21.value"
}
}
},
"$$prop_2.value"
]
}
}
}
}
}
}
]
You can use the below query if you cant modify the structure from 3.4.4 version.
The query uses $arrayToObject and $objectToArray operator to modify the documents to above structure with same logic to modify the property name.
[
{
"$addFields": {
"prop2": {
"$arrayToObject": {
"$map": {
"input": {
"$objectToArray": "$prop2"
},
"as": "prop_2",
"in": {
"k": "$$prop_2.k",
"v": {
"$cond": [
{
"$eq": [
"$$prop_2.k",
"prop21"
]
},
{
"$arrayToObject": {
"$map": {
"input": {
"$objectToArray": "$$prop_2.v"
},
"as": "prop_21",
"in": {
"k": {
"$arrayElemAt": [
{
"$split": [
"$$prop_21.k",
"_"
]
},
0
]
},
"v": "$$prop_21.v"
}
}
}
},
"$$prop_2.v"
]
}
}
}
}
}
}
}
]

Categories