Java. MongoDB. Sort sub document after unwind - java

I have users, and skills into the user.
[
{
"_id": "5b91668a0f77e30c11574c88",
"full_name": "John Smith"
"skills": [
{
"_id": "61966603ceb6da478418aac0",
"name": "Java",
"active": true
},
{
"_id": "61966533ceb6da4092707441",
"name": "Scala",
"active": true
},
{
"_id": "619f9dce86b62c370e1c5fcc",
"name": "MongoDB",
"active": true
}
]
}
]
I need to get all user skills in A-Z order by name. I found many solutions, but they are all for MongoDB. I am trying to use the functions of the class com.mongodb.client.model.Agregate, but sorting doesn't work for me (returns in a different order, but it is not clear in what order.) and I do not understand why. Maybe I am do something wrong, or exist another implementation of my task?
var result = getCollection().aggregate(
asList(
match(eq("_id", userId)),
unwind("$skills"),
sort(ascending("skills.name")),
group(null, addToSet("skills", "$skills"))),
Document.class);

Alternatively, you can altogether avoid the $unwind, $sort and $group stages, and use the following aggregation $addFileds (or $project) stage to sort the array elements. This uses the $function aggregate expression (MongoDB v4.4).
Consider an input document:
{
skills: [ { name: "Java" }, { name: "Scala" }, { name: "Mongo" }, { name: "Groovy" } ]
}
The query:
db.collection.aggregate([
{
$addFields: {
skills: {
$function: {
body: function(input) {
const sorter = function(x1, x2) {
if (x1.name > x2.name) {
return 1;
}
else if (x2.name > x1.name) {
return -1;
}
else {
return 0;
};
};
input.sort(sorter);
return input;
},
args: [ "$skills" ],
lang: "js"
}
}
}
}
])
The output with sorted skills array (by name field):
skills: [ { name: "Groovy" }, { name: "Java" }, { name: "Mongo" }, { name: "Scala" } ]

Related

How to get data from mongodb with duplicated parameters?

I have try to create criteria that fetch from data base items.
Here is the code that fetches items from mongo db:
public List<Location> findByListOfId(List<String> locationsIds){
Query query = new Query();
query.addCriteria(Criteria.where("id").in(locationsIds));
return template.find(query, Location.class);
}
here is Location class defenition:
#Document("loaction")
#Data
public class Location {
#Id
private String id;
private long order;
private Date createdAt;
private Date updatedAt;
}
And here is the value of input(List locationsIds) in findByListOfId function:
List<String> locationsIds = {"5d4eee8047206b6d2df212bb","5d4eee8047206b6d2df212bb","5d4eee8047206b6d2df212bb"}
as you can see the input contains the same value three times.
The result that I get from findByListOfId function is a single item with id equal to 5d4eee8047206b6d2df212bb,
while I need to get the numbers of items with the same id as a number of times that exists with in variable(in my case I expect 3 fetched items with id = 5d4eee8047206b6d2df212bb ).
Any idea how this query can be created?
Not sure why you want to do it, but you can do it this way (in Mongo Query Language, you can then translate it in Java).
MongoDB Playground
db.collection.aggregate([
{
$match: {
key: {
$in: [
"5d4eee8047206b6d2df212bb",
"5d4eee8047206b6d2df212bb",
"5d4eee8047206b6d2df212bb"
]
}
}
},
{
"$addFields": {
"itemsArray": [
"5d4eee8047206b6d2df212bb",
"5d4eee8047206b6d2df212bb",
"5d4eee8047206b6d2df212bb"
]
}
},
{
"$unwind": "$itemsArray"
},
])
Using aggregation pipeline, you will add the array as a field using $addFields and then $unwind it (will give you x number of times).
I agree with others it's not something you want to do in production code, but I find the question interesting.
#Yahya's answer works with an assumption that the $match stage returns exactly 1 document.
The more generic pipeline to fetch exact number of documents regardless of how unique the key is and how many duplicates are in the query https://mongoplayground.net/p/546QnaFn4lV :
db.collection.aggregate([
{
$limit: 1
},
{
$project: {
_id: 1,
list: [
"5d4eee8047206b6d2df212bb",
"5d4eee8047206b6d2df212bb",
"6d4eee8047206b6d2df212bc",
"7d4eee8047206b6d2df212bd"
]
}
},
{
"$unwind": "$list"
},
{
"$lookup": {
"from": "collection",
"localField": "list",
"foreignField": "key",
"as": "match"
}
},
{
$project: {
match: {
$cond: [
{
$eq: [
"$match",
[]
]
},
[
{
_id: null,
"key": "$list"
}
],
"$match"
]
}
}
},
{
"$replaceWith": {
$first: "$match"
}
}
])
The first $project passes the list of requested ids to mongo.
The last $project stage returns "null" for requested ids that don't have a matching document.
Here is an aggregate query with required result:
Consider a collection with these documents:
{ _id: 1, a: 11 }
{ _id: 2, a: 22 }
{ _id: 3, a: 99 }
The query in mongo shell with input documents:
var INPUT_IDS = [ 1, 2, 1, 1 ]
db.collection.aggregate([
{
$match: {
_id: { $in: INPUT_IDS }
}
},
{
$group: {
_id: null,
docs: { $push: "$$ROOT" }
}
},
{
$project: {
docs: {
$map: {
input: INPUT_IDS,
as: "inid",
in: {
$let: {
vars: {
matched: {
$filter: {
input: "$docs", as: "doc", cond: { $eq: [ "$$inid", "$$doc._id" ] }
}
}
},
in: { $arrayElemAt: [ "$$matched", 0 ] }
}
}
}
}
}
},
{
$unwind: "$docs"
},
{
$replaceWith: "$docs"
}
])
The output:
{ "_id" : 1, "a" : 11 }
{ "_id" : 2, "a" : 22 }
{ "_id" : 1, "a" : 11 }
{ "_id" : 1, "a" : 11 }

