Query array by stored index in document in MongoDB - mongodb

I have a document like this
{
"status": {
"current": 0,
"priority": [{
"operationName": "PHOTO",
"status": "WAITING"
},
{
"operationName": "DESIGN",
"status": "NOTARRIVED"
},
{
"operationName": "COLOR_SEPARATION",
"status": "NOTARRIVED"
}]
}
}
and want to query on data like this
{
"status.priority.$status.current.operationName": {
$in: ['SERVICES', 'PHOTO']
}
}
when I query like this
{
"status.priority.0.operationName": {
$in: ['SERVICES', 'PHOTO']
}
}
it returns the data needed as the 'PHOTO' is the current operation.
I need to query based on an index of the array and this index is stored in the document in status.current
any hint?
UPDATE
After question solved I want to optimize it.

You can use $arrayElemAt with $expr in 3.6.
Something like
db.colname.find(
{"$expr":{
"$in":[
{"$arrayElemAt":["$status.priority.operationName","$status.current"]},
['DESIGN', 'COLOR_SEPARATION', 'PHOTO']
]
}}
)

For this you need to use aggregation
db.collname.aggregate([{
"$project": {
"_id": 1,
priority: { $arrayElemAt: ["$status.priority", '$status.current'] },
}
},
{
$match: {
"priority.operationName": {
$in: ['DESIGN', 'COLOR_SEPARATION', 'PHOTO']
}
}
}
])
This will work for you.
Result will be like
{
"_id" : ObjectId("5b6b656818883ec018d1542d"),
"priority" : {
"operationName" : "PHOTO",
"status" : "WAITING"
}
}

Related

MongoDB Aggregate Query to find the documents with missing values

I am having a huge collection of objects where the data is stored for different employees.
{
"employee": "Joe",
"areAllAttributesMatched": false,
"characteristics": [
{
"step": "A",
"name": "house",
"score": "1"
},
{
"step": "B",
"name": "car"
},
{
"step": "C",
"name": "job",
"score": "3"
}
]
}
There are cases where the score for an object is completely missing and I want to find out all these details from the database.
In order to do this, I have written the following query, but seems I am going wrong somewhere due to which it is not displaying the output.
I want the data in the following format for this query, so that it is easy to find out which employee is missing the score for which step and which name.
db.collection.aggregate([
{
"$unwind": "$characteristics"
},
{
"$match": {
"characteristics.score": {
"$exists": false
}
}
},
{
"$project": {
"employee": 1,
"name": "$characteristics.name",
"step": "$characteristics.step",
_id: 0
}
}
])
You need to use $exists to check the existence
playground
You can use $ifNull to handle both cases of 1. the score field is missing 2. score is null.
db.collection.aggregate([
{
"$unwind": "$characteristics"
},
{
"$match": {
$expr: {
$eq: [
{
"$ifNull": [
"$characteristics.score",
null
]
},
null
]
}
}
},
{
"$group": {
_id: null,
documents: {
$push: {
"employee": "$employee",
"name": "$characteristics.name",
"step": "$characteristics.step",
}
}
}
},
{
$project: {
_id: false
}
}
])
Here is the Mongo playground for your reference.

Querying a multi-nested array in MongoDb 3.4.2

