All Mongo documents with duplicated objects inside array - mongodb

Find documents with duplicated objects inside an array.
Some answers works just with array made of "basic type elements" (i.e. array of strings). Here I want to filter on certain objects fields
In example:
{
"name": "1",
"arr": [{ "type": "fruit", "name":"pear"},{ "type": "fruit","name":"banana"}]
},
{
"name":"2",
"arr": [{"type":"fish"}]
}
Given the above two documents, I want to retrieve just document 1, because it has 2 elements in the array that have the same type. (Of course I want all documents with such property, not just one)

The following query can get us the expected output:
db.collection.find({
$expr:{
$ne:[
{
$size:"$arr"
},
{
$size:{
$setUnion:["$arr.type"]
}
}
]
}
}).pretty()
Data set:
{
"_id" : ObjectId("5d7b8546d76ccfa3cb0f133c"),
"name" : "1",
"arr" : [
{
"type" : "fruit",
"name" : "pear"
},
{
"type" : "fruit",
"name" : "banana"
}
]
}
{
"_id" : ObjectId("5d7b8546d76ccfa3cb0f133d"),
"name" : "2",
"arr" : [
{
"type" : "fish"
}
]
}
{
"_id" : ObjectId("5d7b8546d76ccfa3cb0f133e"),
"name" : "3",
"arr" : [
{
"type" : "product",
"name" : "watch"
},
{
"type" : "product",
"name" : "Pen"
}
]
}
Output:
{
"_id" : ObjectId("5d7b8546d76ccfa3cb0f133c"),
"name" : "1",
"arr" : [
{
"type" : "fruit",
"name" : "pear"
},
{
"type" : "fruit",
"name" : "banana"
}
]
}
{
"_id" : ObjectId("5d7b8546d76ccfa3cb0f133e"),
"name" : "3",
"arr" : [
{
"type" : "product",
"name" : "watch"
},
{
"type" : "product",
"name" : "Pen"
}
]
}
Query analysis: We are filtering documents in which the size of arr is not equal to the count of unique type present in the arr

Related

Update double nested array mongodb

I have the below document which contains double nested array format. I have to update the "level" field to "Senior Engineer" when the "someKey":"somevalue" and "Company":"Company1" and "Name":"Nandhi".
Document
{
"_id" : "777",
"someKey" : "someValue",
"someArray" : [
{
"Company" : "Company1",
"someNestedArray" : [
{
"name" : "Nandhi",
"level" : "Junior Engineer"
},
{
"name" : "Rajan",
"level" : "Senio Engineer"
}
]
}],
{
"Company" : "Company2",
"someNestedArray" : [
{
"name" : "Nandhi",
"level" : "Junior Engineer"
},
{
"name" : "Rajan",
"level" : "Senio Engineer"
}
]
}
]
}
Update Query I tried
db.Test123.updateOne(
{"someKey" : "someValue","someArray.Company":"Company1"},
{$set:{"someArray.$[someNestedArray].level":"Senior Developer"}},
{arrayFilters:[{"someNestedArray.name":"Nandhi"}]}
);
Output Screenshot
As you can seen that, the modifiedCount returns 0. Please advice on this!
You need to define arrayFilter for every level of nesting, try:
db.Test123.update(
{ "someKey" : "someValue" },
{ "$set": { "someArray.$[someArrayDoc].someNestedArray.$[someNestedArrayDoc].level": "Senior Developer" } },
{ arrayFilters: [ {"someArrayDoc.Company": "Company1"}, { "someNestedArrayDoc.name": "Nandhi" } ] }
)

MongoDB Grouping and Project Custom Fields

