MongoDB - multiple queries based on condition - mongodb

I have a query that looks something like this.
employees.aggregate(
[{ "$match":
{"$and": [
{"$or": [
{name : { "$regex": param, "$options":"i"}},
{title : { "$regex": param, "$options":"i"}},
]},
{ tenure : true }
]}
},
{"$sort":{experience : -1}},
{"$limit" : 100}
])
I would like to update this query to something like this.
search the employees collection where name = param and tenure = true
if data exists the sort the results by experience and limit the results to 100
if no results found then search the same collection using title and no need to sort the results.
Can someone please help with this?

You need to apply a conditional sorting in query. And another important thing is the value of $regex must have in string(" "). You can't pass like
$regex: User
you need to pass it in string.
db.hotspot.aggregate(
[{
"$match": {
"$and": [{
"$or": [{
name: {
"$regex": "SD",
"$options": "i"
}
},
{
radius: {
"$regex": "250",
"$options": "i"
}
},
]
},
{
infinite: false
}
]
}
},
{
$project: {
sort: {
$cond: {
if: {
$eq: ["$radius", 250]
},
then: "$name",
else: "$_id"
}
}
}
},
{
$sort: {
sort: 1
}
}
])

Related

MongoDB select best matched document

I have a collection of documents like this:
[{
"_id" : ObjectId("6347e5aa0c009a37b81da700"),
"testField1" : "1000",
"testField2" : "2000",
"testField3" : NumberInt(1)
},
{
"_id" : ObjectId("6347e5890c009a37b81da701"),
"testField2" : 2000,
"testField3" : NumberInt(2)
},
{
"_id" : ObjectId("6347e5960c009a37b81da702"),
"testField3" : NumberInt(3)
}]
I need to retrieve documents in the below precedence.
if testField1 and testField2 exist and match their values, the query should return that document.
Otherwise, if testField2 exists and matches its value, the query should return that document,
Otherwise it should return the last document, where testField1 & testField2 do not exist.
I tried the below query, but it returns all the documents.
db.getCollection("TEST_COLLECTION").aggregate([
{
$match: {
$expr: {
$cond: {
if: {
$and: {"testField1": "1000", "testField2": "2000"}
},
then: {
$and: {"testField1": "1000", "testField2": "2000"}
},
else : {
$cond: {
if: {
$and: {"testField1": null, "testField2": "2000"}
},
then: {
$and: {"testField1": null, "testField2": "2000"}
},
else : {
$and: {"testField1": null, "testField2": null}
}
}
}
}
}
}
}
])
There are definitely still some open questions from the comments. #ray has an interesting approach linked in there that uses $setWindowFields which may be appropriate depending on exactly what you're looking for.
I took a different approach (and perhaps interpretation) and built out the following aggregation that uses $unionWith:
db.collection.aggregate([
{
$match: {
testField1: "1000",
testField2: "2000"
}
},
{
"$addFields": {
sortOrder: 1
}
},
{
"$unionWith": {
"coll": "collection",
"pipeline": [
{
$match: {
testField2: "2000"
}
},
{
"$addFields": {
sortOrder: 2
}
}
]
}
},
{
"$unionWith": {
"coll": "collection",
"pipeline": [
{
$match: {
testField1: {
$exists: false
},
testField2: {
$exists: false
}
}
},
{
"$addFields": {
sortOrder: 3
}
},
]
}
},
{
$sort: {
sortOrder: 1
}
},
{
$limit: 1
},
{
"$unset": "sortOrder"
}
])
Basically the aggregation will internally issue three queries, one corresponding with each of three precedence conditions. Similar to #ray's solution, it creates a field to sort on (sortOrder in mine) since the ordering of $unionWith is unspecified otherwise per the documentation. After the $sort we can $limit to a single result and $unset the temporary sorting field prior to returning the result to the client. Depending on the version you are running, you could consider adding a couple of inline $limits for each of the subpipelines to reduce the amount of work being done. Along with appropriate indexes (perhaps just { testField2: 1, testField: 1 }), this operation should be reasonably efficient.
Here is the playground link.
If there are several groups and you need to return the wanted document per group, I would go with #ray's answer. If there is only one group (as implies on your comment, and on #user20042973's nice answer), I would like to point another obvious option:
db.collection.aggregate([
{$facet: {
op1: [{$match: {testField1: "1000", testField2: "2000"}}],
op2: [{$match: {testField1: null, testField2: "2000"}}],
op3: [{$match: {testField1: null, testField2: null}},
{$sort: {timestamp: -1}}, {$limit: 1}]
}},
{$project: {res: {$ifNull: [{$first: "$op1"}, {$first: "$op2"}, {$first: "$op3"}]}}},
{$replaceRoot: {newRoot: "$res"}}
])
See how it works on the playground example

