MongoDB aggregation matching ObjectId against string - mongodb

I have the following document which is also available in the mongo playground at:
https://mongoplayground.net/p/zhcoi1BF0Ny
db={
MyCollectionOne: [
{
"firstId": "10",
"secondId": "123456789012345678901234"
},
{
"firstId": "11",
"secondId": "999999999999999999999999"
}
],
MyCollectionTwo: [
{
"_id": ObjectId("123456789012345678901234"),
"otherFieldOne": "Some Data",
"otherFieldTwo": [
{
someNumber: 7
}
]
},
{
"_id": ObjectId("999999999999999999999999"),
"otherFieldOne": "Some Other Data",
"otherFieldTwo": [
{
someNumber: 9
},
{
someNumber: 39
}
]
}
]
}
Given a firstId, I need to determine the correct MyCollectionTwo entry to return. So for example, if I was given a firstId of 11, I would use that to lookup its corresponding secondId (which is "999999999999999999999999"). I would need to convert the secondId value to an ObjectId and then look through the MyCollectionTwo _id fields until I find the matching one.
I gave it a try and am very close but cannot figure out how to correctly do the string->objectId conversion.
db.MyCollectionTwo.aggregate([
{
$lookup: {
from: "MyCollectionOne",
localField: "_id",
foreignField: "secondId",
as: "Temp"
}
},
{
$unwind: "$Temp"
},
{
$match: {
"Temp.firstId": "11"
}
},
{
$project: {
_id: 1,
otherFieldOne: 1,
otherFieldTwo: 1
}
}
]).find()
I am aware there is a let/pipeline which can use a $toObjectId but I can't get that working in the above context.
Any help would be appreciated. Thanks!

Your $lookup with pipeline should be as below:
$lookup: {
from: "MyCollectionOne",
let: {
id: "$_id"
},
pipeline: [
{
$match: {
$expr: {
$eq: [
{
$toObjectId: "$secondId"
},
"$$id"
]
}
}
}
],
as: "Temp"
}
Sample Mongo Playground

You can try this
db.MyCollectionTwo.aggregate([
{
$lookup: {
//searching collection name
from: "MyCollectionOne",
//setting variable [searchId] where your string converted to ObjectId
let: {
"searchId": {
$toObjectId: "$secondId"
}
},
//search query with our [searchId] value
"pipeline": [
//searching [searchId] value equals your field [_id]
{
"$match": {
"$expr": [
{
"_id": "$$searchId"
}
]
}
},
],
as: "Temp"
}
},
{
$unwind: "$Temp"
},
{
$match: {
"Temp.firstId": "11"
}
},
{
$project: {
_id: 1,
otherFieldOne: 1,
otherFieldTwo: 1
}
}
]).find()
https://mongoplayground.net/p/es0j0AiPDCj

Related

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
}

Mongo DB Join on Primary/Foreign Key

I have two collections, viz: clib and mp.
The schema for clib is : {name: String, type: Number} and that for mp is: {clibId: String}.
Sample Document for clib:
{_id: ObjectId("6178008397be0747443a2a92"), name: "c1", type: 1}
{_id: ObjectId("6178008397be0747443a2a91"), name: "c2", type: 0}
Sample Document for mp:
{clibId: "6178008397be0747443a2a92"}
{clibId:"6178008397be0747443a2a91"}
While Querying mp, I want those clibId's that have type = 0 in clib collection.
Any ideas how this can be achieved?
One approach that I can think of was to use $lookUp, but that doesnt seem to be working. Also, I m not sure if this is anti-pattern for mongodb, another approach is to copy the type from clib to mp while saving mp document.
If I've understood correctly you can use a pipeline like this:
This query get the values from clib where its _id is the same as clibId and also has type = 0. Also I've added a $match stage to not output values where there is not any coincidence.
db.mp.aggregate([
{
"$lookup": {
"from": "clib",
"let": {
"id": "$clibId"
},
"pipeline": [
{
"$match": {
"$expr": {
"$and": [
{
"$eq": [
{
"$toObjectId": "$$id"
},
"$_id"
]
},
{
"$eq": [
"$type",
0
]
}
]
}
}
}
],
"as": "result"
}
},
{
"$match": {
"result": {
"$ne": []
}
}
}
])
Example here
db.mp.aggregate([
{
$lookup: {
from: "clib",
let: {
clibId: "$clibId"
},
pipeline: [
{
$match: {
$expr: {
$and: [
{
$eq: [ "$_id", "$$clibId" ],
}
]
}
}
},
{
$project: { type: 1, _id: 0 }
}
],
as: "clib"
}
},
{
"$unwind": "$clib"
},
{
"$match": {
"clib.type": 0
}
}
])
Test Here

