mongodb find element in array or less but no other - mongodb

I have this collection:
{
"title": "First Item",
"attributes": [
{
"id": "1",
"text": "Alpha"
},
{
"id": "2",
"text": "Bravo"
},
{
"id": "3",
"text": "Charlie"
}
]
},
{
"title": "Second Item",
"attributes": [
{
"id": "1",
"text": "Alpha"
},
{
"id": "2",
"text": "Bravo"
},
{
"id": "3",
"text": "Tango"
}
]
}
Trying search with these values for "attributes.text" field:
{ "Alpha", "Bravo", "Charlie", "Delta" }
I want to find only "First Item" document that contains at least these keys but no others.
But trying these values:
{ "Alpha", "Bravo", "Delta" }
or
{ "Alpha", "Bravo" }
I do not want to find any result (beacuse "Charlie" is missing).
Thank you

Use the $in operator to match values in the attributes object array. The following query will select all documents in the collection where the text field value of the attribute object array is either "Alpha", "Bravo" or "Delta":
dbo.collection.find({
"attributes.text": {
"$in": ["Alpha", "Bravo", "Delta"]
}
});
This will return the two documents:
/* 0 */
{
"_id" : ObjectId("551187bae3757367bf8bd915"),
"title" : "First Item",
"attributes" : [
{
"id" : "1",
"text" : "Alpha"
},
{
"id" : "2",
"text" : "Bravo"
},
{
"id" : "3",
"text" : "Charlie"
}
]
}
/* 1 */
{
"_id" : ObjectId("551187bae3757367bf8bd916"),
"title" : "Second Item",
"attributes" : [
{
"id" : "1",
"text" : "Alpha"
},
{
"id" : "2",
"text" : "Bravo"
},
{
"id" : "3",
"text" : "Tango"
}
]
}

Related

How can we rename a field or give alias to field in MongoDB Atlas charts?

How can we rename a field or give alias to field in MongoDB Atlas charts?
Examples:
this is the students collection
{
"_id": 1,
"alias": [ "The American Cincinnatus", "The American Fabius" ],
"mobile": "555-555-5555",
"nmae": { "first" : "george", "last" : "washington" }
},
{
"_id": 2,
"alias": [ "My dearest friend" ],
"mobile": "222-222-2222",
"nmae": { "first" : "abigail", "last" : "adams" }
}
Here I update the nmae field to name
db.students.updateMany( {}, { $rename: { "nmae": "name" } } )
{
"_id": 1,
"alias": [ "The American Cincinnatus", "The American Fabius" ],
"mobile": "555-555-5555",
"name": { "first" : "george", "last" : "washington" }
},
{
"_id" : 2,
"alias" : [ "My dearest friend" ],
"mobile" : "222-222-2222",
"name" : { "first" : "abigail", "last" : "adams" }
}
ref: https://docs.mongodb.com/manual/reference/operator/update/rename/

Sorting an array of sub-documents in MongoDB (without using Aggregation)

I'm tryng to sort in Mongo using a field belonging to an array of object. The problem is that i cannot use aggregation and the schema cannot be modified because of external constraints.
this is an example of my documents
[{
"name": "product 1",
"tags": [{
"tag": "category",
"value": "shoes"
},{
"tag": "gender",
"value": "man"
}]
},
{
"name": "product 2",
"tags": [{
"tag": "category",
"value": "bags"
},{
"tag": "season",
"value": "fall-winter"
}]
},
{
"name": "product 3",
"tags": [{
"tag": "category",
"value": "clothing"
},{
"tag": "gender",
"value": "woman"
}]
}]
let's assume i want to sort them ascending using the tag category
so i should have the documents sorted in that way: "product 2", "product 3", "product 1"
There is a way to do it without aggregation and without modifing the schema?
... i cannot use aggregation...
The following code will do the sorting, if the "category" sub-document is the first element of the tags array.
db.product_cats.find().toArray().sort( (a, b) => {
return a.tags[0].value > b.tags[0].value;
});
If the "category" sub-document appears at any index of the array, the following will do the sorting.
db.product_cats.find().toArray().sort( (a, b) => {
let ixa = a.tags.findIndex(e => e.tag == "category");
let ixb = b.tags.findIndex(e => e.tag == "category");
return a.tags[ixa].value > b.tags[ixb].value;
});
If the "category" sub-document is missing for some documents then this will do the sorting:
db.product_cats.find().toArray().sort( (a, b) => {
let ixa = a.tags.findIndex(e => e.tag == "category");
let ixb = b.tags.findIndex(e => e.tag == "category");
if ( ixa == -1 ) { a.tags = [ ]; a.tags[ ixa] = { value: "" } };
if ( ixb == -1 ) { b.tags = [ ]; b.tags[ ixa] = { value: "" } };
return a.tags[ixa].value > b.tags[ixb].value;
});
Your sort criteria should be as following:
db.sorter.find().sort({"tags.value": 1})
Outcome
{ "_id" : ObjectId("5e1dd93f4a030a105864baaa"), "name" : "product 2", "tags" : [ { "tag" : "category", "value" : "bags" }, { "tag" : "season", "value" : "fall-winter" } ] }
{ "_id" : ObjectId("5e1dd93f4a030a105864baab"), "name" : "product 3", "tags" : [ { "tag" : "category", "value" : "clothing" }, { "tag" : "gender", "value" : "woman" } ] }
{ "_id" : ObjectId("5e1dd93f4a030a105864baa9"), "name" : "product 1", "tags" : [ { "tag" : "category", "value" : "shoes" }, { "tag" : "gender", "value" : "man" } ] }

