MongoDB: Filtering aggregation by field values and nested arrays - mongodb

I built up a graph structure using MongoDB. I did some complex queries on this structure already, but I am struggling on selecting a subgraph of a given depth starting from a specific node via the aggregation pipeline.
I already did use the $graphLookup to get all the required nodes, which gives the following result:
{ "_id" : "O_4", "name" : "D", "type" : "Info", "links" : [ { "link" : "L_2", "objectId" : "O_1" }, { "link" : "L_4", "objectId" : "O_3" }, { "link" : "L_10", "objectId" : "O_6" } ] }
{ "_id" : "O_2", "name" : "B", "type" : "Info", "links" : [ { "link" : "L_1", "objectId" : "O_1" }, { "link" : "L_3", "objectId" : "O_3" } ] }
{ "_id" : "O_1", "name" : "A", "type" : "Info", "links" : [ { "link" : "L_1", "objectId" : "O_2" }, { "link" : "L_2", "objectId" : "O_4" } ] }
{ "_id" : "O_3", "name" : "C", "type" : "Info", "links" : [ { "link" : "L_3", "objectId" : "O_2" }, { "link" : "L_4", "objectId" : "O_4" }, { "link" : "L_5", "objectId" : "O_5" }, { "link" : "L_6", "objectId" : "O_7" } ] }
{ "_id" : "O_6", "name" : "F", "type" : "System", "links" : [ { "link" : "L_8", "objectId" : "O_7" }, { "link" : "L_9", "objectId" : "O_5" }, { "link" : "L_10", "objectId" : "O_4" } ] }
But now I want to remove the nested "link" objects (in array "links") where the "objectId" is not present in the above result, i.e. in "O_6" the link "L_8" should be removed, since the node "O_7" is not part of the subgraph.
I already tried playing around with $in, $facet and other stuff to get this problem solved, but it seems like I am unable ...
Maybe, you guys can help out?
Edit:
Just found a solution more or less - $filter does a decent job here:
{
$unwind: "$links"
}, {
$group: {
_id: null,
ids: {
$addToSet: "$_id"
},
links: {
$addToSet: "$links"
}
}
}, {
$project: {
links: {
$filter: {
input: "$links",
as: "link",
cond: {
$in: ["$$link.objectId", "$ids"]
}
}
}
}
}, {
$unwind: "$links"
}, {
$replaceRoot: {
newRoot: "$links"
}
}, {
$group: {
_id: "$link"
}
}
Returns what I needed - the list of Link-IDs:
{ "_id" : "L_1" }
{ "_id" : "L_10" }
{ "_id" : "L_3" }
{ "_id" : "L_2" }
{ "_id" : "L_4" }

Related

How to get the document inside a field out after "group by" in MongoDB?

The documents I work on are like this:
{
"id" : "98syf87erfw8n"
"foo" : { "objectid" : "39", "stuff" : "65" },
"yearpublished" : "1979",
.
.
.
}
This is the query I used:
db.foobar.aggregate([
{ $group : {
_id : '$yearpublished',
myItem: { $first: "$$ROOT" }
}}
])
The final output is in the form:
{
{ "_id" : "1923", "myItem" : {
"id" : "98syf87erfw8n",
"foo" : { "objectid" : "39", "stuff" : "65" },
"yearpublished" : "1979"
}
},
{ "_id" : "1453", "myItem" : {
"id" : "88888888888",
"foo" : { "objectid" : "394", "stuff" : "55" },
"yearpublished" : "1453"
"author" : "Ravi Kiran"
}
}
};
But I want the output as:
{
"id" : "98syf87erfw8n",
"foo" : { "objectid" : "39", "stuff" : "65" },
"yearpublished" : "1979"
},
{
"id" : "88888888888",
"foo" : { "objectid" : "394", "stuff" : "55" },
"yearpublished" : "1453",
"author" : "Ravi Kiran"
}
This means I want to get those documents inside the field myItem out.
How can I do that?
You could use $replaceRoot
Play
db.collection.aggregate({
"$replaceRoot": {
"newRoot": "$myItem"
}
})
There are syntax errors in your output.
"id" : 98syf87erfw8n, is invalid.
It should be "id" : "98syf87erfw8n", Note the quotes
There is comma missing after yearPublished in the second object.
One more project stage after the $group can do the job:
{
$project: {
yearpublished: "$myItem.yearpublished",
foo: "$myItem.foo",
id: "$myItem.id",
_id: 0
}
}
playground

How to copy a value between fields within each object in an embedded array of objects in MongoDB?

