I got this structure in my mongodb (2 collections: restaurant and cocktail)
restaurant {id=1001, name="Res1", coor=[12.392, 19.123], cocktail=[13, 0, 92]}
cocktail {id=13, name="Capiroska"}, {id=167, name="Capirinha"}, {id=92, name="Negroni"}, {id=0, name="Martini"}
Multiple restaurants and multiple cocktails, N:N relationship.
My goal is to find which different cocktails I can drink within a specified area.
I've already written a query that finds all restaurants near my position like this:
mongoTemplate.find(new Query(Criteria.where("address.location").withinSphere(new Circle(latitude, longitude, radius/6371))
), Restaurant.class);
So that I obtain a list of restaurants.
Next steps are:
How to obtain distinct cocktail's id (no repetitions allowed)
How to look into cocktail collection in order to obtain all cocktail names
TY in advance!
This might not answer your question completely but can help
how to obtain distinct cocktails id (no repetitions allowed)
Your cocktail is in array so direct group or distinct might not work you can use $unwind.
What $unwind does is allow you to peel off a document for each element
and returns that resulting document
eg: for this object
{id=1001, name="Res1", coor=[12.392, 19.123], cocktail=[13, 0, 92]}
and this query
db.temp.aggregate( [ { $unwind: "$cocktail" } ] )
will result in
{ "_id" : 1001, "name" : "Res1", coor=[12.392, 19.123],, "cocktail" : 13 }
{ "_id" : 1001, "name" : "Res1", coor=[12.392, 19.123],, "cocktail" : 0 }
{ "_id" : 1001, "name" : "Res1", coor=[12.392, 19.123],, "cocktail" : 92 }
Now once you have all individual record you can group by cocktail
db.temp.aggregate( [ { $unwind: "$cocktail" },
{
"$group": {
_id: {
"_id": "$_id",
items: {$addToSet: '$cocktail'}}
}
}
}
] );
This should answer your 1st query
For getting cocktail names you need to use lookup, group and project something like this
db.temp.aggregate([
{
"$unwind": "$cocktail"
},
{
"$lookup": {
"from": "cocktail ",
"localField": "restaurant.cocktail._id",
"foreignField": "_id",
"as": "cocktails"
}
},
{ "$unwind": "$cocktails" },
{
"$group": {
"_id": "$_id",
"cocktail": { "$cocktail": "$cocktail" },
}
},
{
"$project": {
"name": 1,
"coor" : 1,
"cocktail.name" : 1,
}
}
]).pretty()
Note: This is just one approach, might not be the best way and also untested.
You can search data from two or more collection using join in MongoDB.
Depending on your scenario,the following links might help.
https://www.mongodb.com/blog/post/joins-and-other-aggregation-enhancements-coming-in-mongodb-3-2-part-1-of-3-introduction
what about using aggregation.
first perform match stage where you get all restaurant then perform unwind operation on cocktail after this you can perform group by on cocktail field. At this stage you have all unique cocktail id then perform lookup stage .
Order of stage
match
project if you want its optional
unwind
group
lookup
project //because you want only name of cocktail instead of complete
collection.
The code is in kotlin just convert it to java if you are using intellij as ide then it will convert it into java for you.
var match = Aggregation.match(Criteria.where("address.location").withinSphere(Circle(latitude, longitude, radius / 6371)))
var project = Aggregation.project("cocktail")
var unwind = Aggregation.unwind("cocktail")
var group = Aggregation.group("cocktail")
var lookup = Aggregation.lookup("your cocktail collection name","_id.cocktail","id","cocktailCollection")
var project1 = Aggregation.project("cocktailCollection.name").andExclude("_id")
var aggregation = Aggregation.newAggregation(match,project,unwind,group,lookup,project1)
println(aggregation) // if you want to see the query
var result = mongoTemplate.aggregate(aggregation,String::class.java)
Related
This question already has answers here:
Find in Double Nested Array MongoDB
(2 answers)
Spring data Match and Filter Nested Array
(1 answer)
Closed 3 years ago.
I'm looking to search into my collection and retreive only element who matched Criteria.
Here is my collection :
{
"_id" : "id",
"name" : "test",
"groupUsers" : [
{
"name" : "blabla",
"toys" : [
{
"createdAt" : ISODate("2019-10-30T12:59:41.409Z"),
},
{
"createdAt" : ISODate("2019-11-30T12:59:10.409Z"),
},
{
"createdAt" : ISODate("2019-12-30T12:59:12.409Z"),
}
],
"createdAt" : ISODate("2019-10-30T12:33:39.036Z")
},
{
"name" : "blabla2",
"toys" : [
{
"createdAt" : ISODate("2019-10-32T12:59:41.409Z"),
},
{
"createdAt" : ISODate("2019-11-30T12:59:56.409Z"),
},
{
"createdAt" : ISODate("2019-12-30T12:59:15.409Z"),
}
],
"createdAt" : ISODate("2019-10-32T12:33:39.036Z")
}
],
}
I want to retreive the whole collection but it depends when the user was added to the group for example, user blabla2 (in the example above) will only get the whole group but with only the two last toys of the first user in the response.
Anyway, I guess it's something really basic but I don't know why I can't figure it out.
What I'm Doing
I'm doing a first query to get the current user and get when he was added in the group (notice that the date gets converted into java Date Util here).
Aggregation groupAgg = newAggregation(match(Criteria.where("_id").is(groupId).and("groupUsers.userId").is(userId)));
GroupUser groupUser = mongoTemplate.aggregate(groupAgg, Group.class, GroupUser.class).getUniqueMappedResult();
In a second query, I want to get the whole document but only with the Criteria that I define before.
MatchOperation matchedGroup = match(new Criteria("_id").is(groupId));
MatchOperation matchedToys = match(
new Criteria("groupUsers.toys.createdAt").gte(groupUser.getCreatedAt()));
Aggregation aggregation = newAggregation(matchedGroup, matchedToys);
AggregationResults<Group> result = mongoTemplate.aggregate(aggregation, Group.class, Group.class);
Group group = result.getUniqueMappedResult();
This query doesn't work, and I'm looking to something like even if there is no match (for example, none toys has been created yet), it still return the group basic response and not null.
Maybe I need to unwind the nested array ?
Any help is appreciate. I'm using spring data.
Try this query
db.testers.aggregate([
{
$addFields:{
"groupUsers":{
$map:{
"input":"$groupUsers",
"as":"doc",
"in":{
$mergeObjects:[
"$$doc",
{
"toys":{
$filter:{
"input":"$$doc. toys",
"as":"sn",
"cond": {
"$and": [
{ "$gte": [ "$$sn.createdAt", ISODate('2015-06-17T10:03:46.000Z') ] },
]
}
}
}
}
]
}
}
}
}
}
]).pretty()
I try to sort data in mongo by the Spring Sort object, with 2 specific fields.
For some reason, Spring changes the order of the 'sorting fields'.
I use Spring and MongoDB data (MongoTemplate). I use findAll() function for get some records.
In mongo data I have specific index { "status": -1, "_id": -1 }.
I want to sort by this specific index, and for that I added this code for sorting:
new Sort(Sort.Direction.DESC, "status").and(new Sort(Sort.Direction.DESC,
"_id"));
I sort by status field, and then by _id field (like the index).
FYI:
I use other indexes and sort by them, and Spring doesn't sort my 'sorting fields':
{ "status": -1, "__sort_authors": 1 }
{ "status": -1, "__sort_title": 1 }
On these cases the Spring saves the sorting fields' order in the Mongo query:
Query: {...}, Sort: { "status":-1, "__sort_authors":1 }
Query: {...}, Sort: { "status":-1, "__sort_title":1 }
The expected mongo query should be:
{ "foldersIds" : { "$in" : ["project:6s5d4f32sd1f65"] }, "availability" : "PRESENT" }, Fields: { }, Sort: { "status" : -1, "_id" : -1 }
The actual mongo query is:
{ "foldersIds" : { "$in" : ["project:5c505594e4b0e98a6537ec9d"] }, "availability" : "PRESENT" }, Fields: { }, Sort: { "_id" : -1, "status" : -1 }
Maybe it because the _id is the primary field?
I want to keep the order of the sorting fields. Any idea how to save the order?
Thanks!
I would try explicitly setting the order by passing a List instead of calling the and method. Something like this:
ArrayList<Order> orders = new ArrayList<>();
orders.add(new Sort.Order(Sort.Direction.DESC, "status"));
orders.add(new Sort.Order(Sort.Direction.DESC, "_id"));
Sort sort = Sort.by(orders);
The reason I suggest this is because the Sort.and() method's documentation only specifies it combines the specified sorts, it doesn't specify how or in what order it combines them. Using a List should hopefully enforce the ordering that you want.
After asking question to understand a bit more of the aggregation framework in MongoDB I finally found the way to do aggregation for my need (thanks to a StackExchange user)
So basically here is a document from my collection:
{
"_id" : ObjectId("s4dcsd5s4d6c54s6d"),
"items" : [
{
type : "TYPE_1",
text : "blablabla"
},
{
type : "TYPE_2",
text : "blablabla"
},
{
type : "TYPE_3",
text : "blablabla"
},
{
type : "TYPE_1",
text : "blablabla"
},
{
type : "TYPE_2",
text : "blablabla"
},
{
type : "TYPE_1",
text : "blablabla"
}
]
}
The idea was to be able to filter only some elements of my collections (avoiding Type 2 and 3). In fact I have more than 30 types and 6 are not allowed but for simplicity I made this example.
So the aggregation command in command line is this one:
db.history.aggregate([{
$match: {
_id: ObjectId("s4dcsd5s4d6c54s6d")
}
}, {
$unwind: '$items'
}, {
$match: {
'items.type': { '$nin': [ "TYPE_2" , "TYPE_3"] }
}
},
{ $limit: 10 }
]);
With this I am able to retrieve the 10 elements items of this document which do not match TYPE_2 and TYPE_3
However when I am using spring data there is no output. I looked a bit at the example to build mine but its still not working.
So I did:
Aggregation aggregation = newAggregation(
match(Criteria.where("id").is(myID)),
unwind("items"),
match(Criteria.where("items.type").nin(ignoreditemstype)),
limit(3),
skip(offsetLong)
);
AggregationResults<PersonnalHistory> results = mongAccess.getOperation().aggregate(query,
"items", PersonnalHistory.class);
PersonnalHistory is marked with annotation #Document(collection = "history") and id with the #id annotation
ignoreditemstype is a list containing TYPE_2 and TYPE_3
Here is what I have in the toString method of aggregation:
{
"aggregate" : "__collection__" ,
"pipeline" : [
{ "$match": { "id" : "s4dcsd5s4d6c54s6d"} },
{ "$unwind": "$items"},
{ "$match": { "items.type": { "$nin" : [ "TYPE_2" , "TYPE_3" ] } } },
{ "$limit" : 3},
{ "$skip" : 0 }
]
}
I tried a lot of stuff (to have at least an answer :) ) like removing id or the nin:
aggregation = newAggregation(
unwind("items"),
match(Criteria.where("items.type").nin(ignoreditemstype)),
limit(3),
skip(offsetLong)
);
aggregation = newAggregation(
match(Criteria.where("id").is(myid)),
unwind("items")
);
For information when I do a simple query like:
query.addCriteria(Criteria.where("id").is(myID));
My document is returned. However I have thousands of items. So I just want to have the 15 first (in fact the 15 first are the 15 last added)
Do you maybe see what I am doing wrong?
Yeah looks like you are passing simple String while it is expecting ObjectId
Aggregation aggregation = newAggregation(
match(Criteria.where("_id").is(new ObjectId(myID))),
unwind("items"),
match(Criteria.where("items.type").nin(ignoreditemstype)),
limit(3),
skip(offsetLong)
);
Now the question is why it works with simple query, my answer would be because spring-data driver is not that mature at least not with aggregation pipeline.
I'm searching an index with multiple types by simply using 'http://es:9200/products/_search?q=sony'. This will return a lot of hits with many different types. The hits array contains all the results but not in the order I want it to; i want the 'television' type to always show before the rest. Is it possible at all to order by type?
You can achieve this by sorting on the pre-defined field _type. The query below sorts results in ascending order of document types.
POST <indexname>/_search
{
"sort": [
{
"_type": {
"order": "asc"
}
}
],
"query": {
<query goes here>
}
}
I do it by adding a numeric field _is_OF_TYPE to the indexed documents and set it to 1 for those docs that are of the given type. Then just sort on those fields in any order you want.
For example:
Document A:
{
_is_television: 1,
... some television props here ...
}
Document B:
{
_is_television: 1,
... another television props here ...
}
Document C:
{
_is_radio: 1,
... some radio props here ...
}
and so on...
Then in ElasricSearch query:
POST radio,television,foo,bar,baz/_search
{
"sort": [
{"_is_television": {"unmapped_type" : "long"}}, // television goes first
{"_is_radio": {"unmapped_type" : "long"}}, // then radio
{"_is_another_type": {"unmapped_type" : "long"}} // ... and so on
]
}
The benefit of this solution is speed. You simply sort on numeric fields. No script sorting required.
Let me give a example here:
Two entries in the collection Author:
{
"name" : "Joe"
"Book" : "A"
},
{
"name" : "Joe"
"Book" : "B"
}
Now, if I use the aggregation function in Mongo via spring mongo, basically just to grab the books with name Joe, it could be coded like:
Aggregation agg = newAggregation(Map.class, group("name", "Book"));
AggregationResults<Map> results = mongoTemplate.aggregate(agg, "Author",
Map.class);
Obviously I could get two Maps this way, one has entry {"name":"Joe", "Book": A}, the other has {"name" : "Joe", "Book" : "B"}
But what if I want get ONLY one result back, with one entry :
{"name" : Joe, "Books" : ["A", "B"]}?
I'm not sure if it is reachable just using one query. It certainly could be achieved by multiple steps, which I'd hate to do..
You need to use the $addToSet operator in your $group pipeline. This will return an array of all unique values ["A", "B"] that results from applying the $group expression to each document in a group of documents that share the same group by key "name". So in mongo shell you have
db.author.aggregate([
{ $group: {
_id: '$name',
Books: { $addToSet: '$Book' }
} }
]);
which brings back the desired result
{
"result" : [
{
"_id" : "Joe",
"Books" : [ "B", "A" ]
}
],
"ok" : 1
}
The equivalent Spring aggregation:
Aggregation agg = newAggregation(Map.class, group("name").addToSet("Book").as("Books"));
AggregationResults<Map> results = mongoTemplate.aggregate(agg, "Author", Map.class);