How to combine including and excluding functionality in MongoDB? - mongodb

Part of my collection is as follows:
# Order1
{
"countries": [
{
"code":"us"
}
],
"cities": {
"includes":["a", "b"],
"excludes":[]
}
}
# Order2
{
"countries": [
{
"code":"all"
}
],
"cities": {
"includes":["all"],
"excludes":["x"]
}
}
# Order3
{
"countries": [
{
"code":"all"
}
],
"cities": {
"includes":["all"],
"excludes":[]
}
}
How do I query to get both including and excluding orders correctly?
Example 1:
URL parameters: country=us&city=a
Expected result: Order1, Order2, Order3
Example 2:
URL parameters: country=us&city=x
Expected result: Order3
Example 3:
URL parameters: country=de&city=z
Expected result: Order2, Order3
One of things I've tried is similar to this:
$and: [{
$or: [{
'countries.code': 'all'
}, {
'countries.code': URL_PARAMETER_FOR_COUNTRY
}]
}, {
$and: [{
$or: [{
'cities.includes': 'all'
}, {
'cities.includes': URL_PARAMETER_FOR_CITY
}]
},
{
'cities.excludes': {
$ne: URL_PARAMETER_FOR_CITY
}
}]
}]

I think you were basically on the right track but you're getting confused with the "nesting" of logic and the usage of operators like $and. All MongoDB query arguments are already an "AND* condition, so there is generally no need to specify this.
Also when you have more than one condition for an $or on exactly the same field, it's a lot cleaner to write this with $in instead:
db.collection.find({
"countries.code": { "$in": ["all",COUNTRY_PARAM] },
"cities.includes": { "$in": ["all",CITY_PARAM] },
"cities.excludes": { "$ne": CITY_PARAM }
})
Even if you wanted to roll in "mutliple" tests for the "excludes" such as an "all" value, then just apply the "list non-equivalent" statement which is $nin:
db.collection.find({
"countries.code": { "$in": ["all",COUNTRY_PARAM] },
"cities.includes": { "$in": ["all",CITY_PARAM] },
"cities.excludes": { "$nin": ["all",CITY_PARAM }
})
That meets all described combinations of returning the matching documents.
The reason query arguments are an "AND" condition without specifying it is because generally that is what people mean, hence it makes the expression much more simplified than the terse representation, and far less confusing.
AND with AND means "AND" on all conditions, so no need to partition.

Related

after aggregation how to check two fields are equal inside a document in mongodb

{
id: 1,
name: "sree",
userId: "001",
paymentData: {
user_Id: "001",
amount: 200
}
},
{
id: 1,
name: "sree",
userId: "001",
paymentData: {
user_Id: "002",
amount: 200
}
}
I got this result after unwind in aggregation any way to check user_Id equal to userId
Are you looking to only retrieve the results when they are equal (meaning you want to filter out documents where the values are not the same) or are you looking to add a field indicating whether the two are equal?
In either case, you append subsequent stage(s) to the aggregation pipeline to achieve your desired result. If you want to filter the documents, the new stage may be:
{
$match: {
$expr: {
$eq: [
"$userId",
"$paymentData.user_Id"
]
}
}
}
See how it works in this playground example.
If instead you want to add a field that compares the two values, then this stage may be what you are looking for:
{
$addFields: {
isEqual: {
$eq: [
"$userId",
"$paymentData.user_Id"
]
}
}
}
See how it works in this playground example.
You could also combine the two as in:
{
$addFields: {
isEqual: {
$eq: [
"$userId",
"$paymentData.user_Id"
]
}
}
},
{
$match: {
isEqual: true
}
}
Playground demonstration here

conditional 'from' in mongodb lookup

I'm trying to lookup in multiple collections, based on a specific field.
for example if field type equals to 1, lookup the collection from Admin and if type equals to 2, lookup from Client.I know the following query is incorrect, but i just want to show what i mean.
db.User.aggregate([
{
"$lookup":{
"localField":"ID",
"from":{"$cond": { if: { "type":1 } ,then: "Admin", else: "Client"} },
"foreignField":"ID",
"as":"newUser"
},
{
"$unwind":"$newUser"
}
}])
Any help will be appreciated.
Bad news, you cant, the only solution is to use $facet and have 2 separated pipelines.
As you probably imagine this is not a great solution as it wastes resources on the redundant pipeline.
I'm not sure if you can involve some code but if you can it is your best option.
$facet pipeline draft:
db.User.aggregate([
{
$facet: {
user: [
{
"$lookup":{
"localField":"ID",
"from":Client,
"foreignField":"ID",
"as":"newUser"
},
},
{
"$unwind":"$newUser"
}],
admin: [
{
"$lookup":{
"localField":"ID",
"from":Admin,
"foreignField":"ID",
"as":"newUser"
},
},
{
"$unwind":"$newUser"
}],
}
},
{
$match: {
use "correct" user here..
}
}
])

Mongodb aggregate match query with priority on full match

I am attempting to do a mongodb regex query on a field. I'd like the query to prioritize a full match if it finds one and then partials afterwards.
For instance if I have a database full of the following entries.
{
"username": "patrick"
},
{
"username": "robert"
},
{
"username": "patrice"
},
{
"username": "pat"
},
{
"username": "patter"
},
{
"username": "john_patrick"
}
And I query for the username 'pat' I'd like to get back the results with the direct match first, followed by the partials. So the results would be ordered ['pat', 'patrick', 'patrice', 'patter', 'john_patrick'].
Is it possible to do this with a mongo query alone? If so could someone point me towards a resource detailing how to accomplish it?
Here is the query that I am attempting to use to perform this.
db.accounts.aggregate({ $match :
{
$or : [
{ "usernameLowercase" : "pat" },
{ "usernameLowercase" : { $regex : "pat" } }
]
} })
Given your precise example, this could be accomplished in the following way - if your real world scenario is a little bit more complex you may hit problems, though:
db.accounts.aggregate([{
$match: {
"username": /pat/i // find all documents that somehow match "pat" in a case-insensitive fashion
}
}, {
$addFields: {
"exact": {
$eq: [ "$username", "pat" ] // add a field that indicates if a document matches exactly
},
"startswith": {
$eq: [ { $substr: [ "$username", 0, 3 ] }, "pat" ] // add a field that indicates if a document matches at the start
}
}
}, {
$sort: {
"exact": -1, // sort by our primary temporary field
"startswith": -1 // sort by our seconday temporary
}
}, {
$project: {
"exact": 0, // get rid of the "exact" field,
"startswith": 0 // same for "startswith"
}
}])
Another way would be using $facet which may prove a bit more powerful by enabling more complex scenarios but slower (several people here will hate me, though, for this proposal):
db.accounts.aggregate([{
$facet: { // run two pipelines against all documents
"exact": [{ // this one will capture all exact matches
$match: {
"username": "pat"
}
}],
"others": [{ // this one will capture all others
$match: {
"username": { $ne: "pat", $regex: /pat/i }
}
}]
}
}, {
$project: {
"result": { // merge the two arrays
$concatArrays: [ "$exact", "$others" ]
}
}
}, {
$unwind: "$result" // flatten the resulting array into separate documents
}, {
$replaceRoot: { // restore the original document structure
"newRoot": "$result"
}
}])

How to find records in a bson object

var otherLanguages=[ "English","Arabic","French"];
var first, second;
db.collection.find({ $and: [ { "Language" : { $nin : otherLanguages} },{"Language":{ $ne:null}} ]}).forEach(function(obj){
shell out 341 docs one by one. In these docs,I want to find out documents that satisfy two if statements. Later, I want to collect the count it.
if (obj.find({ $and: [{'POS': { $eq: "Past" } },{'Desp': { $ne: null } }] })) { first= first+1;}
if (obj.find({ $and: [{'POS': { $eq: "Past" } },{'Desp': { $eq: null } }] })) {second= second+1;}
});
print (first,second)
I know that I cannot use find() function on the obj, but Is there a way to search on this "bson obj" to find the count.
If this is not feasible, then please suggest a way to get the desired result.
If I understand your question correctly you can achieve that by using the aggregation framework like so:
db.collection.aggregate({
// filter out all documents that you don't care about
$match: {
"Language": { $nin: otherLanguages, $ne: null },
"POS": "Past"
},
}, {
// then split into groups...
$group: {
_id: { $eq: [ "$Desp", null ] }, // ...one for the "eq: null" and one for the "ne: null"
"count": { $sum: 1 } // ...and count the number of documents in each group
}
})

How to concatenate all values and find specific substring in Mongodb?

I have json document like this:
{
"A": [
{
"C": "abc",
"D": "de"
},
{
"C": "fg",
"D": "hi"
}
]
}
I would check whether "A" contains string ef or not.
first Concatenate all values abcdefghi then search for ef
In XML, XPATH it would be something like:
//A[contains(., 'ef')]
Is there any similar query in Mongodb?
All options are pretty horrible for this type of search, but there are a few approaches you can take. Please note though that the end case here is likely the best solution, but I present the options in order to illustrate the problem.
If your keys in the array "A" are consistently defined and always contained an array, you would be searching like this:
db.collection.aggregate([
// Filter the documents containing your parts
{ "$match": {
"$and": [
{ "$or": [
{ "A.C": /e/ },
{ "A.D": /e/ }
]},
{"$or": [
{ "A.C": /f/ },
{ "A.D": /f/ }
]}
]
}},
// Keep the original form and a copy of the array
{ "$project": {
"_id": {
"_id": "$_id",
"A": "$A"
},
"A": 1
}},
// Unwind the array
{ "$unwind": "$A" },
// Join the two fields and push to a single array
{ "$group": {
"_id": "$_id",
"joined": { "$push": {
"$concat": [ "$A.C", "$A.D" ]
}}
}},
// Copy the array
{ "$project": {
"C": "$joined",
"D": "$joined"
}},
// Unwind both arrays
{ "$unwind": "$C" },
{ "$unwind": "$D" },
// Join the copies and test if they are the same
{ "$project": {
"joined": { "$concat": [ "$C", "$D" ] },
"same": { "$eq": [ "$C", "$D" ] },
}},
// Discard the "same" elements and search for the required string
{ "$match": {
"same": false,
"joined": { "$regex": "ef" }
}},
// Project the origial form of the matching documents
{ "$project": {
"_id": "$_id._id",
"A": "$_id.A"
}}
])
So apart from the horrible $regex matching there are a few hoops to go through in order to get the fields "joined" in order to again search for the string in sequence. Also note the reverse joining that is possible here that could possibly produce a false positive. Currently there would be no simple way to avoid that reverse join or otherwise filter it, so there is that to consider.
Another approach is to basically run everything through arbitrary JavaScript. The mapReduce method can be your vehicle for this. Here you can be a bit looser with the types of data that can be contained in "A" and try to tie in some more conditional matching to attempt to reduce the set of documents you are working on:
db.collection.mapReduce(
function () {
var joined = "";
if ( Object.prototype.toString.call( this.A ) === '[object Array]' ) {
this.A.forEach(function(doc) {
for ( var k in doc ) {
joined += doc[k];
}
});
} else {
joined = this.A; // presuming this is just a string
}
var id = this._id;
delete this["_id"];
if ( joined.match(/ef/) )
emit( id, this );
},
function(){}, // will not reduce
{
"query": {
"$or": [
{ "A": /ef/ },
{ "$and": [
{ "$or": [
{ "A.C": /e/ },
{ "A.D": /e/ }
]},
{"$or": [
{ "A.C": /f/ },
{ "A.D": /f/ }
]}
] }
]
},
"out": { "inline": 1 }
}
);
So you can use that with whatever arbitrary logic to search the contained objects. This one just differentiates between "arrays" and presumes otherwise a string, allowing the additional part of the query to just search for the matching "string" element first, and which is a "short circuit" evaluation.
But really at the end of the day, the best approach is to simply have the data present in your document, and you would have to maintain this yourself as you update the document contents:
{
"A": [
{
"C": "abc",
"D": "de"
},
{
"C": "fg",
"D": "hi"
}
],
"search": "abcdefghi"
}
So that is still going to invoke a horrible usage of $regex type queries but at least this avoids ( or rather shifts to writing the document ) the overhead of "joining" the elements in order to effect the search for your desired string.
Where this eventually leads is that a "full blown" text search solution, and that means an external one at this time as opposed to the text search facilities in MongoDB, is probably going to be your best performance option.
Either using the "pre-stored" approach in creating your "joined" field or otherwise where supported ( Solr is one solution that can do this ) have a "computed field" in this text index that is created when indexing document content.
At any rate, those are the approaches and the general point of the problem. This is not XPath searching, not is their some "XPath like" view of an entire collection in this sense, so you are best suited to structuring your data towards the methods that are going to give you the best performance.
With all of that said, your sample here is a fairly contrived example, and if you had an actual use case for something "like" this, then that actual case may make a very interesting question indeed. Actual cases generally have different solutions than the contrived ones. But now you have something to consider.