In my 'assemblies' collection, each document contains an embedded array of objects called 'partlist':
{
"_id" : ObjectId("0001"),
"pn" : "01",
"title" : "MyAssembly",
"partlist" : [
{
"id" : "",
"pn" : "1234",
"desc" : "myPart1",
},
{
"id" : "",
"pn" : "5678",
"desc" : "myPart2",
}]
}
Within each object, I need to copy the value from 'partlist.pn' into 'partlist.id'. I used:
db.assemblies.aggregate([{$set:{"partlist.id":"$partlist.pn"}}])
hoping to achieve this:
{
"_id" : ObjectId("0001"),
"pn" : "01",
"title" : "MyAssembly",
"partlist" : [
{
"id" : "1234",
"pn" : "1234",
"desc" : "myPart1",
},
{
"id" : "5678",
"pn" : "5678",
"desc" : "myPart2",
}]
}
Instead it returned to 'id' an array of ALL the 'pn' values in 'partlist':
{
"_id" : ObjectId("0001"),
"pn" : "01",
"title" : "MyAssembly",
"partlist" : [
{
"id" : [
"1234",
"5678"
],
"pn" : "1234",
"desc" : "myPart1",
},
{
"id" : [
"1234",
"5678"
],
"pn" : "5678",
"desc" : "myPart2",
}]
}
What is the correct syntax for copying the one value within each object?
What you can do is, you can use $map to modify each elements and mearg the id with the help of $mergeObject
db.collection.aggregate([
{
$addFields: {
"partlist": {
$map: {
input: "$partlist",
in: {
"$mergeObjects": [
"$$this",
{
id: "$$this.pn"
}
]
}
}
}
}
}
])
Working Mongo playground
If the pn does not exist it retrieves initial object.
db.collection.aggregate([
{
$addFields: {
partlist: {
$map: {
input: "$partlist",
as: "p",
in: {
$cond: [
"$$p.pn",
{
$mergeObjects: [
"$$p",
{
"id": "$$p.pn"
}
]
},
"$$p"
]
}
}
}
}
}
])
Playground

MongoDB join two collections with between clause

There is a collection "Printers":
{
"_id" : ObjectId("5cc02f9b9931de72296ba6c2"),
"model" : "Xerox WorkCentre 3315",
"serial" : "3255498494",
"date" : ISODate("2019-04-25T08:57:48.001+0000"),
"pages" : NumberInt(4868),
"location" : "New location",
"ip" : "10.159.0.35",
"ip_int" : NumberInt(178192419)
}
and "Branches" collection:
{
"_id" : ObjectId("5cb4799b8c0cfe35e4a4c266"),
"name" : "Office 1",
"ip_start" : NumberLong(178192384),
"ip_end" : NumberLong(178194431)
}
// ----------------------------------------------
{
"_id" : ObjectId("5cb479e68c0cfe35e4a4c269"),
"name" : "Office 2",
"ip_start" : NumberLong(3232258048),
"ip_end" : NumberLong(3232258303)
}
"Branches" collection contains ip addresses converted into integer value, i.e. 192.168.0.1 is 3232235521. Each record in Branches describes subnet.
Each printer located in one branch.
If printers.ip_int between branches record [ip_start;ip_end] then query should return all fields from Printer and one field "Name" from "Branches" collection.
How can i do this?
You need a lookup with custom pipeline where you can specify "between" condition:
db.Branches.aggregate([
{
$lookup: {
from: "Printers",
let: { ip_start: "$ip_start", ip_end: "$ip_end" },
pipeline: [
{
$match: {
$expr: {
$and: [
{ "$gte": [ "$ip_int", "$$ip_start" ] },
{ "$lte": [ "$ip_int", "$$ip_end" ] },
]
}
}
}
],
as: "Printers"
}
}
])
db.getCollection("printers").aggregate(
[
{
"$lookup" : {
"from" : "branches",
"let" : {
"ip_int" : "$ip_int"
},
"pipeline" : [
{
"$match" : {
"$expr" : {
"$and" : [{"$gte" : ["$$ip_int", "$ip_start"]},
{ "$lte" : ["$$ip_int", "$ip_end"]}
]
}
}
}
], "as" : "Printers"
}
},
{
"$sort" : {
"ip_int" : 1.0
}
},
{
"$unwind" : {
"path" : "$Printers"
}
},
{
"$addFields" : {
"filial" : "$Printers.name"
}
},
{
"$project" : {
"Printers" : false, "ip_int" : false
}
}
]);

Group by array element in Mongodb