MongoDB Version - 3.4.2
I'm trying to query within the Sitecore Analytics database, trying to retrieve all users that are associated with a given List Id.
The example dataset I have follows the default Sitecore Analytics setup:
"Tags" : {
"Entries" : {
"ContactLists" : {
"Values" : {
"0" : {
"Value" : "{1E2D1AB7-72A0-4FF7-906B-DCDC020B87D2}",
"DateTime" : ISODate("2020-10-23T17:38:13.891Z")
},
"1" : {
"Value" : "{28BECCD3-476B-4B1D-9A75-02E59EF21286}",
"DateTime" : ISODate("2018-04-18T14:22:41.763Z")
},
"2" : {
"Value" : "{2C2BB0C3-483D-490E-B93A-9155BFBBE5DC}",
"DateTime" : ISODate("2018-05-10T14:26:08.494Z")
},
"3" : {
"Value" : "{DBE480F6-E305-4B35-9E6D-CBED64F4E44F}",
"DateTime" : ISODate("2018-10-27T02:41:28.776Z")
},
}
}
}
},
I want to iterate through all the entries within Values without having to specify 0/1/2/3, avoiding the following:
db.getCollection('Contacts').find({"Tags.Entries.ContactLists.Values.1.Value": "{28BECCD3-476B-4B1D-9A75-02E59EF21286}"})
I've tried the following:
db.getCollection('Contacts').find({"Tags.Entries.ContactLists.Values": {$elemMatch : {"Value":"{28BECCD3-476B-4B1D-9A75-02E59EF21286}"}}})
db.getCollection('Contacts').find({'Tags' : {$elemMatch : {$all : ['{28BECCD3-476B-4B1D-9A75-02E59EF21286}']}}})
db.getCollection('Contacts').forEach(function (doc) {
for(var i in doc.Tags.Entries.ContactLists.Values)
{
doc.Tags.Entries.ContactLists.Values[i].Value = "{28BECCD3-476B-4B1D-9A75-02E59EF21286}";
}
})
And a few other variations which I cannot recall now. And none work.
Any ideas if this is possible or on how to do this?
I want the outcome to just show filter out the results showing only the entries containing the matching GUID
Many thanks!
Demo - https://mongoplayground.net/p/upgYxgzPwJQ
It can be done using aggregation pipeline
Use $objectToArray to convert array
Use $filter to filter the array
db.collection.aggregate([
{
$addFields: {
filteredValue: {
$filter: {
input: {
$objectToArray: "$Tags.Entries.ContactLists.Values"
},
as: "val",
cond: {
$eq: [ // filter condition
"$$val.v.Value",
"{28BECCD3-476B-4B1D-9A75-02E59EF21286}"
]
}
}
}
}
}
])
Output -
[
{
"Tags": {
"Entries": {
"ContactLists": {
"Values": {
"0": {
"DateTime": ISODate("2020-10-23T17:38:13.891Z"),
"Value": "{1E2D1AB7-72A0-4FF7-906B-DCDC020B87D2}"
},
"1": {
"DateTime": ISODate("2018-04-18T14:22:41.763Z"),
"Value": "{28BECCD3-476B-4B1D-9A75-02E59EF21286}"
},
"2": {
"DateTime": ISODate("2018-05-10T14:26:08.494Z"),
"Value": "{2C2BB0C3-483D-490E-B93A-9155BFBBE5DC}"
},
"3": {
"DateTime": ISODate("2018-10-27T02:41:28.776Z"),
"Value": "{DBE480F6-E305-4B35-9E6D-CBED64F4E44F}"
}
}
}
}
},
"_id": ObjectId("5a934e000102030405000000"),
"filteredValue": [
{
"k": "1",
"v": {
"DateTime": ISODate("2018-04-18T14:22:41.763Z"),
"Value": "{28BECCD3-476B-4B1D-9A75-02E59EF21286}"
}
}
]
}
]
You can not use $elemMatch because Values is not array, but object. You can solve the problem with Aggregation Pipeline:
$addFields to add new field Values_Array that will be array representation of Values object.
$objectToArray to transform Values object to array
$match to find all documents that has requested value in new Values_Array field
$project to specify which properties to return from the result
db.getCollection('Contacts').aggregate([
{
"$addFields": {
"Values_Array": {
"$objectToArray": "$Tags.Entries.ContactLists.Values"
}
}
},
{
"$match": {
"Values_Array.v.Value": "{28BECCD3-476B-4B1D-9A75-02E59EF21286}"
}
},
{
"$project": {
"Tags": 1
}
}
])
Here is the working example: https://mongoplayground.net/p/2gY-vu3Qrvz

How to project an addFields attribute in MongoDB

I have a document in my collection testdb that I want to match to and looks like:
{
"student": {
"first": "Joe",
"last": "Johnson"
},
"semester": [{
"semesterName": "Spring2021",
"courses": [{
"title": "Calculus 1",
"professor": "Erik Paulson",
"TA": "Paul Matthews"
},
{
"title": "Computer Science 1",
"professor": "Dennis Ritchie",
"TA": "Ken Thompson"
}
]
}]
}
I want to match on the title attribute in the courses array and return the professor attribute without all of its nesting.
So I have the following query:
db.testcol.aggregate([
{ $match: { "semester.courses.title" : "Calculus 1" } },
{ $project: { "professor" : 1, "_id" : 0 } },
{ $addFields: { "professor": "$semester.courses.professor" } },
]);
but I seem to be getting an output of just { } when I want an output of { "professor" : "Erik Paulson" }.
Can somebody explain why this is happening and how I can fix it? My logic is that I am using $addFields to set the new professor attribute to be the professor attribute inside of the array of course objects whenever there is a match to the desired course title. I am then using $project to return only the new attribute.
Explanation:
Mistake is in your $project stage. property professor is two level inside the document which must be referenced as semester.courses.professor. Accessing it as professor will result in empty value.
So you can fix it using below query. Try this:
db.testcol.aggregate([
{ $unwind: "$semester" },
{ $unwind: "$semester.courses" },
{ $match: { "semester.courses.title" : "Calculus 1" } },
{
$project: {
"_id": 0,
"professor": "$semester.courses.professor"
}
}
]);
Output:
{
"professor" : "Erik Paulson"
}

multiple conditions in $match in mongodb

