Model tree structure with aggregation in MongoDb - mongodb

I want to aggregate a model tree structure directly in mongodb database with Aggregations.
Is it possible to do hierarchical aggregations like that ? Currently, I do that in a program.
I want to use a collection like :
{
"Name" : "john",
"Parents" : ["sandy", "bryan"]
}
{
"Name" : "sandy",
"Parents" : ["bill", "daisy"]
}
{
"Name" : "bryan",
"Parents" : ["dora", "david"]
}
{
"Name" : "dora",
"Parents" : ["cliff", "darla"]
}
And generate a new collection like :
{
"Name" : "sandy",
"Parents" : ["bill", "daisy"],
"Ancestrors" : ["bill", "daisy"]
}
{
"Name" : "dora",
"Parents" : ["cliff", "darla"],
"Ancestrors" : ["cliff", "darla"]
}
{
"Name" : "bryan",
"Parents" : ["dora", "david"],
"Ancestrors" : ["dora", "david", "cliff", "darla"]
}
{
"Name" : "john",
"Parents" : ["sandy", "bryan"],
"Ancestrors" : ["sandy", "bryan", "bill", "daisy", "dora", "david", "cliff", "darla"]
}

I don't think that use a MapReduce to do a tree structure aggregation is possible since MongoDb 2.4 because we can't use "db.mycollection.find(...)" in map functions.
So we can't retrieve hierarchicals documents in map functions...
In MongoDB 2.4, map-reduce operations, the group command, and $where operator expressions cannot access certain global functions or
properties, such as db, that are available in the mongo shell.
When upgrading to MongoDB 2.4, you will need to refactor your code if
your map-reduce operations, group commands, or $where operator
expressions include any global shell functions or properties that are
no longer available, such as db.

Related

MongoDB get all embedded documents where condition is met

I did this in my mongodb:
db.teams.insert({name:"Alpha team",employees:[{name:"john"},{name:"david"}]});
db.teams.insert({name:"True team",employees:[{name:"oliver"},{name:"sam"}]});
db.teams.insert({name:"Blue team",employees:[{name:"jane"},{name:"raji"}]});
db.teams.find({"employees.name":/.*o.*/});
But what I got was:
{ "_id" : ObjectId("5ddf3ca83c182cc5354a15dd"), "name" : "Alpha team", "employees" : [ { "name" : "john" }, { "name" : "david" } ] }
{ "_id" : ObjectId("5ddf3ca93c182cc5354a15de"), "name" : "True team", "employees" : [ { "name" : "oliver" }, { "name" : "sam" } ] }
But what I really want is
[{"name":"john"},{"name":"oliver"}]
I'm having a hard time finding examples of this without using some kind of programmatic iterator/loop. Or examples I find return the parent document, which means I'd have to parse out the embedded array employees and do some kind of UNION statement?
Eg.
How to get embedded document in mongodb?
Retrieve only the queried element in an object array in MongoDB collection
Can someone point me in the right direction?
Please add projections to filter out the fields you don't need. Please refer the project link mongodb projections
Your find query should be constructed with the projection parameters like below:
db.teams.find({"employees.name":/.*o.*/}, {_id:0, "employees.name": 1});
This will return you:
[{"name":"john"},{"name":"oliver"}]
Can be solved with a simple aggregation pipeline.
db.teams.aggregate([
{$unwind : "$employees"},
{$match : {"employees.name":/.*o.*/}},
])
EDIT:
OP Wants to skip the parent fields. Modified query:
db.teams.aggregate([
{$unwind : "$employees"},
{$match : {"employees.name":/.*o.*/}},
{$project : {"name":"$employees.name",_id:0}}
])
Output:
{ "name" : "john" }
{ "name" : "oliver" }

Zipping two collections in mongoDB