PyMongo - Select object from an array having max value

A document in my DB looks like this :
{
"_id": ObjectId("5e92e63fad262707ff301d6c"),
"uknum": 30,
"area": "bath",
"ukelectors": 62355,
"ukresults": [
{
"party": "con",
"leader": "thatcher",
"ukvotes": 22544
},
{
"party": "lab",
"leader": "foot",
"ukvotes": 7259
},
{
"party": "sdp",
"leader": "jenkins",
"ukvotes": 17240
},
{
"party": "eco",
"leader": "whittaker",
"ukvotes": 441
}
]
}
Requirement :
I need to build a query in python to get the name of the party which won area: bath. Basically, check who got maximum votes and choose that party.
The idea was to use $max aggregation pipeline but it does not seem to work.
You can do that using either one of the aggregation-pipeline query :
Query 1 : Without use of $unwind, by using $reduce on array :
db.collection.aggregate([
{ $match: { area: "bath" } },
{
$addFields: {
ukresults: {
$let: {
vars: {
res: {
$reduce: {
input: "$ukresults",
initialValue: { votes: 0, party: {} },
in: {
votes: {
$cond: [
{ $gt: ["$$this.ukvotes", "$$value.votes"] },
"$$this.ukvotes",
"$$value.votes",
],
},
party: {
$cond: [
{ $gt: ["$$this.ukvotes", "$$value.votes"] },
"$$this",
"$$value.party",
],
},
},
},
},
},
in: "$$res.party",
},
},
},
},
]);
Test : MongoDB-Playground
Query 2 : With use of $unwind :
db.collection.aggregate([
{
$match: {
area: "bath"
}
},
{
$unwind: {
path: "$ukresults",
preserveNullAndEmptyArrays: true
}
},
{
$sort: {
"ukresults.ukvotes": -1
}
},
{
$limit: 1
}
])
Test : MongoDB-Playground
I would say these two might perform well as we're doing on mostly one document (Cause we've $match as first stage), maybe first query might take a wile to iterate if you've more elements in array, give it a try & choose one which helps most.
Ref : Check this pymongo documentation for aggregation examples : pymongo-aggregation.
The following aggregation query prints the ukresults sub-document with maximum votes.
The aggregation operator $max cannot be applied directly on the ukresults array, as the array has sub-documents rather than scalar values, like numbers. So, we use $reduce aggregation operator to extract the sub-document with maximum votes. Note that using the $reduce operator is a reduction operation on an array, so is using $max - the difference is the array element data type.
The PyMongo code:
import pymongo
import pprint
client = pymongo.MongoClient()
collection = client.test.testCollection
pipeline = [
{
"$match": { "area": "bath" }
},
{
"$addFields": {
"maxvotes": {
"$reduce": {
"input": "$ukresults",
"initialValue": { "ukvotes": 0 },
"in": {
"$cond": [
{ "$gt": [ "$$this.ukvotes", "$$value.ukvotes"] },
"$$this",
"$$value"
]
}
}
}
}
},
{
"$project": {
"_id": 0,
"area": 1,
"maxvotes": 1
}
}
]
pprint.pprint(list(collection.aggregate(pipeline)))
The output:
[{'area': 'bath',
'maxvotes': {'leader': 'thatcher', 'party': 'con', 'ukvotes': 22544.0}}]

