mongoose query or aggregation to remove certain subdocument fields - mongodb

Say I have the following document
Item
{
details: [
{
"title": "Name",
"content": "",
"is_private": false,
"order": 0,
"type": "text"
},
{
"title": "Price",
"content": "",
"is_private": true,
"order": 1,
"type": "text"
},
{
"title": "Company",
"content": "",
"is_private": false,
"order": 2,
"type": "text"
}
],
}
If I wanted to return only the subdocument fields details which have is_private === false, is there a way to do it in mongoose's query or do I need to use aggregation?
e.g.
ItemModel
.find()
// something should go here to remove

In case you wanted to do it using native aggregation query:
db.getCollection('item').aggregate([
{
$unwind:"$details"
},
{
$match:{
"details.is_private":true
}
},
{
$group:{
_id:"$_id",
details:{
$push:"$details"
}
}
}
])
Output:
{
"_id" : ObjectId("5b7d0edb6cfaf771ecf675f0"),
"details" : [
{
"title" : "Price",
"content" : "",
"is_private" : true,
"order" : 1.0,
"type" : "text"
}
]
}

Related

How to add new field and copy the value from existing field if its not null in mongo using a query

I want to add new fields to all the documents in a mongo collection based on the values of other fields only if they are not null. I have written a script to do this, but is there a single query with which we can accomplish this?
sample data:
[{
"outdoor": {
"location": {
"type": "Point",
"coordinates": [
-92.41151,
35.11683
]
}
},
"address": { "street1" : "Street 1",
"postalCode" : "95050",
"state" : "CA",
"locality" : "City 1",
"countryCode" : "US"},
"customerId":"8047380094"
},
{
"outdoor": {
"location": {
"type": "Point",
"coordinates": [
-89.58342,
36.859161
]
}
},
"customerId":"8047380094"
},
{
"address": {
"street1" : "Street 1",
"postalCode" : "95050",
"state" : "CA",
"locality" : "City 1",
"countryCode" : "US"
},
"customerId":"8047380094"
}]
Need to add below new fields:
new_address - copy address data into this field if its not null
new_location - copy outdoor.location into this field if its not null
Appreciate any help.
You can make use of the Mongo Aggregation Pipeline support for update commands available starting from MongoDB version 4.2
db.collection.updateMany({
"address": {
"$ne": null
}
},
[
{
"$set": {
"new_address": "$address"
},
}
])
db.collection.updateMany({
"outdoor.location": {
"$ne": null
}
},
[
{
"$set": {
"new_location": "$outdoor.location"
},
}
])

Aggregate and grouping in Mongo

How do I get a summary count in Mongo. I have the following record structure and I would like to get a summary by date and status
{
"_id": "1",
"History": [
{
"id": "11",
"message": "",
"status": "send",
"resultCount": 0,
"createdDate": "",
"modifiedDate": ""
},
{
"id": "21",
"message": "",
"status": "skipped",
"resultCount": 0,
"createdDate": "",
"modifiedDate": ""
}
]
}
This is what I would like..
date x
status :
count : nn
This is my Mongo structure
Let's assume you have the following data in 'history' collection:
{
"_id": "1",
"History": [
{
"id": "21",
"message": "",
"status": "send",
"resultCount": 0,
"createdDate": "date1",
"modifiedDate": ""
},
{
"id": "22",
"message": "",
"status": "skipped",
"resultCount": 0,
"createdDate": "date1",
"modifiedDate": ""
},
{
"id": "23",
"message": "",
"status": "skipped",
"resultCount": 0,
"createdDate": "date2",
"modifiedDate": ""
},
{
"id": "24",
"message": "",
"status": "skipped",
"resultCount": 0,
"createdDate": "date2",
"modifiedDate": ""
}
]
}
You can design your query in the following way to get the desired summary.
db.history.aggregate([
{
$unwind:"$History"
},
{
$group:{
"_id":{
"createdDate":"$History.createdDate",
"status":"$History.status"
},
"createdDate":{
$first: "$History.createdDate"
},
"status":{
$first:"$History.status"
},
"count":{
$sum:1
}
}
},
{
$group:{
"_id":"$createdDate",
"createdDate":{
$first:"$createdDate"
},
"info":{
$push:{
"status":"$status",
"count":"$count"
}
}
}
},
{
$project:{
"_id":0
}
}
]).pretty()
It would result in the following:
{
"createdDate" : "date1",
"info" : [
{
"status" : "skipped",
"count" : 1
},
{
"status" : "send",
"count" : 1
}
]
}
{
"createdDate" : "date2",
"info" : [
{
"status" : "skipped",
"count" : 2
}
]
}
Aggregation stages details:
Stage I: The 'History' array is unwinded i.e. the array would be split and each element would create an individual document.
Stage II: The data is grouped on the basis of 'createdDate' and 'status'. In this stage, the count of status is also calculated.
Stage III: The data is further grouped on the basis of 'createdDate'
only
Stage IV: Eliminating non-required fields from the result