Not a question about joins in mongoDB
I have two collections in mongoDB, which do not have a common field and which I would like to apply a zip function to (like in Python, Haskell). Both collections have the same number of documents.
For example:
Let's say one collection (Users) is for users, and the other (Codes) is of unique randomly generated codes.
Collection Users:
{ "_id" : ObjectId(""), "userId" : "123"}
{ "_id" : ObjectId(""), "userId" : "456"}
Collection Codes:
{ "_id" : ObjectId(""), "code" : "randomCode1"}
{ "_id" : ObjectId(""), "code" : "randomCode2"}
The desired output would to assign a user to a unique code. As follows:
Output
{ "_id" : ObjectId(""), "code" : "randomCode1", "userId" : "123"}
{ "_id" : ObjectId(""), "code" : "randomCode2", "userId" : "456"}
Is there any way of doing this with the aggregation pipeline?
Or perhaps with map reduce? Don't think so because it only works on one collection.
I've considered inserting another random id into both collections for each document pair, and then using $lookup with this new id, but this seems like an overkill. Also the alternative would be to export and use Python, since there aren't so many documents, but again I feel like there should be a better way.
I would do something like this to get the records from collection 1 & 2 and merge the required fields into single object.
You have already confirmed that number of records in collection 1 and 2 are same.
The below code will loop through the cursor and map the required fields into one object. Finally, you can print the object to console or insert into another new collection (commented the insert).
var usersCursor = db.users.find( { } );
var codesCursor = db.codes.find( { } );
while (usersCursor.hasNext() && codesCursor.hasNext()) {
var user = usersCursor.next();
var code = codesCursor.next();
var outputObj = {};
outputObj ["_id"] = new ObjectId();
outputObj ["userId"] = user["userId"];
outputObj ["code"] = code["code"];
printjson( outputObj);
//db.collectionName.insertOne(outputObj);
}
Output:-
{
"_id" : ObjectId("58348512ba41f1f22e600c74"),
"userId" : "123",
"code" : "randomCode1"
}
{
"_id" : ObjectId("58348512ba41f1f22e600c75"),
"userId" : "456",
"code" : "randomCode2"
}
Unlike relational database in MongoDB you doing JOIN stuff at the app level (so it will be easy to horizontal scale the database). You need to do that in the app level.

Create a new aggregate collection from two existing collections?

I have a unique field in two collections which is "ip". One collection contains machine data and the other contains geographic data
Is there a way to aggregate data from two collections and create a third collection with this data?
For example:
geo:
"ip" : "1.1.1.1", "lat" : 1.29, "lon" : 103.86
"ip" : "2.2.2.2", "lat" : 1.29, "lon" : 103.86
machines:
"ip" : "1.1.1.1", "load" : 5
"ip" : "2.2.2.2", "load" : 7
## becomes a new collection
"lat" : 1.29, "lon" : 103.86, "load" : 12
I am using the Python driver for MongoDB.
yes there is a way to do it with $out statement
let's say that you have two collections "pc" and "geo" and you are going to create collection "allData" - and mongo query will look like this:
db.pc.aggregate([
{
$lookup:
{
from:"geo" ,
localfield:"ip",
foreginField:"ip",
as: geoData
}
},
{
$unwind:"$geoData"
},
{$project:{transform document as needed}},
{
$out :"allData"
}
])
To shape your allData collection you can use $project to add/remove fields as desired.

MongoDB DBReference how to?

i'm learning MongoDB and i have the next questions.
There are my MongoDB documents
This is coordenada document
> db.coordenada.find().pretty()
{
"_id" : ObjectId("5579b81342a31549b67ad00c"),
"longitud" : "21.878382",
"latitud" : "-102.277364"
}
{
"_id" : ObjectId("5579b85542a31549b67ad00d"),
"longitud" : "21.878626",
"latitud" : "-102.280379"
}
{
"_id" : ObjectId("5579b89442a31549b67ad00e"),
"longitud" : "21.878845",
"latitud" : "-102.283512"
}
{
"_id" : ObjectId("5579b8bf42a31549b67ad00f"),
"longitud" : "21.879253",
"latitud" : "-102.286698"
}
{
"_id" : ObjectId("5579b8dd42a31549b67ad010"),
"longitud" : "21.879203",
"latitud" : "-102.291558"
}
{
"_id" : ObjectId("5579b8fd42a31549b67ad011"),
"longitud" : "21.878427",
"latitud" : "-102.296375"
}
{
"_id" : ObjectId("5579b91d42a31549b67ad012"),
"longitud" : "21.877571",
"latitud" : "-102.299659"
}
And this is rutas document
> db.rutas.find().pretty()
{
"_id" : "1",
"nombre" : "Ruta Penal",
"numero" : "20",
"coordenadas" : [
DBRef("coordenada", "5579b91d42a31549b67ad012")
]
}
{
"_id" : "2",
"nombre" : "Ruta Penal",
"numero" : "20",
"coordenadas" : [
DBRef("coordenada", "5579b91d42a31549b67ad012")
]
}
{
"_id" : "3",
"nombre" : "Ruta Penal",
"numero" : "20",
"coordenadas" : [
DBRef("coordenada", "5579b85542a31549b67ad00d")
]
}
{
"_id" : 6,
"nombre" : "Ruta Penal",
"numero" : "20",
"coordenadas" : [
DBRef("coordenada", "5579b85542a31549b67ad00d")
]
}
>
What i'm tryin to do, it's obtain the "longitud" and "latitud" from "coordenada" but only for the "numero" 20 of "rutas" document for instance
How can i do this?
PS sorry for the spanish terms.
According to the mongodb site for DBRef, you need to use drivers to unpack reference. I don't think mongo shell can unpack it for you.
http://docs.mongodb.org/manual/reference/database-references/
To resolve DBRefs, your application must perform additional queries to return the referenced documents. Many drivers have helper methods that form the query for the DBRef automatically. The drivers [1] do not automatically resolve DBRefs into documents.
DBRefs provide a common format and type to represent relationships among documents. The DBRef format also provides common semantics for representing links between documents if your database must interact with multiple frameworks and tools.
Unless you have a compelling reason to use DBRefs, use manual references instead.
Based on that, I would suggest to change it using manual reference (just the document id) instead.
To answer your question however, you can use any language drivers but below is an example in Python using pymongo:
from pymongo import MongoClient
from bson.objectid import ObjectId
from bson.dbref import DBRef
client = MongoClient()
db = client.testDB
rutas_20 = list(db.rutas.find({"numero": "20"}))
for ruta in rutas_20:
for coordenada in ruta.get('coordenada'):
coord_doc = db.coordenada.find_one({"_id": ObjectId(coordenada.id) })
print coord_doc.get('longitud'), coord_doc.get('latitud')
You can also use db.dereference(DBRef()) as well.
Hope it helps.
Cheers.
Yes, you can definitely obtain the latitude and longitude of the particular item by referencing the object id of the other class.
To use momgo dbRef you have to use the specific drivers depending on the particular language you are using. The driver documentation will tell you about the functions you can use.
I use PHP and hence refer to,
http://www.php.net/manual/en/class.mongodbref.php/