We have nested document and trying to group by array element. Our document structure looks like
/* 1 */
{
"_id" : ObjectId("5a690a4287e0e50010af1432"),
"slug" : [
"true-crime-the-10-most-infamous-american-murder-mysteries",
"10-most-infamous-american-murder-mysteries"
],
"tags" : [
{
"id" : "59244aa6b1be5055278e9b5b",
"name" : "true crime",
"_id" : "59244aa6b1be5055278e9b5b"
},
{
"id" : "5924524db1be5055278ebd6e",
"name" : "Occult Museum",
"_id" : "5924524db1be5055278ebd6e"
},
{
"id" : "5a690f0fc1a72100110c2656",
"_id" : "5a690f0fc1a72100110c2656",
"name" : "murder mysteries"
},
{
"id" : "59244d71b1be5055278ea654",
"name" : "unsolved murders",
"_id" : "59244d71b1be5055278ea654"
}
]
}
We want to find list of all slugs group by tag name. I am trying with following and it gets result but it isn't accurate. We have hundreds of records with each tag but i only get few with my query. I am not sure what i am doing wrong here.
Thanks in advance.
// Requires official MongoShell 3.6+
db.getCollection("test").aggregate(
[
{
"$match" : {
"item_type" : "Post",
"site_id" : NumberLong(2),
"status" : NumberLong(1)
}
},
{$unwind: "$tags" },
{
"$group" : {
"_id" : {
"tags᎐name" : "$tags.name",
"slug" : "$slug"
}
}
},
{
"$project" : {
"tags.name" : "$_id.tags᎐name",
"slug" : "$_id.slug",
"_id" : NumberInt(0)
}
}
],
{
"allowDiskUse" : true
}
);
Expected output is
TagName Slug
----------
true crime "true-crime-the-10-most-infamous-american-murder-mysteries",
"10-most-infamous-american-murder-mysteries"
"All records where tags true crime"
Instead of using slug as a part of _id you should use $push or $addToSet to accumulate them, try:
db.test.aggregate([
{
$unwind: "$tags"
},
{
$unwind: "$slug"
},
{
$group: {
_id: "$tags.name",
slugs: { $addToSet: "$slug" }
}
},
{
$project: {
_id: 1,
slugs: {
$reduce: {
input: "$slugs",
initialValue: "",
in: {
$concat: [ "$$value", ",", "$$this" ]
}
}
}
}
}
])
EDIT: to get comma separated string for slugs you can use $reduce with $concat
Output:
{ "_id" : "murder mysteries", "slugs" : ",10-most-infamous-american-murder-mysteries,true-crime-the-10-most-infamous-american-murder-mysteries" }
{ "_id" : "Occult Museum", "slugs" : ",10-most-infamous-american-murder-mysteries,true-crime-the-10-most-infamous-american-murder-mysteries" }
{ "_id" : "unsolved murders", "slugs" : ",10-most-infamous-american-murder-mysteries,true-crime-the-10-most-infamous-american-murder-mysteries" }
{ "_id" : "true crime", "slugs" : ",10-most-infamous-american-murder- mysteries,true-crime-the-10-most-infamous-american-murder-mysteries" }

Querying subdocument inside a subdocument of a collection in Mongodb

I have the collection named companies as below.
I want to query the url corresponding to C1S1category1 . I do not know which companyName it belongs and which catergories it belongs to.
Please can you let me know what query I need to use in Mongoshell to query the document having catergoryname as C1S1category1
{"companyName": "C1",
"url": "www.com1",
"categories" : [
{"SlNo" : 1,
"url" : "www.com1",
"subcategories" : [
{
"CatergoryName":"C1S1category1",
"Url" : "www.com3"
},
{
"CatergoryName":"C1S1category2",
"Url" : "www.com3"
}
]
},
{
"SlNo" : 2,
"url" : "www.com1",
"subcategories" : [
{
"CatergoryName":"C1S2category1",
"Url" : "www.com3"
},
{
"CatergoryName":"C1S2category2",
"Url" : "www.com3"
}
]
}
]
},
{"companyName": "C2",
"url": "www.com21",
"categories" : [
{"SlNo" : 1,
"url" : "www.com22",
"subcategories" : [
{
"CatergoryName":"C2S1category1",
"Url" : "www.com23"
},
{
"CatergoryName":"C2S1category2",
"Url" : "www.com23"
}
]
},
{
"SlNo" : 2,
"url" : "www.com1",
"subcategories" : [
{
"CatergoryName":"C2S2category1",
"Url" : "www.com23"
},
{
"CatergoryName":"C2S2category2",
"Url" : "www.com23"
}
]
}
]
}
You need to use $elemMatch to get required output as following:
db.collection.find({
"categories": {
$elemMatch: {
subcategories: {
$elemMatch: {
CatergoryName: "C1S1category1"
}
}
}
}
},{"categories.$":1,"companyName":1,"url":1}).pretty()