being a newbie to mongo, stuck in a conditional query:
I want to perform a search on the basis of 3 criteria, first name, last name and email id:
below query works perfect when all the fields exist:
db.students.find( { $and: [ { first_name: /^Abc$/i }, { last_name: 'Xyz'},{email_id:'gd#he'} ]})
the problem is when I don't give an email id , the query dosen't returns any result as it considers the email id to be null and searches for the combination 'Abc Firtis null',
where as I want the below scenario to be fulfilled:
I have a collection of students:
- FirstName: 1. ABC 2. ABC 3.ABC
- LastName: 1.XYZ 2. XY 3. XZ
- EmailID: 1.abc#xyz 2.Ab#xy 3.Ab#xz
if one enters only the first name in the search it should return all the 3 results
if user enters first name and last name it should return first two results and if the user enters all three details it should return only 1 result.
Any leads would be highly appreciated.
You seem to be talking about "input" data being different for the queries you want to issue and how to contruct the query to ignore fields as criteria for which you have no input.
This is all really about how the input is being collected as to how you handle it, but it all boils down to that you "conditionally build" the query ( which is just a data structure anyway ) rather than statically define a query and somehow ignore null or empty data.
So if you have seperate variables, then you test each value and build the query:
var firstName = "abc",
lastName = "xy,
email = null,
query = {};
if (firstName) {
query.firstName = new RegExp("^"+firstName,"i")
}
if (lastName) {
query.lastName = new RegExp("^"+lastName,"i")
}
if (email) {
query.email = new RegExp("^"+email,"i")
}
db.students.find(query)
This would build a query object that would end up like this based on the inputs:
{ "firstName": /^abc/i, "lastName": /^xy/i }
So since there is no value in email then the condition is not included. The end result is the condition not provided is not even queried for and then you get the relevant matches.
The same approach is basically simplified if you have some structured input to begin with:
var params = {
firstName = "abc",
lastName = "xy"
};
var query = {};
Object.keys(params).forEach(function(key) {
if (params[key])
query[key] = new RegExp("^"+key,"i");
});
db.students.find(query);
And it's the same thing, but since you have all parameter keys in one place then you can iterate them to build the query rather than test individually.
This is generally the case where you have input from something like a web request with parameters that come into req.params or even req.body depending on your method of input. So if you structure your code to accept input into a similar object ( or already have it ) then you use it to build your query.
Also note that all MongoDB query arguments are implicitly an "AND" condition by definition, so there is rarely any need to use $and unless you explicitly have multiple conditions to meet for the same document property. Even then there are generally better syntax alternates.
No Need to give and you can simply try this
find( { first_name: { $regex: '/^Abc$/i' }, last_name:'Xyz',email_id:'gd#he'}
Related
I have Book objects in my mongoDb that look like this (very simplified):
{
"_id": ObjectId("620e341dbf575892d9396dc2"),
"title": "my title",
"author": {
"_id": ObjectId("620e341dbf575892d9396dd4"),
"surname": "My surname"
}
}
I'm trying to query and get all books by an author, and so far I've tried:
const booksByAuthor = await Book.find({ author: { _id: "620e341dbf575892d9396dd4" } });
and
const booksByAuthor = await Book.find({ 'author._id': "620e341dbf575892d9396dd4" } );
But in no case I'm getting the existing books from the given author, and I'm only getting empty arrays.
What am I doing wrong?
The problem for 1st query:
When you want to query in a nested field you have to use dot notation as you do in the second query.
The problem to not use dot notation is mongo match the entire object (even the fields order) so is not a good way... you can check simple examples like:
This one: The object is found because the object is exactly the same.
Another this one where the object has the same fields and values from two objects in the collection but... querying using an object the order matters, so only returns one.
The problem for 2nd query:
I think the problem here is you are matching string vs ObjectId. The syntaxis is correct (dot notation) but when mondo match the values one of them is string and another is ObjectId.
So you can try this query:
const booksByAuthor = await Book.find({ 'author._id': mongoose.Types.ObjectId("620e341dbf575892d9396dd4") } );
Check this example with your query, matching string and ObjectId none result is returned, but parsing string to ObjectId... magic appears
I am in the process of learning MongoDB and here is the scenario.
I am trying to find user with id. I wrote the code as
Stmt 1
query = {};
query["_id"] = { $ne: ObjectId(data._id) }
It returns me the user with ID. (I have only one test user as of now. So i am not so clear it returns all the user or only user matching the id). But the logic i made is id should not be equal to.
I changed the code like
Stmt 2
query = {};
query = { "_id": { $ne: ObjectId(data._id) } };
Now this is giving me empty array as there are no users matching the id.
What difference does it make in the query to mongodb and why the query failed.
Although I think it is a general question, I could not find a solution that matches my needs.
I have 2 Mongo collections. The 'users' collection and the second one 'dbInfos'.
Now, I have a template called 'Infos' and want the already existing fields in the Mongo collections to be presented to the user in input fields in case there is data in the collection. When no data is provided in the database yet, it should be empty.
So here is my code, which works fine until I want to capture the fields from the second collection.
Template.Infos.onRendered(function() {
$('#txtName').val(Meteor.user().profile.name);
$('#txtEmail').val(Meteor.user().emails[0].address);
});
These 2 work great.
But I don´t know how to query the infos from the collection 'dbInfos', which is not the 'users' collection. Obviously Meteor.user().country does not work, because it is not in the 'users' collection. Maybe a find({}) query? However, I don´t know how to write it.
$('#txtCountry').val( ***query function***);
Regarding the structure of 'dbInfos': Every object has an _id which is equal to the userId plus more fields like country, city etc...
{
"_id": "12345",
"country": "countryX",
"city": "cityY"
}
Additionally, how can I guarantee that nothing is presented, when the field in the collection is empty? Or is this automatic, because it will just return an empty field?
Edit
I now tried this:
dbInfos.find({},{'country': 1, '_id': 0})
I think this is the correct syntax to retrieve the country field and suppress the output of the _id field. But I only get [object Object] as a return.
you're missing the idea of a foreign key. each item in a collection needs a unique key, assigned by mongo (usually). so the key of your country info being the same as the userId is not correct, but you're close. instead, you can reference the userId like this:
{
"_id": "abc123",
"userId": "12345",
"country": "countryX",
"city": "cityY"
}
here, "abc123" is unique to that collection and assigned by mongo, and "12345" is the _id of some record in Meteor.users.
so you can find it like this (this would be on the client, and you would have already subscribed to DBInfos collection):
let userId = Meteor.userId();
let matchingInfos = DBInfos.find({userId: userId});
the first userId is the name of the field in the collection, the second is the local variable that came from the logged in user.
update:
ok, i think i see where you're getting tripped it. there's a difference between find() and findOne().
find() returns a cursor, and that might be where you're getting your [object object]. findOne() returns an actual object.
for both, the first argument is a filter, and the second argument is an options field. e.g.
let cursor = DBInfos.find({
userId: Meteor.userId()
},
{
fields: {
country: 1
}
});
this is going to:
find all records that belong to the logged in user
make only the country and _id fields available
make that data available in the form of a cursor
the cursor allows you to iterate over the results, but it is not a JSON object of your results. a cursor is handy if you want to use "{{#each}}" in the HTML, for example.
if you simply change the find() to a findOne():
let result = DBInfos.findOne({ /** and the rest **/
... now you actually have a JSON result object.
you can also do a combination of find/fetch, which works like a findOne():
let result = DBInfos.find({
userId: Meteor.userId()
},
{
fields: {
country: 1
}
}).fetch();
with that result, you can now get country:
let country = result.country;
btw, you don't need to use the options to get country. i've been assuming all this code is on the client (might be a bad assumption). so this will work to get the country as well:
let result = DBInfos.findOne({userId: Meteor.userId()});
let country = result.country;
what's going on here? it's just like above, but the result JSON might have more fields in it than just country and _id. (it depends on what was published).
i'll typically use the options field when doing a find() on the server, to limit what's being published to the client. on the client, if you just need to grab the country field, you don't really need to specify the options in that way.
in that options, you can also do things like sort the results. that can be handy on the client when you're going to iterate on a cursor and you want the results displayed in a certain order.
does all that make sense? is that what was tripping you up?
I have a question, lets say I have a collection called contact :
[
{"firstName": "Adam", "lastName":"Peter", "email":"adam#peter.com"},
{"firstName": "Adam", "lastName":"John", "email":"adam#john.com"},
{"firstName": "Adam", "lastName":"Petkovic", "email":"adam#petkovic.com"}
]
What I want is to search specifically, for example: I want to search "Adam peter" then I want to have a result of the first one ONLY which has Adam and peter.
I use meteor + mongo + react for my application.
Any suggestion / recommendation would be high appreciated.
Thanks for all the answers, but probably I need to ask more specific in order to get more appropriate answer.
Scenarios:
I only have 1 text box to search all the fields.
So:
when I enter "Adam", I expect to have 3 results. but when I enter "Adam Peter" I expect to have 1 result only.
When I enter "peter.com" it should have 1 result
When I enter "John", it should have 1 result
When I enter "Adam Pet" it should have 2 results.
From the answer here, below query should work fine.
db.contacts.find( { firstName: /^Adam$/i, lastName: /^peter$/i });
The query in MongoDB is case sensitive, if you want to query contact by ignoring case, you should use a regular expression, but it may not efficient.
db.contact.findOne({firstName: /^adam$/i, lastName: /^peter$/i})
it will much better if you always save these name value in lowercase, and query in lowercase
db.contact.findOne({firstName: 'adam', lastName: 'peter'})
Assuming that the rules that you are applying are:
If a single word, then that could match any field
two words mean "firstname surname"
In that case, you can't use text indices, but instead need to do some work before the mongo search.
First, split the words on whitespace, and then determine if there are one or two words. If there is one word, check that against all fields. If there are two, then only check the first word against the first name, and the second against the lastname.
// assuming input is in variable call 'term'
var words = term.trim().split(/\s+/) || [];
if(words.length === 0) {
return;
}
var first = new RegExp(words[0], 'i');
if(words.length === 2) {
var second = new RegExp(words[1], 'i');
return Contact.find({firstName: first, lastName: second});
else if(words.length === 1) {
return Contact.find({$or: [ {firstName: first}, {lastName: first}, {email: first}]})
}
Also, depending on how large your collection is, it might be better to wrap this up into a Meteor method, so that the search takes place on the server. Otherwise, you will have to publish the whole collection on the client to be able to do the search. That might be fine for a small collection though.
UPDATE:
Based on your examples, I think your rules are:
1. Search terms are combined with AND operator (e.g. Adam Pet returns two rows, not three).
2. Search terms use regular expression matching (Pet matches even though it's not any of the words).
Rule 2 means that text indices won't work, so you will need to build up a complex regex query document using $and and $or for each item:
// assuming input is in variable call 'term'
var words = term.trim().split(/\s+/) || [];
var query = {
"$and": []
};
words.forEach(function(token) {
var reg = new RegExp(token);
var innerQ = {"$or": [ {firstName: reg}, {lastName: reg}, {email: reg}]};
query["$and"].push(innerQ);
});
return Contact.find(query);
I have 2 different collections and I am trying to query the first collection and take the output of that as an input to the second collection.
var mycursor = db.item.find({"itemId":NumberLong(123)},{"_id":0})
var outp = "";
while(mycursor.hasNext()){
var rec = mycursor.next()
outp = outp + rec.eventId;
}
This query works fine and returns me a list of eventIds.
I have another collection named users, which has eventId field in it. A eventId can repeat in multiple users. So for each eventId I get in the above query I want to get the list of users too.
My query for the second collection would be something like this :
db.users.find({"eventId":ObjectdId("each eventId from above query")},{"_id":0})
My final result would be a unique list of users.
Whell this should basically work ( to a point that is ):
db.events.find({
"eventId": { "$in": db.item.find({
"itemId":NumberLong(123)
}).map(function(doc) { return doc.eventId }) }
})
Or even a bit better:
db.events.find({
"eventId": { "$in": db.item.distinct("eventId",{
"itemId":NumberLong(123) }) }
})
The reason is that "inner query" is evaluated before the outer query is sent. So you get an array of arguments for use with $in.
That is basically the same as doing the following, which translates better outside of the shell:
var events = db.item.distinct("eventId",{ "itemId":NumberLong(123) });
db.events.find({ "eventId": { "$in": events } })
If the results are "too large" however, then your best approach is to loop the initial results as you have done already and build up an array of arguments. Once at a certain size then do the same $in query several times in "pages" to get the results.
But looking for "ditinct" eventId via .distinct() or .aggregate() will help.