How to use $elemMatch query in mongodb

I tried to filter the data using $elemmatch but it's not correctly working.
My Scenario
Prod Table
[
{
id:1.
product:[
{
id:1,
name:true
},
{
id:2,
name:true
},
{
id:3,
name:false
}
]
}
]
Query
db.Prod.find(
{"product.name": true},
{_id: 0, product: {$elemMatch: {name: true}}});
I got Output
[
{
id:1.
product:[
{
id:1,
name:true
}
]
}
]
Excepted Output
[
{
id:1.
product:[
{
id:1,
name:true
},
{
id:2,
name:true
}
]
}
]
How to achieve this Scenario and I referred this Link Retrieve only the queried element in an object array in MongoDB collection and I tried all the answers in this link mentioned. but Still it's not working can give an example query.
Hmm, you probably didn't try the aggregation provided in referenced link, 'cause it perfectly works.
db.collection.aggregate([
{
$match: {
"product.name": true
}
},
{
$project: {
product: {
$filter: {
input: "$product",
as: "prod",
cond: {
$eq: [
"$$prod.name",
true
]
}
}
}
}
}
])
Will output :
[
{
"_id": 1,
"product": [
{
"id": 1,
"name": true
},
{
"id": 2,
"name": true
}
]
}
]
Here's the example.
EDIT :
From the doc :
Usage Considerations
Both the $ operator and the $elemMatch operator project the first
matching element from an array based on a condition.
Considering this, you cannot achieve what you need with a find query, but only with an aggregation query.

Get Distinct list of two properties using MongoDB 2.4

I have an article collection:
{
_id: 9999,
authorId: 12345,
coAuthors: [23456,34567],
title: 'My Article'
},
{
_id: 10000,
authorId: 78910,
title: 'My Second Article'
}
I'm trying to figure out how to get a list of distinct author and co-author ids out of the database. I have tried push, concat, and addToSet, but can't seem to find the right combination. I'm on 2.4.6 so I don't have access to setUnion.
Whilst $setUnion would be the "ideal" way to do this, there is another way that basically involved "switching" between a "type" to alternate which field is picked:
db.collection.aggregate([
{ "$project": {
"authorId": 1,
"coAuthors": { "$ifNull": [ "$coAuthors", [null] ] },
"type": { "$const": [ true,false ] }
}},
{ "$unwind": "$coAuthors" },
{ "$unwind": "$type" },
{ "$group": {
"_id": {
"$cond": [
"$type",
"$authorId",
"$coAuthors"
]
}
}},
{ "$match": { "_id": { "$ne": null } } }
])
And that is it. You may know the $const operation as the $literal operator from MongoDB 2.6. It has always been there, but was only documented and given an "alias" at the 2.6 release.
Of course the $unwind operations in both cases produce more "copies" of the data, but this is grouping for "distinct" values so it does not matter. Just depending on the true/false alternating value for the projected "type" field ( once unwound ) you just pick the field alternately.
Also this little mapReduce does much the same thing:
db.collection.mapReduce(
function() {
emit(this.authorId,null);
if ( this.hasOwnProperty("coAuthors"))
this.coAuthors.forEach(function(id) {
emit(id,null);
});
},
function(key,values) {
return null;
},
{ "out": { "inline": 1 } }
)
For the record, $setUnion is of course a lot cleaner and more performant:
db.collection.aggregate([
{ "$project": {
"combined": {
"$setUnion": [
{ "$map": {
"input": ["A"],
"as": "el",
"in": "$authorId"
}},
{ "$ifNull": [ "$coAuthors", [] ] }
]
}
}},
{ "$unwind": "$combined" },
{ "$group": {
"_id": "$combined"
}}
])
So there the only real concerns are converting the singular "authorId" to an array via $map and feeding an empty array where the "coAuthors" field is not present in the document.
Both output the same distinct values from the sample documents:
{ "_id" : 78910 }
{ "_id" : 23456 }
{ "_id" : 34567 }
{ "_id" : 12345 }