I am stuck with MongoDB grouping and project custom fields. I have the following collection:
{
"DescId" : "1",
"Desc" : "Testing",
"ParentId" : "null",
"Order" : 1.0,
"Type" : "A",
"Parent" : null
}
{
"DescId" : "1.1",
"Desc" : "Testing Child 1",
"ParentId" : "1",
"Order" : 1.0,
"Type" : "B",
"Parent" : "Testing"
}
{
"DescId" : "1.2",
"Desc" : "Testing Child 2",
"ParentId" : "1",
"Order" : 2.0,
"Type" : "B",
"Parent" : "Testing"
}
I have done following grouping based on Type, DescId, Desc fields and projected DescId and Desc
db.getCollection("GenericData").aggregate(
[
{
"$group" : {
"_id" : {
"Type" : "$Type",
"DescId" : "$DescId",
"DescName" : "$Desc"
}
}
},
{
"$project" : {
"_id" : 0.0,
"Id" : "$_id.DescId",
"Name" : "$_id.DescName",
"Type" : "$_id.Type"
}
}
],
{
"allowDiskUse" : false
}
);
This is the output I am getting:
{
"Id" : "1.2",
"Name" : "Testing Child 2",
"Type" : "B"
}
{
"Id" : "1.1",
"Name" : "Testing Child 1",
"Type" : "B"
}
{
"Id" : "1",
"Name" : "Testing",
"Type" : "A"
}
Would it be possible to project fields based on Type field, like concatenating Type value with field name like following:
{
"A" + "Id" : "1",
"A" + "Name" : "Testing"
},
{
"B" + "Id" : "1.1",
"B" + "Name" : "Testing Child 1"
}
{
"B" + "Id" : "1.2",
"B" + "Name" : "Testing Child 2"
}
To rename your object's keys you have to use $objectToArray and $arrayToObject operators. First one can convert your $$ROOT object into an array of keys and values. Then you can apply $map to modify keys (using $concat) and $filter to exclude Type key. Then you can convert that array back to an object using $arrayToObject and promote that object to a root level using $replaceRoot. So you can add below stage to your aggregation pipeline:
db.GenericData.aggregate([
{
$replaceRoot: {
newRoot: {
$arrayToObject: {
$map: {
input: {
$filter: {
input: { $objectToArray: "$$ROOT" },
as: "kv",
cond: { $in: [ "$$kv.k", [ "Id", "Name" ] ] }
}
},
as: "kv",
in: {
k: { $concat: [ "$Type", "$$kv.k" ] },
v: "$$kv.v"
}
}
}
}
}
}
])
outputs:
{ "BId" : "1.2", "BName" : "Testing Child 2" }
{ "BId" : "1.1", "BName" : "Testing Child 1" }
{ "AId" : "1", "AName" : "Testing" }
EDIT:
If you'd like to explicitly specify which properties should be projected you can use $in operator inside $filter

MongoDB query with multiple conditions

