MongoDB join two complex collection - mongodb

I got two collections
data
{
_id: ObjectId('123'),
uuid: '123abc'
content: 'hello'
}
{
_id: ObjectId('456'),
uuid: '123abc'
content: 'hi'
}
history
{
_id: ObjectId('xxx'),
uuid: '123abc'
data: [{path: '/hello.json', objectId: '123'}, {path: '/hi.json', objectId: '456'}]
}
I want
{
_id: ObjectId('123'),
uuid: '123abc'
content: 'hello'
path: '/hello.json'
}
{
_id: ObjectId('456'),
uuid: '123abc'
content: 'hi'
path: '/hi.json'
}
Step:
use the uuid to find the second json
use the objectId of the second json data array element to locate the first json
Does anyone who knows how to write the MongoDB operations to join the two collections
the result after the second stage is finished

You can do it with Aggregation Framework:
$lookup - To fetch data from the history collection
$set with $first - To get the first element from the history property, since the $lookup always return an array
$set with $filter - To create a path_element property that has the filtered history based on the document _id.
$project - to project the data as expected output.
db.data.aggregate([
{
"$lookup": {
"from": "history",
"localField": "uuid",
"foreignField": "uuid",
"as": "history"
}
},
{
"$set": {
"history": {
"$first": "$history"
}
}
},
{
$set: {
path_element: {
"$filter": {
"input": "$history.data",
"cond": {
$eq: [
{
$toString: "$_id"
},
"$$this.objectId"
]
}
}
}
}
},
{
$project: {
_id: 1,
uuid: 1,
content: 1,
path: {
"$getField": {
"field": "path",
"input": {
$first: "$path_element"
}
}
}
}
}
])
Working example

Related

Relate and Count Between Two Collections in MongoDB