select documents with sub arrays that match some critieria

I have a collections with documents such as:
{
_id: "1234",
_class: "com.acme.classA",
a_collection: [
{
otherdata: 'somedata',
type: 'a'
},
{
otherdata: 'bar',
type: 'a'
},
{
otherdata: 'foo',
type: 'b'
}
],
lastChange: ISODate("2014-08-17T22:25:48.918Z")
}
I want to find all document by id and a subset of the sub array. for example I want to find all documents with id "1234" and a_collection.type is 'a' giving this result:
{
_id: "1234",
_class: "com.acme.classA",
a_collection: [
{
otherdata: 'somedata',
type: 'a'
},
{
otherdata: 'bar',
type: 'a'
}
],
lastChange: ISODate("2014-08-17T22:25:48.918Z")
}
I have tried this :
db.collection_name.aggregate({
$match: {
'a_collection.type': 'a'
}
},
{
$unwind: "$a_collection"
},
{
$match: {
"a_collection.type": 'a'
}
},
{
$group: {
_id: "$_id",
a_collection: {
$addToSet: "$a_collection"
},
}
}).pretty()
but this doesnt return other properties ( such as 'lastChange' )
what is the correct way to do this ?
Are you using PHP?
And is this the only way you can get the "text"?
maybe you can rewrite it that it is like an JSON element.
something like that:
{
"_id": "1234",
"_class": "com.acme.classA",
"a_collection": [
{
"otherdata": "somedata",
"type": "a"
},
{
"otherdata": "bar",
"type": "a"
},
{
"otherdata": "foo",
"type": "b"
}
]
}
Then you can use the json_decode() function from PHP to make an array and then you can search and return only the needed data.
Edit: I read read false. do you search for a funktion like this?
db.inventory.find( {
$or: [ { _id: "1234" }, { 'a_collection.type': 'a' }]
} )
[Here][1] I found the code ;) [1]: http://docs.mongodb.org/manual/tutorial/query-documents/
this is the correct query:
db.collection_name.aggregate({
$match: {
'a_collection.type': 'a'
}
},
{
$unwind: "$a_collection"
},
{
$match: {
"a_collection.type": 'a'
}
},
{
$group: {
_id: "$_id",
a_collection: {
$addToSet: "$a_collection"
},
lastChange : { $first : "$lastChange" }
}
}).pretty()
Something is very strange about your desired query (and your pipelines). First of all, _id is a reserved field with a unique index on it. The result of finding all documents with _id = "1234" can only be 0 or 1 documents. Second, to find documents with a_collection.type = "a" for some element of the array a_collection, you don't need the aggregation framework. You just need a find query:
> db.test.find({ "a_collection.type" : "a" })
So all the work here appears to be winnowing the subarray of one document down to just those elements with a_collection.type = "a". Why do you have these objects in the same document if most of what you do is split them up and eliminate some to find a result set? How common and how truly necessary is it to harvest just the array elements with a_collection.type = "a"? Perhaps you want to model your data differently so a query like
> db.test.find({ <some condition>, "a_collection.type" : "a" })
returns you the correct documents. I can't say how you can do it best with the given information, but I can say that your current approach strongly suggests revision is needed (and I'm happy to help with suggestions if you include further information or post a new question).
I would agree with the answer you have submitted yourself, but for that in MongoDB 2.6 and greater there is a better way to do this with $map and $setDifference. Which wer both introduced at that version. But where available, this is much faster in the approach:
db.collection.aggregate([
{ "$match": { "a_collection.type": "a" } },
{ "$project": {
"$setDifference": [
{ "$map": [
"input": "$a_collection",
"as": "el",
"in": {
"$cond": [
{ "$eq": [ "$$el.type", "a" ] },
"$$el",
false
]
}
]},
[false]
]
}}
])
So that has no "group" or initial "unwind" which both can be costly options, along with the $match stage. So MongoDB 2.6 does it better.