How to count a specific element in a collection in mongodb

I know you can count how many elements are in a collection with
collection.find().count().
However, I was wondering how can I count a certain element inside the item inside the collection. For example I have four images inside a Documents like this:
"Documents":{
"1":"image.png",
"2":"Test.jpg",
"3":"Next.png"
}
I was wondering how I can count all the items in Documents? I have tried a few things but none of them are working. Can anyone help?
Example data:
{
"name": "Oran",
"username": "Oran.Hammes",
"avatar": "https://s3.amazonaws.com/uifaces/faces/twitter/brandonflatsoda/128.jpg",
"email": "xxxxxx#hotmail.com",
"dob": "1953-03-21T17:40:17.020Z",
"phone": "364-846-1607",
"address": {
"street": "Schultz Stream",
"suite": "Suite 618",
"city": "North Muriel mouth",
"zipcode": "06447-1081",
"geo": {
"lat": "57.1844",
"lng": "-56.8890"
}
},
"website": "misty.net",
"company": {
"name": "Hettinger, Reilly and Stracke",
"catchPhrase": "Multi-tiered system-worthy database",
"bs": "best-of-breed evolve e-markets"
},
"Documents": [
{
"id": "1",
"name": "image.png",
},
{
"id": "2",
"name": "Test.jpg",
},
{
"id": "3",
"name": "Next.png"
}
]
}
After this comment I changed my document's structure and it looks like this:
{
"_id" : ObjectId("568703d08981f193cf343698"),
"name" : "Oran",
"username" : "Oran.Hammes",
"email" : "xxxxxxx#hotmail.com",
"dob" : "1953-03-21T17:40:17.020Z",
"phone" : "364-846-1607",
"company" : {
"name" : "Hettinger, Reilly and Stracke",
"catchPhrase" : "Multi-tiered system-worthy database",
"bs" : "best-of-breed evolve e-markets"
},
"Documents" : [
{
"id" : "1",
"name" : "image.png"
},
{
"id" : "2",
"name" : "Test.jpg"
},
{
"id" : "3",
"name" : "Next.png"
}
]
}
Generally speaking it's not a good idea to have dynamic keys in your documents. Your best bet in situation like this is mapReduce
var map = function() {
emit(this._id, Object.keys(this.Documents).length);
};
var reduce = function(key, values) {};
db.collection.mapReduce(map, reduce, { "out": { "inline": 1 } } )
Which yields:
{
"results" : [
{
"_id" : ObjectId("5686e2a98981f193cf343697"),
"value" : 3
}
],
"timeMillis" : 697,
"counts" : {
"input" : 1,
"emit" : 1,
"reduce" : 0,
"output" : 1
},
"ok" : 1
}
Definitely the best thing to do is to change your documents structure and make Documents an array of sub-documents so that the Documents field value looks like this:
"Documents": [
{
"id": "1",
"name": "image.png",
},
{
"id": "2",
"name": "Test.jpg",
},
{
"id": "3",
"name": "Next.png"
}
]
and use the .aggregate() method to $project your documents and return the number of elements in the "Documents" array using the $size operator
db.collections.aggregate([
{ "$project": { "count": { "$size": "$Documents" } } }
] )

Search key in Mongodb document