How to aggregate MongoDB the final total sum? From the sum calculated earlier

How to aggregate the final total sum? From the sum calculated earlier
this is original result.
[
{
"name": "a",
"prices": 10,
},
{
"name": "a",
"prices": 20,
}
]
but i need to do this.
[
{
"name": "a",
"prices": 10,
},
{
"name": "a",
"prices": 20,
},
//i need to do more//
{
"name": "total",
"total":30
}
]
this is example picture.
enter image description here
$group by null and construct array of root documents in docs, get total price in totalPrices
concat current docs and total prices doc using $concatArrays
$unwind deconstruct docs array
$project to show both the fields from docs object
db.collection.aggregate([
{
$group: {
_id: null,
docs: { $push: "$$ROOT" },
totalPrices: { $sum: "$prices" }
}
},
{
$project: {
docs: {
$concatArrays: [
"$docs",
[
{
name: "total",
prices: "$totalPrices"
}
]
]
}
}
},
{ $unwind: "$docs" },
{
$project: {
_id: 0,
name: "$docs.name",
prices: "$docs.prices"
}
}
])
Playground

Trying to get the name of the object whose collection array contains 'random'

I'm trying to get the names of the object whose collection array contains the word 'random'. Have tried various json path queries but couldn't get the right one.
{
"elements": [
{
"name": "My first element",
"language": "French",
"tags": ["Paris", "baguette", "Eiffel tower"]
},
{
"name": "randomOne",
"language": "Gibberish",
"tags": ["random", "plant, bag"]
},
{
"name": "bliep",
"language": "English",
"tags": ["lamp", "table, bed, oven"]
}
]}
Try this :
const jsonObj = {
"elements": [{
"name": "My first element",
"language": "French",
"tags": ["Paris", "baguette", "Eiffel tower"]
},
{
"name": "randomOne",
"language": "Gibberish",
"tags": ["random", "plant, bag"]
},
{
"name": "bliep",
"language": "English",
"tags": ["lamp", "table, bed, oven"]
}
]
};
let obj = jsonObj.elements.find((obj) => obj.tags.includes('random'));
console.log(obj.name); // randomOne
You can simply:
Loop through elements with Array.prototype.forEach()
Find instances containing "random" with Array.prototype.includes()
Add found names to result array with Array.prototype.push()
See below:
const jsondata = {
elements: [{
name: "My first element",
language: "French",
tags: ["Paris", "baguette", "Eiffel tower"]
},
{
name: "randomOne",
language: "Gibberish",
tags: ["random", "plant, bag"]
},
{
name: "bliep",
language: "English",
tags: ["lamp", "table, bed, oven"]
}
]
};
const result = [];
jsondata.elements.forEach(elem => {
if (elem.tags.includes("random")) {
result.push(elem.name);
}
});
console.log(result); // [ 'randomOne' ]
I have tried this question and able to get the names whose having 'random' in collection array.
$.elements[?(#.tags.indexOf('random') != -1)].name
Please update if it serves your purpose.

MongoDB Query to match both single entry and array elements

I have a problem with MongoDB QueryBuilder.
Assume I have a number of documents, that can contain one or more users:
{
"_id": "document1",
"data": {
"user": {
"credentials": {
"name": "John",
"lastname": "Watson",
"middle": "Hemish"
}
}
}
}
{
"_id": "document2",
"data": {
"user": [
{
"credentials": {
"name": "John",
"lastname": "Nicholson",
"middle": "Joseph"
}
},
{
"credentials": {
"name": "Mary",
"lastname": "Watson",
"middle": ""
}
}
]
}
}
{
"_id": "document3",
"data": {
"user": [
{
"credentials": {
"name": "John",
"lastname": "Watson",
"middle": "Hemish"
}
},
{
"credentials": {
"name": "John",
"lastname": "Nicholson",
"middle": "Joseph"
}
},
{
"credentials": {
"name": "Mary",
"lastname": "Watson",
"middle": ""
}
}
]
}
}
What I am trying to do is the query, that will return only those documents containing John Watson as a user.
Here what I got so far:
1.
QueryBuilder qb = QueryBuilder.start("credentials.lastname").is("Watson").and("credentials.name").is("John");
DBObject query = QueryBuilder.start("data.user").elemMatch(qb.get()).get();
this query will return only document3: there is no array in document1 and no match in document2 (but I would like it to return document1 and document3)
2.
DBObject query = QueryBuilder.start("data.user.credentials.lastname").is("Watson").and("data.user.credentials.name").is("John").get();
this one will return all three documents: document1 and document3 are desired match, but the query will match as well document2, for it has Watson and John in query fields in the array, no matter that they are separate entries.
Is there any way to make a right query that will return document1 and document3 for John Watson?
I am trying to do it in Java, but any other example would be fine.
Right now I use a workaround combining results from both queries: first I get limit(100) results from the query with elementMatch(), then, if there are less than 100 results, I do the second query and filter all wrong matches. But I hope there is a better and more effective way to get those results.
I could give you at best like the following where user would be in an array as unwind value of the key data. I think a little bit more effort would lead you to the exact format as you want.
I am sharing it as I think it should serve the purpose or anyhow it should help you.
The aggregation query:
db.tuttut.aggregate([
{$unwind:"$data.user"},
{ $project: {
_id:1,
data:1,
temp: {name:"$data.user.credentials.name",
lastname:"$data.user.credentials.lastname"}
} } ,
{ $group:{
_id:"$_id" ,
data: {$addToSet: "$data"} ,
temp:{ $addToSet: "$temp" } } },
{ $match:{ temp:{name:"John",lastname:"Watson"} } } ,
{$project:{_id:1, data:1}}
]).pretty()
Returned Result:
{
"_id" : "document1",
"data" : [
{
"user" : {
"credentials" : {
"name" : "John",
"lastname" : "Watson",
"middle" : "Hemish"
}
}
}
]
}
{
"_id" : "document3",
"data" : [
{
"user" : {
"credentials" : {
"name" : "John",
"lastname" : "Watson",
"middle" : "Hemish"
}
}
},
{
"user" : {
"credentials" : {
"name" : "Mary",
"lastname" : "Watson",
"middle" : ""
}
}
},
{
"user" : {
"credentials" : {
"name" : "John",
"lastname" : "Nicholson",
"middle" : "Joseph"
}
}
}
]
}

Java & MongoDB - Check if field name exists in nested arrays

Looking at this simplified example of a record in Mongo:
{
"_id": ObjectId("573b30cb1d62485110330c35"),
"changes": [
{
"after": {
"comments": [
{
"user": {
"idRef": "test1234",
"name": "test user"
}
}
]
}
},
{
"after": {}
}
]
}
Is there a way, using Java's Mongo library, to iterate through each element in each nested array (changes -> after -> comments) and see if the user.idRef field exists?
You can use the following statement
db.collection.find({ "fieldToCheck" : { $exists : true, $ne : null } })
This will return a list of documents matching the criteria.

Categories