Get specific object in array of array in MongoDB

I need get a specific object in array of array in MongoDB.
I need get only the task object = [_id = ObjectId("543429a2cb38b1d83c3ff2c2")].
My document (projects):
{
"_id" : ObjectId("543428c2cb38b1d83c3ff2bd"),
"name" : "new project",
"author" : ObjectId("5424ac37eb0ea85d4c921f8b"),
"members" : [
ObjectId("5424ac37eb0ea85d4c921f8b")
],
"US" : [
{
"_id" : ObjectId("5434297fcb38b1d83c3ff2c0"),
"name" : "Test Story",
"author" : ObjectId("5424ac37eb0ea85d4c921f8b"),
"tasks" : [
{
"_id" : ObjectId("54342987cb38b1d83c3ff2c1"),
"name" : "teste3",
"author" : ObjectId("5424ac37eb0ea85d4c921f8b")
},
{
"_id" : ObjectId("543429a2cb38b1d83c3ff2c2"),
"name" : "jklasdfa_XXX",
"author" : ObjectId("5424ac37eb0ea85d4c921f8b")
}
]
}
]
}
Result expected:
{
"_id" : ObjectId("543429a2cb38b1d83c3ff2c2"),
"name" : "jklasdfa_XXX",
"author" : ObjectId("5424ac37eb0ea85d4c921f8b")
}
But i not getting it.
I still testing with no success:
db.projects.find({
"US.tasks._id" : ObjectId("543429a2cb38b1d83c3ff2c2")
}, { "US.tasks.$" : 1 })
I tryed with $elemMatch too, but return nothing.
db.projects.find({
"US" : {
"tasks" : {
$elemMatch : {
"_id" : ObjectId("543429a2cb38b1d83c3ff2c2")
}
}
}
})
Can i get ONLY my result expected using find()? If not, what and how use?
Thanks!
You will need an aggregation for that:
db.projects.aggregate([{$unwind:"$US"},
{$unwind:"$US.tasks"},
{$match:{"US.tasks._id":ObjectId("543429a2cb38b1d83c3ff2c2")}},
{$project:{_id:0,"task":"$US.tasks"}}])
should return
{ task : {
"_id" : ObjectId("543429a2cb38b1d83c3ff2c2"),
"name" : "jklasdfa_XXX",
"author" : ObjectId("5424ac37eb0ea85d4c921f8b")
}
Explanation:
$unwind creates a new (virtual) document for each array element
$match is the query part of your find
$project is similar as to project part in find i.e. it specifies the fields you want to get in the results
You might want to add a second $match before the $unwind if you know the document you are searching (look at performance metrics).
Edit: added a second $unwind since US is an array.
Don't know what you are doing (so realy can't tell and just sugesting) but you might want to examine if your schema (and mongodb) is ideal for your task because the document looks just like denormalized relational data probably a relational database would be better for you.