I have to fetch a record which have the name field that I give and a subjectId field of another collection for which I have the subjectname with me -- e.g., I would like to fetch a student record which have name lora and subjectname as physics (but the problem is I have subjectname with me but the collection student have the field id of that subject which is been already saved to some other collection say subjects --with _id ---subject _id act as foreign field in student record, I have student name and subject name with me, how can I access the student with corresponding fields?
You have two options on how to do this in Mongo, with option number 1 being the one I personally recommend as it's going to be more efficient.
Split this into two queries:
let subject = await subjectCollection.findOne({name: subjectname});
// add validation that subject exists in case needed.
let doc = await firstCollection.findOne({name: name, subjectId: subject._id})
// you can use `find` instead if multiple documents can be matched.
Use $lookup
firstCollection.aggregate([
{
$match: {
name: name
}
},
{
$lookup: {
from: "subject_collection",
let: {subjectName: subjectName},
pipeline: [
{
$match: {
$expr: {
$eq: ["$$subjectName", "$name"]
}
}
}
],
as: "subjects"
}
},
{
$match: {
"subjects.0": {$exists: true}
}
}
])
Related
I'm trying to return all documents which satisfy document.field.value > document.array.length. I'm trying to do this using MongoClient in ExpressJS and was not able to find answers on SO.
Here is what I have tried but this gives an empty list.
const db = ref()
const c = db.collection(t)
const docs = await c.find({
"stop.time":{$gt: new Date().toISOString()},
"stop.expect": {$gt: { $size: "stop.riders"}}
})
console.log(docs)
Also tried replacing
"stop.expect": {$gt: { $size: "stop.riders"}}
with the below code which does not compile
$expr: {$lt: [{$size: "stop.riders"}, "stop.expect"]}
Sample data:
{ "stop": { "expect":3, "riders": ["asd", "erw", "wer"] } },
{ "stop": { "expect":4, "riders": ["asq", "frw", "wbr"] } }
To filter the query with a complex query involving the calculation, you need to use the $expr operator, which allows the aggregation operators.
Next, within the $expr operator, to refer to the field, you need to add the prefix $.
db.collection.find({
$expr: {
$lt: [
{
$size: "$stop.riders"
},
"$stop.expect"
]
}
})
Demo # Mongo Playground
To compare fields within a document to each other, you must use $expr. It would look like this:
const docs = await c.find({
$expr: { $gt: ["$stop.expect", { $size: "$stop.riders" }] },
});
I omitted the stop.time condition because it's not in your sample data and it's unclear what type of field it is, if it actually is in your data (whether it's a Date or String would be very important).
Note that this sort of query is unable to use an index.
So I'm using NodeJS to query MongoDB (4.4). I'm trying to figure how to search for a field inside an object inside a document and retrieve the object (or at least the _id). The field I'm querying by is the created field within the transactions document. How the table looks like is bellow.
I tried:
const result = await Model.findOne({}, { created: createdDate });
Didn't work. Never worked with these kinds of DB and am a bit lost. Any help is appreciated.
Maybe something like this:
Option 1: ( Find )
db.collection.find({
"transactions.created": "2022-12-21"
},
{
transactions: {
$elemMatch: {
created: "2022-12-21"
}
}
})
Explained:
Find the document based on "transaction.created".
Project only the matching "transaction.created" object.
playground1
Option 2: (Aggregation)
db.collection.aggregate([
{
$match: {
"transactions.created": "2022-12-21"
}
},
{
$addFields: {
transactions: {
"$filter": {
"input": "$transactions",
"as": "t",
"cond": {
$eq: [
"$$t.created",
"2022-12-21"
]
}
}
}
}
}
])
Explained:
Find the document based on transaction.created
Filter only the matched transaction created object.
playground2
For best performance index need to be created on the "transaction.created" field.
I have 2 collections:
Office -
{
_id: ObjectId(someOfficeId),
name: "some name",
..other fields
}
Documents -
{
_id: ObjectId(SomeId),
name: "Some document name",
officeId: ObjectId(someOfficeId),
...etc
}
I need to get list of offices sorted by count of documetns that refer to office. Also should be realized pagination.
I tryied to do this by aggregation and using $lookup
const aggregation = [
{
$lookup: {
from: 'documents',
let: {
id: '$id'
},
pipeline: [
{
$match: {
$expr: {
$eq: ['$officeId', '$id']
},
// sent_at: {
// $gte: start,
// $lt: end,
// },
}
}
],
as: 'documents'
},
},
{ $sortByCount: "$documents" },
{ $skip: (page - 1) * limit },
{ $limit: limit },
];
But this doesn't work for me
Any Ideas how to realize this?
p.s. I need to show offices with 0 documents, so get offices by documets - doesn't work for me
Query
you can use lookup to join on that field, and pipeline to group so you count the documents of each office (instead of putting the documents into an array, because you only case for the count)
$set is to get that count at top level field
sort using the noffices field
you can use the skip/limit way for pagination, but if your collection is very big it will be slow see this. Alternative you can do the pagination using the _id natural order, or retrieve more document in each query and have them in memory (instead of retriving just 1 page's documents)
Test code here
offices.aggregate(
[{"$lookup":
{"from":"documents",
"localField":"_id",
"foreignField":"officeId",
"pipeline":[{"$group":{"_id":null, "count":{"$sum":1}}}],
"as":"noffices"}},
{"$set":
{"noffices":
{"$cond":
[{"$eq":["$noffices", []]}, 0,
{"$arrayElemAt":["$noffices.count", 0]}]}}},
{"$sort":{"noffices":-1}}])
As the other answer pointed out you forgot the _ of id, but you don't need the let or match inside the pipeline with $expr, with the above lookup. Also $sortByCount doesn't count the member of an array, you would need $size (sort by count is just group and count its not for arrays). But you dont need $size also you can count them in the pipeline, like above.
Edit
Query
you can add in the pipeline what you need or just remove it
this keeps all documents, and counts the array size
and then sorts
Test code here
offices.aggregate(
[{"$lookup":
{"from":"documents",
"localField":"_id",
"foreignField":"officeId",
"pipeline":[],
"as":"alldocuments"}},
{"$set":{"ndocuments":{"$size":"$alldocuments"}}},
{"$sort":{"ndocuments":-1}}])
There are two errors in your lookup
While passing the variable in with $let. You forgot the _ of the $_id local field
let: {
id: '$id'
},
In the $exp, since you are using a variable id and not a field of the
Documents collection, you should use $$ to make reference to the variable.
$expr: {
$eq: ['$officeId', '$$id']
},
I am building a chat app and I am stuck at a query. I have chats stored inside an array of objects. I want to fetch chats matching the key of object value. I am using mongoose library to query mongodb. Please do help me in solving this.
My chat schema is:
chats: [
{
sentFrom: 'user1',
sentTo: 'athlete1',
message: 'hi'
},
{
sentFrom : 'user2',
sentTo: 'athlete1',
message: 'hi athleyte'
}
]
I want to find all the chats where sentFrom value is 'user1'.
This is my code and it's not working.
From your document ,
you are doing match on whole collection and your condition is chats.sentFrom should be equal to user1 that's why,
If any other document has similar kind of data in that case it will return 2 documents in order to filter out the array you will have to use $filter using aggregation
db.test.aggregate([
{ $match: {_id: ObjectId("5f87fec2e80b410a20c6819a")}},
{ $project: {
chats: {$filter: {
input: '$chats',
as: 'chat',
cond: {$eq: ['$$chat.sentFrom', "user1"]}
}}
}}
])
Try filter:
let result = chats.filter(chat => chat.sentFrom === 'user1');
We have two models:
User
const userSchema = new mongoose.Schema({
name: {
type: String
},
email: {
type: String
},
vehicleId: {
type: mongoose.Schema.Types.ObjectId,
ref: 'vehicle'
}
})
Vehicle
const vehicleSchema = new mongoose.Schema({
name: {
type: String
},
color: {
type: String
}
})
Now, we have to find user input from user's ( name, email ) & also from vehicle's ( name, color )
- If user search "pink" then we have to find that if any user has with name, email or them vehicle include "pink" keyword or not.
- If string match from reference model then we have to return whole record.
Can you please help us, how to achieve this?
You can first run a query on a Vehicle model to fetch all vehicles that have name pink
let vehicleIds=await Model.Vehicle.aggregate([
{$match:{$or:[
{name:{$regex:"pink",$options:"i"}},
{color:{$regex:"pink",$options:"i"}
]}},
{$group:{_id:null,ids:{$push:"$_id"}}}
])
here if vehicleIds is not empty, then vehicles[0].ids will have vehcileIds that have pink in name or color
let vehcileIds=[]
if(vehicles.length) vehcileIds=vehicles[0].ids
after above query runs run another query in the user model
let users= await Models.User.find({$or:[
{name:{$regex:"pink",$options:"i"}},
{email:{$regex:"pink",$options:"i"}
{vehicleId:{$in:}}
]})
I think, the best thing you can do is, Text Search.
ANSWER 1:
1.1. Step one
You have to create text index on your collection to perform text search.
create text index with
db.user.createIndex({"name":"text", "email": "text"})
db.vehicle.createIndex({"name": "text", "color": "text"})
we have to create text index on both collection as we want to perform text search on both collections.
*
Note:
as we are creating a text index on "name", "email" from User & "name", "color" from Vehicle, you can only perform text search on this four fields in respective collections.
you can assign weights to fields so that you can sort your result according to text score. (you must do this while creating index).
*
1.2. Step two
(i am assuming you are using Javascript and mongoose here.)
db.collection("users").aggregate({$match:{$text: {$search: "pink"}}},
(err, userData: any)=>{
if(err){
return err;
}
if(userdata.length){
let vehicleIds = userData.map((user) => mongoose.Types.ObjectId(user.vehicleId));
db.collection("vehicles").aggregate({$match:{$text:{$search:"pink", _id: {$in: vehicleIds}}}}, (err, vehicleData)=>{
//this will be your vehicle details
})
})
}else{
console.log("no Data found");
}
*
The problem with this approach is you have to fire two queries because of the restrictions in Text Search and extra overhead of text index on collections.
Good Thing is you get a lot of ways to perform a search, you can sort results according to relevance with text score, it is fast than the regular expression and you get better results, you get stemming, you get stop words (please ref links that are given).
*
ANSWER 2:
you can use regular expression
db.collection("users").aggregate({$match:{$or:[{name:{$regex: /pink/}}, {email:{$regex: /pink/}}]}},
{$lookup:{from:"vehicles", localField:"vehicleId", foreignFild:"_id", as:"vehicleDetail"}},
{$unwind:{path:"$vehicleDetail"}},
{$match:{$or:[{name:{$regex:/pink/}},{color:{$regex:/pink/}}]}}
(err, data)=>{
})
ANSWER 3:
If you dont want to prefer above options, you can fire normal query too
db.collection("users").aggregate({$match:{$or:[{name:{$regex: /pink/}}, {email:{$regex: /pink/}}]}},
{$lookup:{from:"vehicles", localField:"vehicleId", foreignFild:"_id", as:"vehicleDetail"}},
{$unwind:{path:"$vehicleDetail"}},
{$match:{$or:[{name:{$regex:/pink/}},{color:{$regex:/pink/}}]}}
(err, data)=>{
})
Hope this helps. :-)
Correct me if I am wrong anywhere. :-)
Below steps you should follow:
Find user by name and email.
Populate vehicle
Again match with the color.
db.getCollection('User').aggregate([
{ $match: { email: "h#gmail.com", name: "Hardik"}},
{ $lookup: {from: 'Vehicle', localField: 'vehicleId', foreignField: '_id', as: 'vehicle'} },
{ $match: { "vehicle.color": {$regex: '.*'+ "Pink" +'.*', $options: 'si'}}},
])
Data:
User
{
"_id" : ObjectId("5d51bd5ef5fc3d6486b40ffb"),
"email" : "h#gmail.com",
"name" : "Hardik",
"vehicleId" : ObjectId("5d539786f5fc3d6486b4252b")
}
Vehicle
{
"_id" : ObjectId("5d539786f5fc3d6486b4252b"),
"name" : "Bike",
"color" : "Pink"
}
Output:
{
"_id" : ObjectId("5d51bd5ef5fc3d6486b40ffb"),
"email" : "h#gmail.com",
"name" : "Hardik",
"vehicleId" : ObjectId("5d539786f5fc3d6486b4252b"),
"vehicle" : [
{
"_id" : ObjectId("5d539786f5fc3d6486b4252b"),
"name" : "Bike",
"color" : "Pink"
}
]
}
Hope this helps!