How can I count the number of completed houses designed by a specific architect in MongoDB?
I have the next two collections, "plans" and "houses".
Where the only relationship between houses and plans is that houses have the id of a given plan.
Is there a way to do this in MongoDB with just one query?
plans
{
_id: ObjectId("6388024d0dfd27246fb47a5f")
"hight": 10,
"arquitec": "Aneesa Wade",
},
{
_id: ObjectId("1188024d0dfd27246fb4711f")
"hight": 50,
"arquitec": "Smith Stone",
}
houses
{
_id: ObjectId
"plansId": "6388024d0dfd27246fb47a5f" -> string,
"status": "under construction",
},
{
_id: ObjectId
"plansId": "6388024d0dfd27246fb47a5f" -> string,
"status": "completed",
}
What I tried was to use mongo aggregations while using $match and $lookup.
The "idea" with clear errors would be something like this.
db.houses.aggregate([
{"$match": {"status": "completed"}},
{
"$lookup": {
"from": "plans",
"pipeline": [
{
"$match": {
"$expr": {
"$and": [
{ "$eq": [ "houses.plansId", { "$toString": "$plans._id" }]},
{ "plans.arquitec" : "Smith Stone" },
]
}
}
},
],
}
}
If it's a single join condition, simply do a project to object ID to avoid any complicated lookup pipelines.
Example playground - https://mongoplayground.net/p/gaqxZ7SzDTg
db.houses.aggregate([
{
$match: {
status: "completed"
}
},
{
$project: {
_id: 1,
plansId: 1,
status: 1,
plans_id: {
$toObjectId: "$plansId"
}
}
},
{
$lookup: {
from: "plans",
localField: "plans_id",
foreignField: "_id",
as: "plan"
}
},
{
$project: {
_id: 1,
plansId: 1,
status: 1,
plan: {
$first: "$plan"
}
}
},
{
$match: {
"plan.arquitec": "Some One"
}
}
])
Update: As per OP comment, added additional match stage for filtering the final result based on the lookup response.

MongoDB aggregation, use value from one document as key in another

So I’m trying to aggregate two documents matched on an id and based on the value of the first.
Document 1
{
“id”:3
“Whats for dinner”: “dinner”,
“What is for dinner tonight”: “dinner”,
“Whats for lunch”:“lunch”
}
Document 2
{
“Id”:3
“dinner” : “We are having roast!”,
“lunch” : “We are having sandwiches”
}
I’d like to start by matching the id and test if the question exists in doc1.
then return the question from doc1 and the answer from doc 2 . Like
{“Whats for dinner”:“We are having roast!”}
I’ve tried:
{ “$match”: { “id”: 3, “Whats for dinner”:{"$exists":True}} },
{
"$lookup": {
"from": "doc 2",
"localField": "id",
"foreignField": "id",
"as": "qa"
}
}
But from here I can’t figure out how to use the value from doc1 as key in doc2
It might be simple! but I’m a new to this, and just can’t get it to work!?
Crazy data model! This would be a solution:
db.doc1.aggregate([
{ $project: { data: { $objectToArray: "$$ROOT" } } },
{ $unwind: "$data" },
{
$lookup: {
from: "doc2",
pipeline: [
{ $project: { data: { $objectToArray: "$$ROOT" } } }
],
as: "answers"
}
},
{
$set: {
answers: {
$first: {
$filter: {
input: { $first: "$answers.data" },
cond: { $eq: [ "$$this.k", "$data.v" ] }
}
}
}
}
},
{ $match: { answers: { $exists: true } } },
{
$project: {
data: [
{
k: "$data.k",
v: "$answers.v"
}
]
}
},
{ $replaceWith: { $arrayToObject: "$data" } }
])
Mongo Playground
Better don't use any user data as key names, you will always have to juggle with $objectToArray and $arrayToObject
Maybe consider this:
questions: {
guildid: 3,
text: [
"Whats for dinner",
"What is for dinner tonight",
"Whats for lunch"
],
"nospace": 1
}

Mongodb $lookup nested objects with pipeline

I have the following schema Thing:
{
name: "My thing",
files: [
{
name: "My file 1",
versions: [
{
file_id: ObjectId("blahblahblah")
},
{
file_id: ObjectId("blahblahblah")
},
],
},
{
name: "My file 2",
versions: [
{
file_id: ObjectId("blahblahblah")
},
{
file_id: ObjectId("blahblahblah")
},
],
}
]
}
And then I a have a File schema:
{
_id: ObjectId("blahblah"),
type: "image",
size: 1234,
}
The file_id in the Thing schema is a REF to the _id of the File schema.
I want to $lookup all the files inside my Thing. So I started with this:
{
"$lookup": {
"from": "files",
"let": { "files": "$files" },
"pipeline": [
{ "$match": { "$expr": { "$in": [ "$_id", "$$files.versions.file_id" ] } } }.
],
"as": "files.versions.file"
}
}
But it's obviously wrong. Can someone help?
The problem is when we $$files.versions.file_id access ids it will return array of array of ids so $in will not match nested array of ids,
I can see you are trying to project file details in same nested level, so direct lookup will not set that detail in nested array, you have to deconstruct the array first before set files details,
$unwind deconstruct files array
$unwind deconstruct versions array
$lookup with files collection and pass files.versions.file_id as localField
$unwind deconstruct files.versions.file_id array
$group by name and file name and re-construct versions array
$group by name only and reconstruct files array
{ $unwind: "$files" },
{ $unwind: "$files.versions" },
{
$lookup: {
from: "files",
localField: "files.versions.file_id",
foreignField: "_id",
as: "files.versions.file_id"
}
},
{ $unwind: "$files.versions.file_id" },
{
$group: {
_id: {
name: "$name",
file_name: "$files.name"
},
versions: { $push: "$files.versions" }
}
},
{
$group: {
_id: "$_id.name",
files: {
$push: {
name: "$_id.file_name",
versions: "$versions"
}
}
}
}
Playground

How to achieve MongoDB nested lookup inside array?

I am doing an aggregation in Paper collection like below
const papers = await Paper.aggregate([
{
"$lookup": {
"from": "reviews",
"localField": "reviewId",
"foreignField": "_id",
"as": "review"
}
},
{ $unwind: '$review' }
]);
It returns the result that contains review object which has a reviews array like:
[
{
...
review: {
_id: 5f1638770f3a8d20f8c1beeb,
reviews: [Array],
},
...
}
]
If I make the review more clear, it is like below:
{
_id: 5f1638770f3a8d20f8c1beeb
reviews: [
{
_id: 5f164395857bcdd1d8674b69,
reviewerId: 5f15b28d534b5886c0d9eb8a
},
{
_id: 5f164395857bcdd1d8674b6a,
reviewerId: 5f1358c523dc2367c43a6311
}
]
}
In above, reviewerId inside reviews array refers to user id from "users" collection. I want to get users name, email, and address in reviews array like below:
{
reviews: [
{
_id: 5f164395857bcdd1d8674b69,
reviewerId: 5f15b28d534b5886c0d9eb8a
reviewer : {
name:"some_name",
email:"abc#example.com"
}
},
{
_id: 5f164395857bcdd1d8674b6a,
reviewerId: 5f1358c523dc2367c43a6311
reviewer : {
name:"some_name",
email:"efg#example.com"
}
}
]
}
How can I achieve it?
Hopefully, the structure of your collection almost similar as I mention below in the Mongo playground.
db.reviews.aggregate([
{
$unwind: {
path: "$reviews",
preserveNullAndEmptyArrays: false
}
},
{
$lookup: {
from: "user",
localField: "reviews.reviewerId",
foreignField: "_id",
as: "reviews.reviewer"
}
},
{
$group: {
_id: "$_id",
question: {
$first: "$question"
},
reviews: {
$addToSet: "$reviews"
}
}
}
])
Working Mongo playground

How to order mongodb $lookup query

Overview
A few devices are collecting data and sending it to a Node/MongoDb endpoint. Then, the user would use an endpoint to get all that data into a json.
Models
Device Model
const deviceSchema = new Schema({
group: { type: Schema.Types.ObjectId, ref: 'Group' },
deviceId: { type: String, unique: true },
name: String,
notes: String,
pac: String,
endCertificate: String,
lat: Number,
lng: Number
});
Message Model
const messageSchema = new Schema({
deviceId: { type: String, required: true },
raw: { type: String, required: true },
receivedAt: { type: Date, default: Date.now() }
});
One device can have N messages
Problem to solve
I want to get a json that has all the devices and have an array containing
all the messages that belongs to that device.
[
{
"id":"5b86c323e95759603ad7ea54",
"deviceId":"Device 01",
"name":"Device bla",
"notes":"...",
"pac":"pac",
"lat":-20.817396,
"endCertificate":"cert",
"lng":-27.031321,
"messages":[
{
"id":"5b869a42e0b94041b5f21eed",
"deviceId":"Device 01",
"raw":"1111111",
"receivedAt":"2018-08-29T13:04:43.641Z",
"__v":0
},
{
"id":"5b8c782fef4f8e98783f6f35",
"deviceId":"Device 01",
"raw":"2222222",
"receivedAt":"2018-09-01T09:04:43.641Z",
"__v":0
},
{
"id":"5b8c7840ef4f8e98783f6f3e",
"deviceId":"Device 01",
"raw":"3333333",
"receivedAt":"2018-09-02T09:04:43.641Z",
"__v":0
}
]
},
{
"id":"5b8c28ec38c51813cd159bac",
"deviceId":"Device 02",
"name":"Device ...",
"notes":"...",
"lat":-27.812296,
"lng":-27.073314,
"__v":0,
"messages":[
{
"id":"5b8c784cef4f8e98783f6f43",
"deviceId":"Device 02",
"raw":"1111111",
"receivedAt":"2018-09-01T09:04:43.641Z",
"__v":0
}
]
}
]
My solution
To get a json as the one above I have:
const [results, itemCount] = await Promise.all([
Device.aggregate([
{ $match: {} },
{
$lookup: {
from: 'messageschemas',
localField: 'deviceId',
foreignField: 'deviceId',
as: 'messages'
}
}
]).limit(req.query.limit).skip(req.skip)
.exec(),
Device.countDocuments(match)
]);
res.setHeader('X-Total-Count', itemCount);
res.send(results);
My question
How can I order the messages I get from the $lookup into messages[] by
'receivedAt'?
You need to $unwind the messages array and can apply $sort with receivedAt field and finally $group to rollback again into the array.
Device.aggregate([
{ "$match": { }},
{ "$lookup": {
"from": "messageschemas",
"localField": "deviceId",
"foreignField": "deviceId",
"as": "messages"
}},
{ "$unwind": "$messages" },
{ "$sort": { "messages.receivedAt": 1 }},
{ "$group": {
"_id": "$_id",
"messages": { "$push": "$messages" }
}}
])
Which can be simply done with the 3.6 $lookup syntax
Device.aggregate([
{ "$match": { }},
{ "$lookup": {
"from": "messageschemas",
"let": { "deviceId": "$deviceId" },
"pipeline": [
{ "$match": { "$expr": { "$eq": [ "$deviceId", "$$deviceId" ] } } },
{ "$sort": { "receivedAt": 1 }}
],
"as": "messages"
}}
])