MongoDB Return Inner Document From Array

I am trying to fetch an element from an array in a document and only the element I don't want the entire document
I tried a different method but they all return the entire document
db.dept.find({"section.classes.CRN":"1901"}).limit(100)
db.dept.where("section.classes.CRN").eq("1901").limit(100)
json
{
"_id" : ObjectId("5d70ab0c280d6b8ebb850cc1"),
"name" : "Art Studio",
"abbr" : "ARS",
"section" : [
{
"type" : "Undergraduate Courses",
"classes" : [
{
"CRN" : "193",
"Course" : "ARS100",
"Sec" : "01",
"Title" : "Drawing I",
"Cr" : "3",
"Dates" : "8/26-12/19",
"Days" : "MR",
"Time" : "1230P-0320P",
"Loc" : "SAB 226",
"Instructor" : "Schuck",
"Attributes" : "",
"Avail" : "F"
},
{
"CRN" : "293",
"Course" : "ARS100",
"Sec" : "02",
"Title" : "Drawing I",
"Cr" : "3",
"Dates" : "8/26-12/19",
"Days" : "MR",
"Time" : "0330P-0620P",
"Loc" : "SAB 226",
"Instructor" : "Itty",
"Attributes" : "",
"Avail" : "F"
},
{...
I am trying to get this or something similar when searching for a set of CRN values
json
[ {
"CRN" : "193",
"Course" : "ARS100",
"Sec" : "01",
"Title" : "Drawing I",
"Cr" : "3",
"Dates" : "8/26-12/19",
...
"Instructor" : "Schuck",
"Attributes" : "",
"Avail" : "F"
}
]
Try using the aggregate pipeline to project double nested array as:
Input:
[
{
"_id": ObjectId("5d70ab0c280d6b8ebb850cc1"),
"name": "Art Studio",
"abbr": "ARS",
"section": [
{
"type": "Undergraduate Courses",
"classes": [
{
"CRN": "193",
"Course": "ARS100",
"Sec": "01",
"Title": "Drawing I",
"Cr": "3",
"Dates": "8/26-12/19",
"Days": "MR",
"Time": "1230P-0320P",
"Loc": "SAB 226",
"Instructor": "Schuck",
"Attributes": "",
"Avail": "F"
},
{
"CRN": "293",
"Course": "ARS100",
"Sec": "02",
"Title": "Drawing I",
"Cr": "3",
"Dates": "8/26-12/19",
"Days": "MR",
"Time": "0330P-0620P",
"Loc": "SAB 226",
"Instructor": "Itty",
"Attributes": "",
"Avail": "F"
}
]
}
]
}
]
Query:
hereafter unwinding section you can filter classes for CRN
db.collection.aggregate([
{
$unwind: "$section"
},
{
$project: {
name: 1,
abbr: 1,
"section.type": 1,
"section.classes": {
$filter: {
input: "$section.classes",
as: "item",
cond: {
$eq: [
"$$item.CRN",
"193"
]
}
}
}
}
},
{
$group: {
_id: "$_id",
section: {
$push: "$section"
}
}
}
])
Output:
you can manage your keys as you want in project for adding new keys or replacing them.
[
{
"_id": ObjectId("5d70ab0c280d6b8ebb850cc1"),
"section": [
{
"classes": [
{
"Attributes": "",
"Avail": "F",
"CRN": "193",
"Course": "ARS100",
"Cr": "3",
"Dates": "8/26-12/19",
"Days": "MR",
"Instructor": "Schuck",
"Loc": "SAB 226",
"Sec": "01",
"Time": "1230P-0320P",
"Title": "Drawing I"
}
],
"type": "Undergraduate Courses"
}
]
}
]
db.dept.find({"section.classes.CRN":"1901"},{"section.classes":1}).limit(100)
It's called projection in mongodb, you pass a second object in find query to specify which fields you want in result.
so according to your above case if you want name, and section in result you should pass something like this
db.dept.find({"section.classes.CRN":"1901"},{"name":1, "section":1}).limit(100)

mongodb aggregation with array

I have data like this:
{
"_id" : ObjectId("..."),
"name" : "Entry 1",
"time" : ISODate("2013-12-28T06:00:00.000Z"),
"value" : 100
},
{
"_id" : ObjectId("..."),
"name" : "Entry 2",
"time" : ISODate("2013-12-28T06:00:00.000Z"),
"value" : 200
},
{
"_id" : ObjectId("..."),
"name" : "Entry 1",
"time" : ISODate("2013-12-28T11:00:00.000Z"),
"value" : 110
},
{
"_id" : ObjectId("..."),
"name" : "Entry 2",
"time" : ISODate("2013-12-28T11:00:00.000Z"),
"value" : 230
},
{
"_id" : ObjectId("..."),
"name" : "Entry 3",
"time" : ISODate("2013-12-28T11:00:00.000Z"),
"value" : 25
},
{
"_id" : ObjectId("..."),
"name" : "Entry 4",
"time" : ISODate("2013-12-28T11:00:00.000Z"),
"value" : 15
}
I need the result grouped by time with percentage for each entry like this (group entries by volume "others" when entries for time period more than two, but it's not necessary):
{
"_id": ISODate("2013-12-28T11:00:00.000Z"),
"entries": [
{
"name": "Entry 1",
"percentage": 33.3
},
{
"name": "Entry 2",
"percentage": 66.6
},
]
},
{
"_id": ISODate("2013-12-28T06:00:00.000Z"),
"entries": [
{
"name": "Entry 1",
"percentage": 28.9
},
{
"name": "Entry 2",
"percentage": 60.5
},
{
"name": "Others",
"percentage": 10.5
}
]
}
So the request I was try:
db.collection.aggregate([
{
"$addFields": {
"full_datetime": {"$substr": ["$time", 0, 19]}
}
},
{
"$group": {
"_id": "$full_datetime",
"value_sum": {"$sum": "$value"},
"entries": {
"$push": {
"name": "$name",
"percentage": {
"$multiply": [{
"$divide": ["$value", {"$literal": "$value_sum" }]
}, 100 ]
}
}
}
}
}
])
This request is not work because $value_sum does not exists inside $push.
Please help me how I can to send this $value_sum into the $push statement
You can use one more stage to calculate percentage using $map as,
db.collection.aggregate([
"$addFields": {
"full_datetime": {
"$substr": ["$time", 0, 19]
}
}
}, {
"$group": {
"_id": "$full_datetime",
"value_sum": {
"$sum": "$value"
},
"entries": {
"$push": {
"name": "$name",
"value": "$value"
}
}
}
}, {
"$project": {
"entriesNew": {
"$map": {
"input": "$entries",
"as": "entry",
"in": {
"name": "$$entry.name",
"percentage": {
"$multiply": [{
"$divide": ["$$entry.value", "$value_sum"]
}, 100]
}
}
}
}
}
}])
Output:
/* 1 */
{
"_id" : "2013-12-28T11:00:00",
"entries" : [
{
"name" : "Entry 1",
"percentage" : 28.9473684210526
},
{
"name" : "Entry 2",
"percentage" : 60.5263157894737
},
{
"name" : "Entry 3",
"percentage" : 6.57894736842105
},
{
"name" : "Entry 4",
"percentage" : 3.94736842105263
}
]
}
/* 2 */
{
"_id" : "2013-12-28T06:00:00",
"entries" : [
{
"name" : "Entry 1",
"percentage" : 33.3333333333333
},
{
"name" : "Entry 2",
"percentage" : 66.6666666666667
}
]
}

Aggregate in MongoDB excluding array item

I need to group these docs by taxonomy array but I've to exclude from group the "term3" item from the first Doc. (My situation is more complicated in a real app but this example fits.)
[
{
"_id": "1",
"Taxonomy": [
{
"Key": "1", "Term": "term1"
},
{
"Key": "2", "Term": "term2"
},
{
"Key": "3", "Term": "term3"
}
]
},
{
"_id": "2",
"Taxonomy": [
{
"Key": "1", "Term": "term1"
},
{
"Key": "2", "Term": "term2"
}
]
},
{
"_id": "3",
"Taxonomy": [
{
"Key": "1", "Term": "term1"
},
{
"Key": "2", "Term": "term2"
}
]
}
]
This command generates two nodes due to the "term3" item:
{$group: {"_id" :"$Taxonomy","Posts" : { "$addToSet" : { "_id" : "$_id"}}}}
It is possible to unwind, match and then re-group documents or there is a simple way?
Running this aggregation:
db.terms.aggregate([
{$unwind:"$Taxonomy"},
{$match:{"Taxonomy.Term":{$ne:"term3"}}},
{$group:{_id:"$_id",Taxonomy:{$push:"$Taxonomy"}}}
])
Will produce this result:
{ "_id" : "2", "Taxonomy" : [ { "Key" : "1", "Term" : "term1" }, { "Key" : "2", "Term" : "term2" } ] }
{ "_id" : "3", "Taxonomy" : [ { "Key" : "1", "Term" : "term1" }, { "Key" : "2", "Term" : "term2" } ] }
{ "_id" : "1", "Taxonomy" : [ { "Key" : "1", "Term" : "term1" }, { "Key" : "2", "Term" : "term2" } ] }
If you then need to do further grouping/processing, you can add more stages. If you needed to exclude based on Key rather than Term then you can adjust $match stage. If you want to exclude everything other than term1 and term2 you would change the $ne to be $in:[<list-of-values-to-keep>].