MongoDb return records only if $lookup cond is occur

I have a class model which has field ref.
I'm trying to fetch only records that match the condition in lookup.
so what i did:
{
$lookup: {
from: 'fields',
localField: "field",
foreignField: "_id",
as: 'FieldCollege',
},
},
{
$addFields: {
"FieldCollege": {
$arrayElemAt: [
{
$filter: {
input: "$FieldCollege",
as: "field",
cond: {
$eq: ["$$field.level", req.query.level]
}
}
}, 0
]
}
}
},
The above code works fine and returning the FieldCollege if the cond is matched.
but the thing is, i wanted to return the class records only if the FieldCollege is not empty.
I'm totally new to mongodb. so i tried something like this:
{
$match: {
'FieldCollege': { $exists: true, $ne: [] }
}
},
Obv this didn't work.
does mongodb support something like this or am i complicating things?
EDIT:
the result from the above code:
"Classes": [
{
"_id": "613245664c6ea614e001fcef",
"name": "test",
"language": "en",
"year_cost": "3232323",
"FieldCollege":[] // with $unwind
}
],
expected Result:
"Classes": [
// FieldCollege is empty
],
I think the good option is to use lookup with pipeline, and see the final version of your query,
$lookup with fields collection and match your both conditions
$limit to result one document
$match FieldCollege is not empty []
$addElemAt to get first element from result FieldCollege
[
{
$lookup: {
from: "fields",
let: { field: "$field" },
pipeline: [
{
$match: {
$and: [
{ $expr: { $eq: ["$$field", "$_id"] } },
{ level: req.query.level }
]
}
},
{ $limit: 1 }
],
as: "FieldCollege"
}
},
{ $match: { FieldCollege: { $ne: [] } } },
{
$addFields: {
FieldCollege: { $arrayElemAt: ["$FieldCollege", 0] }
}
}
]

How to convert to ObjectId and match dates on MongoDB lookup?

I am grabbing an id from a nested value in a schema, then using that to lookup the id from another table and also matching on a couple dates from that table. I've tried a regular match/lookup/unwind/match and also a lookup/let/pipeline technique. In both cases, it ignores matching on the date for some reason. What am I missing?
Here is one method for reference. I'm not sure where to put the sort either since it doesn't seem to pull $meeting out to sort on.
EXAMPLE RECORDS
PRODUCT
{
"_id" : ObjectId("5f36c0df6d5553e6af208cac"),
"items" : [
{
"paramType" : "Meeting",
"paramValue" : "5f36c0df6d5553e6af208cab"
}
],
"ownerId" : ObjectId("12345678901234567")
}
MEETING
{
"_id" : ObjectId("5f36c0df6d5553e6af208cab"),
"startDate" : ISODate("2020-08-18T10:00:00.000+0000"),
"endDate" : ISODate("2020-08-18T11:00:00.000+0000")
}
AGGREGATE
db.getCollection("products").aggregate(
[
{
$match: {
"ownerId": ObjectId("12345678901234567")
}
},
{
$unwind: "$items"
},
{
$lookup: {
from: "meetings",
let: { "meetingId": '$items.paramValue' },
pipeline: [
{
$match: {
$expr: {
$and: [
{ $eq: ["$_id", "$$meetingId"] },
{
$eq: ["meeting.startDate", {
"$gte": ["$meeting.startDate", ISODate("2020-08-01T00:00:00.000Z")]
}]
},
{
$eq: ["meeting.endDate", {
"$lte": ["$meeting.endDate", ISODate("2020-08-31T23:59:59.999Z")]
}]
}
],
},
},
},
],
as: "meeting"
}
},
{
$unwind: "$meeting"
},
{
$project: {
"_id": 1,
"items": 1,
"meeting": "$meeting"
}
},
{
$sort: {
'meeting.startDate': 1
}
},
]
);
It might be because item.paramValue is not converted to an ObjectId before the lookup. But can't figure out how to convert it inside an aggregate. I tried this, but no go
{
$addFields: {
"convertedMeetingId": { $toObjectId: "$items.paramValue" }
}}
let: { "meetingId": "$convertedMeetingId" }
There are quick fixes in $lookup other looks good,
let you can convert meetingId string to ObjectId here using $toObjectId
$gte and $lte, you used $meeting.startDate and $meeting.endDate it should be $startDate and $endDate because you are already inside meeting lookup.
i am not sure why you have used $eq and with $gte and $lte, if i am not wrong i have corrected and removed $eq it will work directly.
{
$lookup: {
from: "meetings",
let: { meetingId: { $toObjectId: "$items.paramValue" } },
pipeline: [
{
$match: {
$expr: {
$and: [
{ $eq: ["$_id", "$$meetingId"] },
{ $gte: ["$startDate", ISODate("2020-08-01T00:00:00.000Z")] },
{ $lte: ["$endDate", ISODate("2020-08-31T23:59:59.999Z")] }
]
}
}
}
],
as: "meeting"
}
},
Playground

