How to count a specific element in a collection in mongodb - 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" } } }
] )

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

mongoose query or aggregation to remove certain subdocument fields

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"
}
]
}

Selecting part of tree in MongoDB

I am storing some tree-structured data in MongoDB. It looks something like this:
{
"_id": 1,
"name": "foo",
"subs": [{
"name": "bar",
"subs": [{
"name": "baz"
},
{
"name": "gizmo"
}]
}]
}
{
"_id": 2,
"name": "foo",
"subs": [{
"name": "bar",
"subs": [{
"name": "gizmo"
}]
}]
}
I want to query on the subtrees, and wish only to extract the part of the tree that matches the query. In each node, there is at most one subtree that matches the query.
In this test-query, I want to find the part of the tree that matches the path "foo/bar/gizmo". So, I have tried the following:
db.Test.find({
name: "foo",
subs: {
$elemMatch: {
name: "bar",
subs: {
$elemMatch: {
name: "gizmo"
}
}
}
}
},
{
"subs.subs": {
$slice: 1
}
})
The result I get is
{
"_id": 1,
"name": "foo",
"subs": [{
"name": "bar",
"subs": [{
"name": "baz"
}]
}]
}{
"_id": 2,
"name": "foo",
"subs": [{
"name": "bar",
"subs": [{
"name": "gizmo"
}]
}]
}
whereas I actually wanted
{
"_id": 1,
"name": "foo",
"subs": [{
"name": "bar",
"subs": [{
"name": "gizmo"
}]
}]
}{
"_id": 2,
"name": "foo",
"subs": [{
"name": "bar",
"subs": [{
"name": "gizmo"
}]
}]
}
Is it possible to do this? I have tried using the $elemMatch projection operator on the subtrees, but it seems it is not currently supported by Mongo (Cannot use $elemMatch projection on a nested field (currently unsupported)).
You can use Aggregation Framework to get result as you wish. Try the following code :
db.Test.aggregate(
{$unwind : "$subs"},
{$unwind : "$subs.subs"},
{$match : {name : "foo", "subs.name" : "bar", "subs.subs.name" : "gizmo"}}
);
Result will be like :
"result" : [
{
"_id" : ObjectId("52540da2e3a2c44e082642d4"),
"name" : "foo",
"subs" : {
"name" : "bar",
"subs" : {
"name" : "gizmo"
}
}
},
{
"_id" : ObjectId("52540dd6e3a2c44e082642d5"),
"name" : "foo",
"subs" : {
"name" : "bar",
"subs" : {
"name" : "gizmo"
}
}
}
]

MongoDB - Retrieve only the matching subdocument AND the root document

Edit:
Problem solved, see below.
I have the following document:
db.clients.insert({
_id: ObjectId("524d720d8d3ea014a52e95bb"),
company: "Example",
logins: [
{
"name": "John Smith",
"username": "test",
"password": "eF9wnBEys0OzL5vmR/OHGCaekHiw/Miy+XvbDdayxeo=",
"email": "a#a.com",
"last": null,
"roles": ["CONFIG"]
},
{
"name": "Guest",
"username": "guest",
"password": "K/gYODb7XPo0erySvL276DyPi4+stPPK4jM3pJ8aaVg=",
"email": "a#a.com",
"last": null,
"roles": []
}
]
});
And now, I want to authenticate my clients, using this document. But, I don't want to retrieve every sub logins, I want only the one which match.
That's why I'm using an aggregate:
db.clients.aggregate(
{
"$project": {
"login": "$logins",
"_id": 0
}
},
{
"$unwind": "$login"
},
{
"$group": {
"_id": "$login.username",
"login": {
"$first": "$login"
}
}
},
{
"$match": {
"login.username": "test",
"login.password": "eF9wnBEys0OzL5vmR/OHGCaekHiw/Miy+XvbDdayxeo=",
}
}
);
Which works fine, giving me:
{
"result" : [
{
"_id" : "test",
"login" : {
"name" : "John Smith",
"username" : "test",
"password" : "eF9wnBEys0OzL5vmR/OHGCaekHiw/Miy+XvbDdayxeo=",
"email" : "a#a.com",
"last" : null,
"roles" : [
"CONFIG"
]
}
}
],
"ok" : 1
}
But now, the tricky part is that I would like to retrieve also the root document fields. Such as _id and company for example.
But no matter what I try, I can't manage to do it. Do you have a solution? :)
Edit:
Ok, in fact it wasn't that hard. I'm sorry!
db.clients.aggregate(
{
"$project": {
"login": "$logins",
"_id": "$_id",
"company": "$company"
}
},
{
"$unwind": "$login"
},
{
"$group": {
"_id": "$login.username",
"login": {
"$first": "$login"
},
"clientId": {
"$first": "$_id"
},
"company": {
"$first": "$company"
},
}
},
{
"$match": {
"login.username": "test",
"login.password": "eF9wnBEys0OzL5vmR/OHGCaekHiw/Miy+XvbDdayxeo=",
}
}
);
You can do this with a find and the $ positional projection operator as well:
db.clients.find({
"logins.username": "test",
"logins.password": "eF9wnBEys0OzL5vmR/OHGCaekHiw/Miy+XvbDdayxeo=",
}, {
"logins.$": 1,
"company": 1
})
The $ in the projection contains the index of the logins array element that was matched in the query.
Output:
{
"_id": ObjectId("524d720d8d3ea014a52e95bb"),
"company": "Example",
"logins": [
{
"name": "John Smith",
"username": "test",
"password": "eF9wnBEys0OzL5vmR/OHGCaekHiw/Miy+XvbDdayxeo=",
"email": "a#a.com",
"last": null,
"roles": [
"CONFIG"
]
}
]
}
A bit shorter variant:
db.clients.aggregate(
{$match:{"logins.username":"test"}},
{$unwind:"$logins"},
{$match:{"logins.username":"test","logins.password":"eF9wnBEys0OzL5vmR/OHGCaekHiw/Miy+XvbDdayxeo="}}
)
Output is:
{
"result" : [
{
"_id" : ObjectId("524d720d8d3ea014a52e95bb"),
"company" : "Example",
"logins" : {
"name" : "John Smith",
"username" : "test",
"password" : "eF9wnBEys0OzL5vmR/OHGCaekHiw/Miy+XvbDdayxeo=",
"email" : "a#a.com",
"last" : null,
"roles" : [
"CONFIG"
]
}
}
],
"ok" : 1
}