following is my json which i have inserted in mongodb. I need to find record where cardholders value is 200
{
"_id": "11",
"cardholders": {
"100": [
{
"message": "message1",
"time": "timestamp"
},
{
"message": "message2",
"time": "timestamp"
}
],
"200": [
{
"message": "message1",
"time": "timestamp"
},
{
"message": "message2",
"time": "timestamp"
},
{
"message": "message3",
"time": "timestamp"
}
],
"300": [
{
"message": "message1",
"time": "timestamp"
},
{
"message": "message2",
"time": "timestamp"
}
]
}
}
Please advice. I have following
db.test3.find({"message1":{$eq:true}})
> db.test3.find({"100":{$eq:true}})
> db.test3.find({cardholders:{$eq:'100'}})
> db.test3.find({cardholders:{$eq:100}})
You essentially want to include a projection argument in your find() query which matches documents containing only the projection field and the _id field. In this case since "200" is the key, you can project it and use the map() cursor to get the values as follows:
db.test3.find(
{},
{"cardholders.200": 1}
).map(function(doc){ return doc["cardholders"]["200"]})
Output:
/* 0 */
{
"0" : [
{
"message" : "message1",
"time" : "timestamp"
},
{
"message" : "message2",
"time" : "timestamp"
},
{
"message" : "message3",
"time" : "timestamp"
}
]
}
UPDATE
To make querying easier, I would recommend changing your schema to change the cardholders key into an array that holds embedded documents. These embedded documents would have a key and value fields; the key field holds the previous dynamic keys and the value field holds the array values:
{
"_id": "11",
"cardholders": [
{
"key": "100",
"values": [
{
"message": "message1",
"time": "timestamp"
},
{
"message": "message2",
"time": "timestamp"
}
]
},
{
"key": "200",
"values": [
{
"message": "message1",
"time": "timestamp"
},
{
"message": "message2",
"time": "timestamp"
}
]
},
{
"key": "300",
"values": [
{
"message": "message1",
"time": "timestamp"
},
{
"message": "message2",
"time": "timestamp"
}
]
}
]
}
You can then query the embedded documents using a combination of the dot notation and the $elemMatch projection as follows:
db.test3.find(
{"cardholders.key": "200"},
{
"cardholders": {
"$elemMatch": { "key": "200" }
}
}
);

Mongo returning an array element

I have the following JSON document in my mongoDB which I added with mingoimport.
I am trying to return a single element from the questions array where theQuestion equals "q1".
{
"questions": [
{
"questionEntry": {
"id": 1,
"info": {
"seasonNumber": 1,
"episodeNumber": 1,
"episodeName": "Days Gone Bye"
},
"questionItem": {
"theQuestion": "q1",
"attachedElement": {
"type": 1,
"value": ""
}
},
"options": [
{
"type": 1,
"value": "o1"
},
{
"type": 1,
"value": "o1"
}
],
"answer": {
"questionId": 1,
"answer": 1
},
"metaTags": [
"Season 1",
"Episode 1",
"Rick Grimmes"
]
}
},
{
"questionEntry": {
"id": 1,
"info": {
"seasonNumber": 1,
"episodeNumber": 1,
"episodeName": "Days Gone Bye"
},
"questionItem": {
"theQuestion": "q2",
"attachedElement": {
"type": 1,
"value": ""
}
},
"options": [
{
"type": 1,
"value": "o2"
},
{
"type": 1,
"value": "o2"
}
],
"answer": {
"questionId": 1,
"answer": 1
},
"metaTags": [
"Season 1",
"Episode 1",
"Rick Grimmes",
"Glenn Rhee"
]
}
}
]
}
I ran the query db.questions.find({"questions.questionEntry.questionItem.theQuestion" : "q1"}) but this retruned the whole document (both questionEntry's in question array!
I have tried db.questions.find({"questions.questionEntry.questionItem.theQuestion" : "q1"}, _id:0," questions.questionItem": {$elemMatch : {theQuestion: "q1"}}})
But get the following error:
Error: error: {
"$err" : "Can't canonicalize query: BadValue Cannot use $elemMatch projection on a nested field.", "code" : 17287
Is there a way I could limit the result to just the array element which contains it?
Thanks
db.questions.find({},{"questions.questionEntry.questionItem.theQuestion" : "q1"});
or
db.questions.find({"questions.questionEntry.questionItem.theQuestion" : "q1"},{'questions.$':1});
please try these.
If you want to use $elemMatch the query should be:
db.questions.find(
{"questions.questionEntry.questionItem.theQuestion" : "q1"},
{
'_id':0,
"questions": {
$elemMatch : {"questionEntry.questionItem.theQuestion": "q1"}
}
}
)