How to find documents with duplicated attributes in Mongoose? - mongodb

I have the following documents stored in my MongoDB database (let's say collection products):
{
a: "test1",
c: "data1"
},
{
a: "test2",
c: "data2"
},
{
a: "test3",
c: "data1"
},
{
a: "test4",
c: "data3"
},
{
a: "test5",
c: "data1"
},
{
a: "test6",
c: "data3"
},
How can I query all documents that have attribute c duplicated (or triplicated...)? In the example data, the query should return test1, test3, test4, test5 and test6 documents.

You can do this by grouping on c and then getting the groups with more than one in the group:
db.test.aggregate([
{$group: {_id: '$c', count: {$sum: 1}, docs: {$push: '$$ROOT'}}},
{$match: {count: {$gt: 1}}}
])

Related

How to compare two objects in MongoDB (ignoring order of keys)?

What is the best way to find all the documents where objA is the same as objB (order of keys is not important)?
Inspired by another question by #Digvijay, I was looking for a way to compare two objects on MongoDB query and could not find a relevant solution on SO.
Sample data:
[
{
objA: {a: 1, b: 2},
objB: {a: 1, b: 2}
},
{
objA: {m: "g", c: 5},
objB: {c: 5, m: "g"}
},
{
objA: {m: "g", c: 7},
objB: {c: 5, m: "g"}
},
{
objA: {m: "g", c: 7},
objB: {b: "g", c: 7}
}
]
Expected results:
[
{
objA: {a: 1, b: 2},
objB: {a: 1, b: 2}
},
{
objA: {m: "g", c: 5},
objB: {c: 5, m: "g"}
},
]
You can do it like this:
$objectToArray - to transform objA and objB to arrays.
$setEquals - to compare if above arrays have the same distinct elements.
db.collection.aggregate([
{
$match: {
$expr: {
$setEquals: [
{ $objectToArray: "$objA" },
{ $objectToArray: "$objB" }
]
}
}
}
])
Working example
Maybe use the old-school trick of converting them into an array by $objectToArray. Use $sortArray to sort by key. Compare by the sorted array to get the matches.
db.collection.aggregate([
{
$addFields: {
"sortedA": {
$sortArray: {
input: {
"$objectToArray": "$objA"
},
sortBy: {
k: 1
}
}
},
"sortedB": {
$sortArray: {
input: {
"$objectToArray": "$objB"
},
sortBy: {
k: 1
}
}
}
}
},
{
"$match": {
$expr: {
$eq: [
"$sortedA",
"$sortedB"
]
}
}
},
{
"$unset": [
"sortedA",
"sortedB"
]
}
])
Mongo Playground

Add a total to aggregated sub-totals in MongoDB

Let's say I have documents in my MongoDB collection that look like this:
{ name: "X", ...}
{ name: "Y", ...}
{ name: "X", ...}
{ name: "X", ...}
I can create a pipeline view using aggregation that shows sub-totals i.e.
$group: {
_id: '$name',
count: {
$sum: 1
}
}
which results in:
{ _id: "X",
count: 3 },
{ _id: "Y",
count: 1}
but how do I add a total in this view i.e.
{ _id: "X",
count: 3 },
{ _id: "Y",
count: 1},
{_id: "ALL",
count: 4}
Query1
group to count
union with the same collection, with pipeline to add the total count, in one extra document
Test code here
coll1.aggregate(
[{"$group":{"_id":"$name", "count":{"$sum":1}}},
{"$unionWith":
{"coll":"coll1",
"pipeline":[{"$group":{"_id":"ALL", "count":{"$sum":1}}}]}}])
Query2
without $union for MongoDB < 4.4
group and count
group by null and collect the documents, and total count
add to docs array the extra document with the total count
unwind and replace root to restore the structure
Test code here
aggregate(
[{"$group":{"_id":"$name", "count":{"$sum":1}}},
{"$group":
{"_id":null, "docs":{"$push":"$$ROOT"}, "total":{"$sum":"$count"}}},
{"$project":
{"docs":
{"$concatArrays":["$docs", [{"_id":"ALL", "count":"$total"}]]}}},
{"$unwind":"$docs"},
{"$replaceRoot":{"newRoot":"$docs"}}])
Try this one:
db.collection.aggregate([
{
$group: {
_id: "$name",
count: { $count: {} }
}
},
{
$unionWith: {
coll: "collection",
pipeline: [
{
$group: {
_id: "ALL",
count: { $count: {} }
}
}
]
}
}
])
Mongo Playground

MongoDB: Find duplicate docs where a field has lowest values

so I have this problem
I have this duplicate collection that goes like:
{name: "a", otherField: 1, _id: "id1"},
{name: "a", otherField: 2, _id: "id2"},
{name: "a", otherField: 3, _id: "id3"},
{name: "b", otherField: 1, _id: "id4"}
{name: "b", otherField: 2, _id: "id5"}
My goal is to get id of with less otherField that will look like:
{"name": "a", _id: "id1"},
{"name": "a", _id: "id2"},
{"name": "b", _id: "id4"}
Since highest otherField from a and b is "id3" and "id5", I want id other than the highest otherField
How to achieve this through query in mongodb?
Thank you
You can try below query :
db.collection.aggregate([
/** group all docs based on name & push docs to data field & find max value for otherField field */
{
$group: {
_id: "$name",
data: {
$push: "$$ROOT"
},
maxOtherField: {
$max: "$otherField"
}
}
},
/** Recreate data field array with removing doc which has max otherField value */
{
$addFields: {
data: {
$filter: {
input: "$data",
cond: {
$ne: [
"$$this.otherField",
"$maxOtherField"
]
}
}
}
}
},
/** unwind data array */
{
$unwind: "$data"
},
/** Replace data field as new root for each doc in coll */
{
$replaceRoot: {
newRoot: "$data"
}
}
])
Test : MongoDB-Playground
Note : We might lean towards sorting docs on field otherField, but it's not preferable on huge datasets.

Mongodb How to find two documents in a collection that the values of a property are the same

The collection is like this:
{a: 1, c: true}, {a: 1, c: false}, {a: 1, c: false},
{a: 2, c: true}, {a: 2, c: false}, {a: 2, c: true}
Is there a query that can find two docs that has {c: true} and the value of a are the same? In the above example, it should return {a:2, c: true}, {a:2, c: true}.
You can try below aggregation
db.collection.aggregate([
{ "$match": { "c": true } },
{ "$group": {
"_id": { "a": "$a" },
"data": { "$push": "$$ROOT" }
}},
{ "$addFields": { "dataSize": { "$size": "$data" } } },
{ "$match": { "dataSize": 2 } },
{ "$unwind": "$data" },
{ "$replaceRoot": { "newRoot": "$data" } }
])

Can I easily return all of the fields of a subdocument as fields in the top level document using the aggregation framework?

I have a document similar to the following, from which I want to return the sub-fields of the current top level field as the top level fields in every document of the results array:
{
field1: {
subfield1: {},
subfield2: [],
subfield3: 44,
subfield5: xyz
},
field2: {
othercontent: {}
}
}
I want the results of my aggregation query to return the following (the contents of field1 as the top level document):
{
subfield1: {},
subfield2: [],
subfield3: 44,
subfield5: xyz
}
Can this be done with $project and the aggregation framework without defining every sub fields to return as a top level field?
You can use $replaceRoot aggregation operator since 3.4:
db.getCollection('sample').aggregate([
{
$replaceRoot: {newRoot: "$field1"}
}
])
Provides output as expected:
{
"subfield" : {},
"subfield2" : [],
"subfield3" : 44,
"subfield5" : "xyz"
}
It's generally hard to make MongoDB deal with ambiguous or parameterized json keys. I ran into a similar issue and the best solution was to modify the schema so that the members of the subdocument became elements in an array.
However, I think this will get you close to what you want (all code should run directly in the Mongo shell). Assuming you have documents like:
db.collection.insert({
"_id": "doc1",
"field1": {
"subfield1": {"key1": "value1"},
"subfield2": ["a", "b", "c"],
"subfield3": 1,
"subfield4": "a"
},
"field2": "other content"
})
db.collection.insert({
"_id": "doc2",
"field1": {
"subfield1": {"key2": "value2"},
"subfield2": [1, 2, 3],
"subfield3": 2,
"subfield4": "b"
},
"field2": "yet more content"
})
Then you can run an aggregation command that promotes the content of field1 while ignoring the rest of the document:
db.collection.aggregate({
"$group":{
"_id": "$_id",
"value": {"$push": "$field1"}
}})
This makes all the subfield* keys into top-level fields of an object, and that object is the only element in an array. It's clumsy, but workable:
"result" : [
{
"_id" : "doc2",
"value" : [
{
"subfield1" : {"key2" : "value2"},
"subfield2" : [1, 2, 3],
"subfield3" : 2,
"subfield4" : "b"
}
]
},
{
"_id" : "doc1",
"value" : [
{
"subfield1" : {"key1" : "value1"},
"subfield2" : ["a","b","c"],
"subfield3" : 1,
"subfield4" : "a"
}
]
}
],
"ok" : 1
Starting Mongo 4.2, the $replaceWith aggregation operator can be used to replace a document by another (in our case by a sub-document) as syntaxic sugar for $replaceRoot:
// { field1: { a: 1, b: 2, c: 3 }, field2: { d: 4, e: 5 } }
// { field1: { a: 6, b: 7 }, field2: { d: 8 } }
db.collection.aggregate({ $replaceWith: "$field1" })
// { a: 1, b: 2, c: 3 }
// { a: 6, b: 7 }