Aggregate mongodb change objects to array

I am working with a mongodb aggregation and I would need to replace the part of my object array to an array concatenation. I understand that the part that I have to modify would be push but it is added as objects and I am not very clear how to concatenate that part
This is my current aggregation:
Datagreenhouse.aggregate([
{ "$match": { "id_sensor_station_absolute": { "$in": arr }, "recvTime": { $gte: fechainicio, $lte: fechafin } } }, //, "id_station": { "$in": id_station }
{
"$lookup": {
"from": "station_types",
"localField": "id_station", // local field in measurements collection
"foreignField": "id_station", //foreign field from sensors collection
"as": "sensor"
}
},
{ "$unwind": "$sensor" },
{
"$addFields": {
"sensor.attrValue": "$attrValue", // Add attrValue to the sensors
"sensor.recvTime": "$recvTime", // Add attrName to the sensors
"sensor.marca": "$sensor.marca",
"sensor.sensor_type": {
$filter: {
input: '$sensor.sensor_type',
as: 'shape',
cond: { $eq: ['$$shape.name', '$attrName'] },
}
}
}
},
{
"$group": {
"_id": {
"name_comun": "$sensor.sensor_type.name_comun",
"name_comun2": "$sensor.marca"
}, // Group by time
"data": {
"$push": {
"name": "$sensor.recvTime",
"value": "$sensor.attrValue",
},
},
}
},
{
"$project": {
"name": {
$reduce: {
input: [
["$_id.name_comun2"]
],
initialValue: "$_id.name_comun",
in: { $concatArrays: ["$$value", "$$this"] }
}
},
"_id": 0,
"data": 1
}
}
]
The current output of this aggregation is:
[
{
"data":[
{
"name":"2020-06-04T14:30:50.00Z",
"value":69.4
},
{
"name":"2020-06-04T14:13:31.00Z",
"value":68.9
}
],
"name":[
"Hum. Relativa",
"Hortisys"
]
},
{
"data":[
{
"name":"2020-06-04T14:30:50.00Z",
"value":25.5
},
{
"name":"2020-06-04T14:13:31.00Z",
"value":26.6
}
],
"name":[
"Temp. Ambiente",
"Hortisys"
]
}
]
I need to change the format of data for data: [[], [], []]
Example:
[
{
"data":[
["2020-06-04T14:30:50.00Z",69.4],
["2020-06-04T14:13:31.00Z",68.9],
"name":[
"Hum. Relativa",
"Hortisys"
]
},
{
"data":[
["2020-06-04T14:30:50.00Z",69.4],
["2020-06-04T14:13:31.00Z",68.9],
"name":[
"Temp. Ambiente",
"Hortisys"
]
}
]
The $push operator won't accept an array directly, but you can give it another operator that returns an array, like
data:{ $push:{ $concatArrays:[[ "$sensor.recvTime", "$sensor.attrValue" ]]}},