I have a collection AccountSupport. I has array of supports property. I want to filter the record on parent property and a property of an array
db = {
"AccountSupport": [
{
"_id" : ObjectId("5e9c6170b38c373530c5b00a"),
"accountName" : "domestic",
"supports" : [
{
"subject" : "Traverse",
"desc" : "Travers support consolidation",
},
{
"subject" : "Non Traverse",
"desc" : "Non Travers support consolidation",
},
{
"subject" : "Domestic Traverse",
"desc" : "Domestic Travers support consolidation",
}
],
}
I want to filter on accountName and supports.subject.
Below is my query
db.AccountSupport.aggregate([
{
"$match": {
"$and": [
{
"supports.subject": "Traverse"
},
{
"accountName": "domestic"
}
]
}
},
{
"$unwind": "$supports"
},
{
"$project": {
"SupportName": "$supports.subject",
"desc": "$supports.desc"
}
}
])
The above query returns me all the supports of a particular accountName whereas i only want the single object of the matched subject. Is above the simplest approach to do it?
MongoPlayGround
MongoPlayGround for my query
Took a little bit of experimenting but try this:
db.AccountSupport.aggregate([
{
$match: {
"accountName": "domestic",
"supports.subject": "Traverse"
}
},
{
$project: {
accountName: "$accountName",
subject: {
$filter: {
input: "$supports",
as: "supports",
cond: {
$eq: [
"$$supports.subject",
"Traverse"
]
}
}
},
_id: 0
}
}
])
Which returns:
[
{
"accountName": "domestic",
"subject": [
{
"desc": "Travers support consolidation",
"subject": "Traverse"
}
]
}
]
One option is to just add another $match step to your aggregation query. Since the $unwind breaks your doc into 3 docs, you can then match the individual doc you are interested in returning. The $filter solution by james looks worth investigating and could be the simpler approach for you.
Query:
db.AccountSupport.aggregate([
{
"$match": {
"$and": [
{
"supports.subject": "Traverse"
},
{
"accountName": "domestic"
}
]
}
},
{
"$unwind": "$supports"
},
{
"$match": {
"supports.subject": "Traverse"
}
},
{
"$project": {
"SupportName": "$supports.subject",
"desc": "$supports.desc"
}
}
])
Result:
[
{
"SupportName": "Traverse",
"_id": ObjectId("5e9c6170b38c373530c5b00a"),
"desc": "Travers support consolidation"
}
]
MongoPlayground
Below is the query to get my needed result, not sure if this is the best practice though, as the other answers posted works fine as well for the mentioned requirement
db.AccountSupport.find({'accountName': 'domestic' },
{
'supports':
{
'$elemMatch': { 'subject': 'Traverse'}
}
})

Distinct array element with condition

My documents look like this:
{
"_id": "1",
"tags": [
{ "code": "01-01", "type": "machine" },
{ "code": "04-06", "type": "gearbox" },
{ "code": "07-01", "type": "machine" }
]
},
{
"_id": "2",
"tags": [
{ "code": "03-04","type": "gearbox" },
{ "code": "01-01", "type": "machine" },
{ "code": "04-11", "type": "machine" }
]
}
I want to get distinct codes only for tags whose type is "machine". so, for the example above, the result should be ["01-01", "07-01", "04-11"].
How do I do this?
Using $unwind and then $group with the tag as the key will give you each tag in a separate document in your result set:
db.collection_name.aggregate([
{
$unwind: "$tags"
},
{
$match: {
"tags.type": "machine"
}
},
{
$group: {
_id: "$tags.code"
}
},
{
$project:{
_id:false
code: "$_id"
}
}
]);
Or, if you want them put into an array within a single document, you can use $push within a second $group stage:
db.collection_name.aggregate([
{
$unwind: "$tags"
},
{
$match: {
"tags.type": "machine"
}
},
{
$group: {
_id: "$tags.code"
}
},
{
$group:{
_id: null,
codes: {$push: "$_id"}
}
}
]);
Another user suggested including an initial stage of { $match: { "tags.type": "machine" } }. This is a good idea if your data is likely to contain a significant number of documents that do not include "machine" tags. That way you will eliminate unnecessary processing of those documents. Your pipeline would look like this:
db.collection_name.aggregate([
{
$match: {
"tags.type": "machine"
}
},
{
$unwind: "$tags"
},
{
$match: {
"tags.type": "machine"
}
},
{
$group: {
_id: "$tags.code"
}
},
{
$group:{
_id: null,
codes: {$push: "$_id"}
}
}
]);
> db.foo.aggregate( [
... { $unwind : "$tags" },
... { $match : { "tags.type" : "machine" } },
... { $group : { "_id" : "$tags.code" } },
... { $group : { _id : null , "codes" : {$push : "$_id"} }}
... ] )
{ "_id" : null, "codes" : [ "04-11", "07-01", "01-01" ] }
A better way would be to group directly on tags.type and use addToSet on tags.code.
Here's how we can achieve the same output in 3 stages of aggregation :
db.name.aggregate([
{$unwind:"$tags"},
{$match:{"tags.type":"machine"}},
{$group:{_id:"$tags.type","codes":{$addToSet:"$tags.code"}}}
])
Output : { "_id" : "machine", "codes" : [ "04-11", "07-01", "01-01" ] }
Also, if you wish to filter out tag.type codes, we just need to replace "machine" in match stage with desired tag.type.