I am using MongoDB 3.6 for my project.
I have 2 collections "users" and "follow". I want to extract out details of user's followers and following (like an Instagram app).
users collection
{
"id" : "1",
"name" : "abc",
"age" : "26"
},
{
"id" : "2",
"name" : "xyz",
"age" : "22"
},
{
"id" : "3",
"name" : "qwe",
"age" : "23"
}
follow collection
{
"id" : "2",
"follow id" : "1"
},
{
"id" : "3",
"follow id" : "1"
},
{
"id" : "1",
"follow id" : "2"
},
{
"id" : "2",
"follow id" : "3"
},
{
"id" : "1",
"follow id" : "3"
}
Now i want following list of id 2 So id 2 is following id 1 and id 3
So, Output should be like this
{
"id" : "1",
"name" : "abc",
"age" : "26"
},
{
"id" : "3",
"name" : "qwe",
"age" : "23"
}
For that, I am using $lookup aggregation. But this is not giving the desired output which I want.
Here is my code -
Follow.aggregate([
{
$lookup:{
from:"users",
localField:"id",
foreignField:"id",
as:"fromItems"
}
},
{
$replaceRoot:{newRoot: {$mergeObjects: [ { $arrayElemAt: ["$fromItems", 0 ] }, "$$ROOT" ] } }
},
{ $project :
{
fromItems : 0
}
}
], callback)
For more understanding please refer the image
To get following list of id 2 you can use following query:
Follow.aggregate([
{
$match: { "id": "2" }
},
{
$lookup:{
from:"users",
localField:"follow id",
foreignField:"id",
as:"fromItems"
}
},
{
$replaceRoot:{newRoot: {$mergeObjects: [ { $arrayElemAt: ["$fromItems", 0 ] }, "$$ROOT" ] } }
},
{ $project :
{
id : "$follow id",
name: 1,
age: 1
}
}
])
So the point here is that you have a relation between id and follow id and after $lookup phase follow id becomes the new id since it's parent-child relation.
EDIT:
3.4 solution below
Follow.aggregate([
{
$match: { "id": "2" }
},
{
$lookup:{
from:"users",
localField:"follow id",
foreignField:"id",
as:"fromItems"
}
},
{
$project: {
id: "$follow id",
from: { $arrayElemAt: ["$fromItems", 0 ] }
}
},
{ $project :
{
id : 1,
name: "$from.name",
age: "$from.age"
}
}
])
Related
In post index, postid is primary key and userid is foreign key.
i want all post but only post from one userid, such that only one user have the one post in results sort by postdate(optional latest first)
//Actual Result
[
{
userid: "u1",
postid: "p1"
},
{
userid: "u1",
postid: "p2"
},
{
userid: "u2",
postid: "p3"
},
{
userid: "u3",
postid: "p4"
},
{
userid: "u3",
postid: "p5"
},
{
userid: "u3",
postid: "p6"
}
]
needed as below
//Expecting Result
[
{
userid: "u1",
postid: "p1"
},
{
userid: "u2",
postid: "p3"
},
{
userid: "u3",
postid: "p4"
}
]
I think you can use top hit for this. Here the sample for this :
DELETE my-index-000001
PUT my-index-000001
{
"mappings": {
"properties": {
"userid": {
"type": "keyword"
},
"postid": {
"type": "keyword"
},
"postdate": {
"type": "date"
}
}
}
}
PUT my-index-000001/_doc/1
{"userid": "u1", "postid": "p1", "postdate": "2021-03-01"}
PUT my-index-000001/_doc/2
{"userid": "u1", "postid": "p2", "postdate": "2021-03-02"}
PUT my-index-000001/_doc/3
{"userid": "u2", "postid": "p3", "postdate": "2021-03-03"}
PUT my-index-000001/_doc/4
{"userid": "u3", "postid": "p4", "postdate": "2021-03-04"}
PUT my-index-000001/_doc/5
{"userid": "u3", "postid": "p5", "postdate": "2021-03-05"}
PUT my-index-000001/_doc/6
{"userid": "u3", "postid": "p6", "postdate": "2021-03-06"}
These are the sample index creating steps. And here the query :
GET my-index-000001/_search
{
"size": 0,
"aggs": {
"top_users": {
"terms": {
"field": "userid",
"size": 100
},
"aggs": {
"top": {
"top_hits": {
"sort": [
{
"postdate": {
"order": "desc"
}
}
],
"_source": {
"includes": [ "postdate", "postid" ]
},
"size": 1
}
}
}
}
}
}
And, inside the resultset you can see the top post for the every users inside the aggregations:
{
"took" : 3,
"timed_out" : false,
"_shards" : {
"total" : 1,
"successful" : 1,
"skipped" : 0,
"failed" : 0
},
"hits" : {
"total" : {
"value" : 6,
"relation" : "eq"
},
"max_score" : null,
"hits" : [ ]
},
"aggregations" : {
"top_users" : {
"doc_count_error_upper_bound" : 0,
"sum_other_doc_count" : 0,
"buckets" : [
{
"key" : "u3",
"doc_count" : 3,
"top" : {
"hits" : {
"total" : {
"value" : 3,
"relation" : "eq"
},
"max_score" : null,
"hits" : [
{
"_index" : "my-index-000001",
"_type" : "_doc",
"_id" : "6",
"_score" : null,
"_source" : {
"postdate" : "2021-03-06",
"postid" : "p6"
},
"sort" : [
1614988800000
]
}
]
}
}
},
{
"key" : "u1",
"doc_count" : 2,
"top" : {
"hits" : {
"total" : {
"value" : 2,
"relation" : "eq"
},
"max_score" : null,
"hits" : [
{
"_index" : "my-index-000001",
"_type" : "_doc",
"_id" : "2",
"_score" : null,
"_source" : {
"postdate" : "2021-03-02",
"postid" : "p2"
},
"sort" : [
1614643200000
]
}
]
}
}
},
{
"key" : "u2",
"doc_count" : 1,
"top" : {
"hits" : {
"total" : {
"value" : 1,
"relation" : "eq"
},
"max_score" : null,
"hits" : [
{
"_index" : "my-index-000001",
"_type" : "_doc",
"_id" : "3",
"_score" : null,
"_source" : {
"postdate" : "2021-03-03",
"postid" : "p3"
},
"sort" : [
1614729600000
]
}
]
}
}
}
]
}
}
}
Assuming an index mapping of the form:
PUT user_posts
{
"mappings": {
"properties": {
"userid": {
"type": "keyword"
},
"postid": {
"type": "keyword"
},
"postdate": {
"type": "date"
}
}
}
}
You could:
aggregate on the userid and order the IDs alphabetically
sub-aggregate on the postid and sort the post by posttime descending via a max aggregation.
filter the response through the filter_path option to only retrieve what you need
POST user_posts/_search?filter_path=aggregations.*.buckets.key,aggregations.*.buckets.*.buckets.key
{
"size": 0,
"aggs": {
"by_userid": {
"terms": {
"field": "userid",
"order": {
"_key": "asc"
},
"size": 100
},
"aggs": {
"by_latest_postid": {
"terms": {
"field": "postid",
"size": 1,
"order": {
"latest_posttime": "desc"
}
},
"aggs": {
"latest_posttime": {
"max": {
"field": "postdate"
}
}
}
}
}
}
}
}
Yielding:
{
"aggregations" : {
"by_userid" : {
"buckets" : [
{
"key" : "u1",
"by_latest_postid" : {
"buckets" : [
{
"key" : "p1"
}
]
}
},
{
"key" : "u2",
"by_latest_postid" : {
"buckets" : [
{
"key" : "p3"
}
]
}
},
{
"key" : "u3",
"by_latest_postid" : {
"buckets" : [
{
"key" : "p4"
}
]
}
}
]
}
}
}
which you can then post-process as you normally would:
...
const response = await ...; // transform the above request for use in the ES JS lib of your choice
const result = response.aggregations.by_userid.buckets.map(b => {
return {
userid: b.key,
postid: b.by_latest_postid.buckets && b.by_latest_postid.buckets[0].key
}
})
You can use the top hits sub-aggregation. So first do a terms aggregation by userId, then you can use top-hits with a sort by post-date to get the latest post by each user.
I should say that if you have many userIds and you want the top hit for each one, you should probably use composite aggregation as your top-level agg, and not terms.
I have two tables in my database.
teams table:
[
{
"users": [
{ "id": '5f9971e6f4f60959c0668896', "role": 'admin' },
{ "id": '5fb11570ece5672ba95850ee', "role": 'read' }
],
"_id": '5fb0f993795d1634d4e486dc',
"name": 'team1'
},
{
"users": [
{ "id": '5fb11570ece5672ba95850ee', "role": 'admin' }
],
"_id": "5fb0f993795d1634d4e48fed",
"name": 'team2'
}
]
users table:
[
{
"_id" : ObjectId("5f9971e6f4f60959c0668896"),
"firstname" : "foo",
"lastname" : "bar"
},
{
"_id" : ObjectId("5fb11570ece5672ba95850ee"),
"firstname" : "hey",
"lastname" : "you"
},
{
"_id" : ObjectId("5fb11570ece5672ba95grs0"),
"firstname" : "12",
"lastname" : "34"
}
]
and what i want to get is this:
{
users:
[
{ id: '5f9971e6f4f60959c0668896',firstname: "foo",lastname: "bar", role: 'admin' },
{ id: '5fb11570ece5672ba95850ee',firstname: "hey",lastname: "you", role: 'read' }
],
_id: 5fb0f993795d1634d4e486dc,
name: 'team1'
}
i'm trying to solve this a week and i didn't find solution.
Teams.aggregate([
{
$lookup: {
from: "users",
localField: "_id",
foreignField: "users.id",
as: "users",
},
},
]).then((data) => {
console.log("data", data);
});
tnx for the help
Your teams collection users.id need to be also ObjectId , not a string ... , or alternatively you need to change the users collection _id's to strings ...
My documents looks like this:
{
"_id" : "53ce85eda2579da8b40c1f0f",
"name" : "Autokino",
"tags" : [
"forMen"
],
"ratings" : [
{ "rating" : 5, "uuid" : "..."},
{ "rating" : 4, "uuid" : "..."},
{ "rating" : 4, "uuid" : "..."},
{ "rating" : 1, "uuid" : "..."},
]
}
Now I need the average of ratings.rating (here it should be 3.5). My query looks like this:
activities.aggregate([
{ $match: { _id: ObjectID(req.params.id) } },
{ $unwind: '$ratings' },
{ $group: {
_id: '$_id',
rating: { $avg: '$ratings.rating'},
}},
]);
It works, but what I get is:
{
"_id" : "53ce85eda2579da8b40c1f0f",
"rating" : 3.5
}
and this is what I need to get:
{
"_id" : "53ce85eda2579da8b40c1f0f",
"name" : "Autokino",
"tags" : [
"forMen"
],
"rating" : 3.5
}
(The original document without ratings array but with rating average)
How can I solve this problem?
Pipeline stages like $group and $project are "absolute" in that only the declared fields are emitted. You need another operator here. $first will do:
activities.aggregate([
{ "$match": { "_id": ObjectID(req.params.id) } },
{ "$unwind": "$ratings" },
{ "$group": {
"_id": "$_id",
"name": { "$first": "$name" },
"tags": { "$first": "$tags" },
"rating": { "$avg": "$ratings.rating" },
}},
]);
Since $unwind makes many documents out of the array contents de-normalized, you can use $first here to just take the "first" occurrence of the additional field that you are not otherwise aggregating.
If you are worried about lots of fields to declare this way MongoDB 2.6 does offer the $$ROOT variable. I'ts usage and output are likely not what you really want:
activities.aggregate([
{ "$match": { "_id": ObjectID(req.params.id) } },
{ "$project": {
"_id": "$$ROOT",
"ratings": 1
}},
{ "$unwind": "$ratings" },
{ "$group": {
"_id": "$_id",
"rating": { "$avg": "$ratings.rating" },
}},
]);
This gives you something like:
{
"_id" : {
"_id": "53ce85eda2579da8b40c1f0f",
"name" : "Autokino",
"tags" : [
"forMen"
],
"ratings" : [
{ "rating" : 5, "uuid" : "..."},
{ "rating" : 4, "uuid" : "..."},
{ "rating" : 4, "uuid" : "..."},
{ "rating" : 1, "uuid" : "..."},
]
},
"rating": 3.5
}
This is okay here since grouping by _id is the same as grouping on the whole document. So you can always add a final $project to return to similar state. But there are no wildcards here.
I just went through this whole song and dance as well and ended up having to re-add all my fields back. Not ideal!
So I just found this - much easier: I have a reviews array field, which has a rating property of 1 - 5
{
$addFields: { avg: { $avg: '$reviews.rating'}}
},
I have the following document in the collection:
"_id" : "2",
"workspace" : [{
"name" : "1",
"widgets" : [ ]
},{
"name" : "2",
"widgets" : [ ]
},{
"name" : "3",
"widgets" : [ ]
},{
"name" : "4",
"widgets" : [ ]
}
]}
How can I insert {id: "1", blabla: "blabla"} in "widgets" for the "name" 3?
In comparison to a previous answer which just inserts everything into the root of the document, here is a correct way to do this with positional operator:
db.t.update({
"_id" : "2",
"workspace.name" : "3"
},{
$push: {
'workspace.$.widgets' : {
id: "2",
blabla: "blabla"
}
}
});
I am trying to use select2 to get remote JSON data and display it with mutli levels.
http://ivaynberg.github.io/select2/index.html
This is my response
{
"Company": [
{
"name": "athenahealth Inc"
},
{
"name": "Localiza Rent a Car"
},
{
"name": "M and B Switchgears"
}
],
"Functional": [
{
"name": "arranger"
},
{
"name": "ambassadors"
}
],
"Persons": [
{
"name": "Moustapha al"
},
{
"name": "Saleh al"
}
]
}
I want to show the result in Multi-Value format - http://ivaynberg.github.io/select2/index.html#multi
So far i am able to fetch data from server side , but then i have no idea how to enable the multi select option.
JSON in following format will work fine
Related issue - https://github.com/ivaynberg/select2/issues/58
{ "Data" : [ {
"id" :1 ,
"text" : "Subsection" ,
"children" : [{
"id" : 2,
"text" : "Paru"
},
{
"id" : 3,
"text" : "Vinu"
}]
},
{ "id" : 4 ,
"text" : "Family" ,
"children" : [{
"id" : 5,
"text" : "ChildVM"
},
{
"id" : 6,
"text" : "ChildPM"
}]
}
]
}