I have data with multiple documents :
{
"_id" : ObjectId("57b68dbbc19c0bd86d62e486"),
"empId" : "1"
"type" : "WebUser",
"city" : "Pune"
}
{
"_id" : ObjectId("57b68dbbc19c0bd86d62e487"),
"empId" : "2"
"type" : "Admin",
"city" : "Mumbai"
}
{
"_id" : ObjectId("57b68dbbc19c0bd86d62e488"),
"empId" : "3"
"type" : "Admin",
"city" : "Pune"
}
{
"_id" : ObjectId("57b68dbbc19c0bd86d62e489"),
"empId" : "4"
"type" : "User",
"city" : "Mumbai"
}
I want to get data according to my multiple conditions :
condition 1:- {"type" : "WebUser", "city" : "Pune"}
condition 2:- {"type" : "WebUser", "city" : "Pune"} & {"type" : "User", "city" : "Mumbai"}
I want below result when run condition 1 :
{
"_id" : ObjectId("57b68dbbc19c0bd86d62e486"),
"empId" : "1"
"type" : "WebUser",
"city" : "Pune"
}
When I run second condition :
{
"_id" : ObjectId("57b68dbbc19c0bd86d62e486"),
"empId" : "1"
"type" : "WebUser",
"city" : "Pune"
}
{
"_id" : ObjectId("57b68dbbc19c0bd86d62e489"),
"empId" : "4"
"type" : "User",
"city" : "Mumbai"
}
I want above result by one query,
Currently I am using below aggregate query,
db.emp.aggregate([
{ $match: { '$and': [
{"type" : "WebUser", "city" : "Pune"},
{"type" : "User", "city" : "Mumbai"}
] } },
{ $group: { _id: 1, ids: { $push: "$empId" } } }
])
Above query work for first condition & fails for other. Please help me.
For the second condition, you can use the $in operator in your query as:
db.emp.find({
"type" : { "$in": ["WebUser", "User"] },
"city" : { "$in": ["Pune", "Mumbai"] }
})
If you want to use in aggregation:
db.emp.aggregate([
{
"$match": {
"type" : { "$in": ["WebUser", "User"] },
"city" : { "$in": ["Pune", "Mumbai"] }
}
},
{ "$group": { "_id": null, "ids": { "$push": "$empId" } } }
])
or simply use the distinct() method to return an array of distinct empIds that match the above query as:
var employeeIds = db.emp.distinct("empId", {
"type" : { "$in": ["WebUser", "User"] },
"city" : { "$in": ["Pune", "Mumbai"] }
});
If you are looking for the AND operator
This example checks if a field exists AND is null
db.getCollection('TheCollection').find({
$and: [
{'the_key': { $exists: true }},
{'the_key': null}
]
})
This example checks if a field has 'value1' OR 'value2'
db.getCollection('TheCollection').find({
$or: [
{'the_key': 'value1'},
{`the_key': 'value2'}
]
})
When just checking for null, the return contains non-existing fields plus fields with value null
db.getCollection('TheCollection').find({'the_key': null})
You can use mongo db $or operator.
db.emp.find({ $or: [
{ "type": "WebUser", "city": "Pune" },
{ "type": "user", "city": "Mumbai"}
]})
You can pass conditions in the array.
For more reference see mongo docs
Display the document where in the “StudName” has value “Ajay Rathod”.
db.Student.find({name:"ajay rathod"})
{ "_id" : ObjectId("5fdd895cd2d5a20ee8cea0de"), "
Retrieve only Student Name and Grade.
db.Student.find({},{name:1,grade:1,_id:0})
{ "name" : "dhruv", "grade" : "A" }
{ "name" : "jay", "grade" : "B" }
{ "name" : "abhi", "grade" : "C" }
{ "name" : "aayush", "grade" : "A" }
{ "name" : "sukhdev", "grade" : "B" }
{ "name" : "dhruval", "grade" : "B" }
{ "name" : "ajay rathod", "grade" : "D" }

Group multi-dimensional array after unwinding elements

Again with mongoDB. I really like aggregation, but still can't "get it".
So here is my array:
{
"_id" : ObjectId("55951b2bf41edfc80b00002a"),
"orders" : [
{
"id" : "55929142f41edfdc0f00002f",
"name" : "XYZ",
"id_basket" : 1,
"card" : [
{
"id" : "250",
"serial" : "B",
"type" : "9cf4161002b9eda349bb9c5ae64b9f4a",
"name" : "Eco",
"ticket" : [
{
"id" : "55927d41f41edfd00f000030",
"name" : "ZZZ",
"price" : {
"name" : "Normal",
"price" : "10",
"price_disp" : "10 €",
}
},
{
"id" : "55927d41f41edfd00f000030",
"name" : "ZZZ",
"price" : {
"name" : "Normal",
"price" : "10",
"price_disp" : "10 €",
}
}
]
},
{
"id" : "250",
"serial" : "B",
"type" : "9cf4161002b9eda349bb9c5ae64b9f4a",
"name" : "Eco",
"ticket" : [
{
"id" : "55927d41f41edfd00f000030",
"name" : "ZZZ",
"price" : {
"name" : "Normal",
"price" : "10",
"price_disp" : "10 €",
}
},
{
"id" : "55927d41f41edfd00f000030",
"name" : "ZZZ",
"price" : {
"name" : "Normal",
"price" : "10",
"price_disp" : "10 €",
}
}
]
}
],
"full_amount" : "40",
},
{
"id" : "55929142f41edfdc0f00002f",
"name" : "XYZ",
"id_basket" : 1,
"card" : [
{
"id" : "250",
"serial" : "B",
"type" : "9cf4161002b9eda349bb9c5ae64b9f4a",
"name" : "Eco",
"ticket" : [
{
"id" : "55927d41f41edfd00f000030",
"name" : "ZZZ",
"price" : {
"name" : "Normal",
"price" : "10",
"price_disp" : "10 €",
}
},
{
"id" : "55927d41f41edfd00f000030",
"name" : "ZZZ",
"price" : {
"name" : "Normal",
"price" : "10",
"price_disp" : "10 €",
}
}
]
},
{
"id" : "250",
"serial" : "B",
"type" : "9cf4161002b9eda349bb9c5ae64b9f4a",
"name" : "Eco",
"ticket" : [
{
"id" : "55927d41f41edfd00f000030",
"name" : "ZZZ",
"price" : {
"name" : "Normal",
"price" : "10",
"price_disp" : "10 €",
}
},
{
"id" : "55927d41f41edfd00f000030",
"name" : "ZZZ",
"price" : {
"name" : "Normal",
"price" : "10",
"price_disp" : "10 €",
}
}
]
}
],
"full_amount" : "40",
},
],
"rate" : "0.23",
"date" : "2015-07-02 13:04:34",
"id_user" : 97,
}
I want to output something like this:
{
"_id" : ObjectId("55951b2bf41edfc80b00002a"),
"orders" : [
{
"id" : "55929142f41edfdc0f00002f",
"name" : "XYZ",
"card" : [
{
"id" : "250",
"serial" : "B",
"name" : "Eco",
"ticket" : [
{
"id" : "55927d41f41edfd00f000030",
"name" : "ZZZ",
"price" : "10 €"
},
{
"id" : "55927d41f41edfd00f000030",
"name" : "ZZZ",
"price" : "10 €"
}
]
},
{
"id" : "250",
"serial" : "B",
"name" : "Eco",
"ticket" : [
{
"id" : "55927d41f41edfd00f000030",
"name" : "ZZZ",
"price" : "10 €"
},
{
"id" : "55927d41f41edfd00f000030",
"name" : "ZZZ",
"price" : "10 €"
}
]
}
],
"full_amount" : "40",
},
{
"id" : "55929142f41edfdc0f00002f",
"name" : "XYZ",
"card" : [
{
"id" : "250",
"serial" : "B",
"name" : "Eco",
"ticket" : [
{
"id" : "55927d41f41edfd00f000030",
"name" : "ZZZ",
"price" : "10 €"
},
{
"id" : "55927d41f41edfd00f000030",
"name" : "ZZZ",
"price" : "10 €"
}
]
},
{
"id" : "250",
"serial" : "B",
"name" : "Eco",
"ticket" : [
{
"id" : "55927d41f41edfd00f000030",
"name" : "ZZZ",
"price" : "10 €"
},
{
"id" : "55927d41f41edfd00f000030",
"name" : "ZZZ",
"price" : "10 €"
}
]
}
],
"full_amount" : "40",
},
],
"rate" : "0.23",
"date" : "2015-07-02 13:04:34",
}
I've tried many combinations with unwinding, projecting and grouping and failed to get what I want. Can someone help me with this?
You probably shouldn't be using the aggregation framework for tasks like this that do not actually "aggregate" anything between documents. This really is a "projection" task since all you are asking is to "alter" the structure of a document, and that is a task probably better suited to coding in the client after the document is retrieved.
A very good reason for this is that operations like $unwind are very costly in terms of performance. What $unwind does is produce a "copy" of the document content for each array member present, which results in a lot more documents to process.
Think of that like a "SQL Join" with a "one to many" relationship, the only difference being the data is self contained in one document. Processing $unwind simulates the "join" results in that the "master" (one) document contents are reproduced for every "child" (many) document.
In order to counter such operations being done by people, MongoDB 2.6 introduced the $map operator, which processes array elements within the document itself.
So instead of doing multiple ( or any ) $unwind actions, you can instead just process the arrays within the document itself using $map in a $project stage:
db.collection.aggregate([
{ "$project": {
"orders": { "$map": {
"input": "$orders",
"as": "o",
"in": {
"id": "$$o.id",
"name": "$$o.name",
"card": { "$map": {
"input": "$$o.card",
"as": "c",
"in": {
"id": "$$c.id",
"serial": "$$c.serial",
"name": "$$c.name",
"ticket": { "$map": {
"input": "$$c.ticket",
"as": "t",
"in": {
"id": "$$t.id",
"name": "$$t.name",
"price": "$$t.price.price_disp"
}
}}
}
}},
"full_amount": "$$o.full_amount"
}
}},
"rate": 1,
"date": 1
}}
])
The operations are fairly simple there as each "array" is assigned it's own variable name, and for a simple projection operation such as this all that is really left is selecting which fields you want.
In earlier versions, processing using $unwind is much more difficult:
db.collection.aggregate([
{ "$unwind": "$orders" },
{ "$unwind": "$orders.card" },
{ "$unwind": "$orders.card.ticket" },
{ "$group": {
"_id": {
"_id": "$_id",
"orders": {
"id": "$orders.id",
"name": "$orders.name",
"card": {
"id": "$orders.card.id",
"serial": "$orders.card.serial",
"name": "$orders.card.name"
},
"full_amount": "$orders.full_amount"
},
"rate": "$rate",
"date": "$date"
},
"ticket": {
"$push": {
"id": "$orders.card.ticket.id",
"name": "$orders.card.ticket.name",
"price": "$orders.card.ticket.price.price_disp"
}
}
}},
{ "$group": {
"_id": {
"_id": "$_id._id",
"orders": {
"id": "$_id.orders.id",
"name": "$_id.orders.name",
"full_amount": "$_id.orders.full_amount"
},
"rate": "$_id.rate",
"date": "$_id.date"
},
"card": {
"$push": {
"id": "$_id.orders.card.id",
"serial": "$_id.orders.card.serial",
"name": "$_id.orders.card.name",
"ticket": "$ticket"
}
}
}},
{ "$group": {
"_id": "$_id._id",
"orders": {
"$push": {
"id": "$_id.orders.id",
"name": "$_id.orders.name",
"card": "$card",
"full_amount": "$_id.orders.full_amount"
}
},
"rate": { "$first": "$_id.rate" },
"date": { "$first": "$_id.date" }
}}
])
So following through that carefully, you should see that since you $unwind three times it is necessary to $group "three times" as well, while carefully grouping all the distinct values at each "level" and re-constructing the arrays via $push.
This really is not advised at all as was mentioned earlier:
You "are not grouping/aggregating anything" and each sub-document "must" contain a "unique" itentifier because of the "grouping" operations required to re-construct arrays. ( See: NOTE )
The $unwind operation here is very costly. All of the document information is re-produced by a factor of "n" array X "n" array elements and so on. So there is much more data in the aggregation pipeline than your collection or query selection actually contains in itself.
Therefore in conclusion, for the general processing of "reformatting your data" you should instead be processing each document in your code rather than be "throwing it" at the aggregation pipeline to do.
If your document data requires "sufficient" manipulation that makes a "substantial difference" to the returned result size that you deem to be more efficient than pulling the whole document and manipulating in the client, then and "only" then should you be using the $project form as shown with the $map operations.
Sidebar
Your original "tag" here mentions "PHP".
All MongoDB queries including the aggregation have nothing language specific about them and are just "data structures" and are represented as such mostly in the "native form" for those languages (PHP,JavaScript,python,etc), and with "builder methods" for those languages without "native" expressive formats for free structures ( C,C#,Java ).
In all cases, there are simple parsers available for JSON, which is a common "linqua franca" here as the MongoB Shell itself is JavaScript based and understands JSON structre ( as actual JavaScript Objects ) natively.
So when working with such examples use tools like:
json_decode: to get more of an insight into how your native data structure is constructed.
json_encode: in order to check your native data structure against any JSON represented sample.
All content here is just simple "key/value" array() notation, though nested. But it is probably good practice to be aware of the tools and use them regularly.
NOTE:
The data sample you give looks very much like you have "cut and paste" data in order to create multiple items, as various "sub-items" all share the same "id" values.
Your "real" data should not do this! So I hope it does not, but if so then fix it.
In order to make the second example workable ( first is perfectly fine as is ) the data needs to be altered to included "unique" "id" values for each sub-element.
As I used here:
{
"_id" : ObjectId("55951b2bf41edfc80b00002a"),
"orders" : [
{
"id" : "55929142f41edfdc0f00002a",
"name" : "XYZ",
"card" : [
{
"id" : "250",
"serial" : "B",
"name" : "Eco",
"ticket" : [
{
"id" : "55927d41f41edfd00f000031",
"name" : "ZZZ",
"price" : "10 €"
},
{
"id" : "55927d41f41edfd00f000032",
"name" : "ZZZ",
"price" : "10 €"
}
]
},
{
"id" : "251",
"serial" : "B",
"name" : "Eco",
"ticket" : [
{
"id" : "55927d41f41edfd00f000033",
"name" : "ZZZ",
"price" : "10 €"
},
{
"id" : "55927d41f41edfd00f000034",
"name" : "ZZZ",
"price" : "10 €"
}
]
}
],
"full_amount" : "40",
},
{
"id" : "55929142f41edfdc0f00002b",
"name" : "XYZ",
"card" : [
{
"id" : "252",
"serial" : "B",
"name" : "Eco",
"ticket" : [
{
"id" : "55927d41f41edfd00f000035",
"name" : "ZZZ",
"price" : "10 €"
},
{
"id" : "55927d41f41edfd00f000036",
"name" : "ZZZ",
"price" : "10 €"
}
]
},
{
"id" : "253",
"serial" : "B",
"name" : "Eco",
"ticket" : [
{
"id" : "55927d41f41edfd00f000037",
"name" : "ZZZ",
"price" : "10 €"
},
{
"id" : "55927d41f41edfd00f000038",
"name" : "ZZZ",
"price" : "10 €"
}
]
}
],
"full_amount" : "40",
}
],
"rate" : "0.23",
"date" : "2015-07-02 13:04:34",
}

Ask update and delete multiple array in MongoDb

I have a real case in my project:
> db.foo.insert({a:'1',
... province: [{id:'1',name:'Yogyakarta',state:[{id:'1',name:'bantul'}]}]
... })
Then I find()...
> db.foo.find();
> { "_id" : ObjectId("5279ef4c6cfd9d5c0e19bbe0"),
"a" : "1",
"province" : [
{"id" : "1",
"name" : "Yogyakarta",
"state" : [
{"id" : "1","name" : "bantul" }
]
}
]
}
how to remove and update state with id='1'
REMOVE
To remove the documents that match a deletion criteria, call the remove() method with the <query> parameter.
db.foo.remove({'province.state.id': '1'})
Example
First, insert data Yogyakarta - Bantul
db.foo.insert({a:'1', province: [{id:'1',name:'Yogyakarta',state:[{id:'1',name:'bantul'}]}] })
Insert data Jakarta - Jakarta Selatan
db.foo.insert({a:'1', province: [{id:'2',name:'Jakarta',state:[{id:'2',name:'Jakarta Selatan'}]}] })
Now, you have two documents
db.foo.find();
Result
[
{ "a" : "1", "_id" : { "$oid" : "527b54c6cc937439340367f9" }, "province" : [ { "name" : "Yogyakarta", "id" : "1", "state" : [ { "name" : "bantul", "id" : "1" } ] } ] },
{ "a" : "1", "_id" : { "$oid" : "527b54d3cc937439340367fa" }, "province" : [ { "name" : "Jakarta", "id" : "2", "state" : [ { "name" : "Jakarta Selatan", "id" : "2" } ] } ] }
]
Now, delete document where the subdocument province contains a field state whose value 1.
db.foo.remove({'province.state.id': '1'})
Check
db.foo.find();
Now, you have one document
[
{ "a" : "1", "_id" : { "$oid" : "527b54d3cc937439340367fa" }, "province" : [ { "name" : "Jakarta", "id" : "2", "state" : [ { "name" : "Jakarta Selatan", "id" : "2" } ] } ] }
]
UPDATE
By default, the update() method updates a single document. If the multi option is set to true, the method updates all documents that match the query criteria.
db.foo.update({'province.state.id': '2'}, { $set: {'a': '2'} })
Check
db.foo.find();
Result
[
{ "a" : "2", "_id" : { "$oid" : "527b54d3cc937439340367fa" }, "province" : [ { "name" : "Jakarta", "id" : "2", "state" : [ { "name" : "Jakarta Selatan", "id" : "2" } ] } ] }
]