This question already has answers here:
Retrieve only the queried element in an object array in MongoDB collection
(18 answers)
Closed 4 years ago.
I have mongo document like this
{
"_id": "5b14679e592baa493e0bc208",
"productCode": "ABC",
"corridors": [
{
"countryNameEn": "Sweden",
"countryNameFr": "Suède",
"countryCode": "SE",
"currencyNameEn": "Swedish Krona",
"currencyNameFr": "Couronne suédoise",
"currencyCode": "SEK",
"corridorLimit": "abc"
},
{
"countryNameEn": "USA",
"countryNameFr": "Suède",
"countryCode": "US",
"currencyNameEn": "USA",
"currencyNameFr": "Couronne suédoise",
"currencyCode": "USD",
"corridorLimit": "abc"
}
]
},
{
"_id": "5b14679e592baa493e0bc208",
"productCode": "XYZ",
"corridors": [
{
"countryNameEn": "Sweden",
"countryNameFr": "Suède",
"countryCode": "SE",
"currencyNameEn": "Swedish Krona",
"currencyNameFr": "Couronne suédoise",
"currencyCode": "SEK",
"corridorLimit": "abc"
},
{
"countryNameEn": "USA",
"countryNameFr": "Suède",
"countryCode": "US",
"currencyNameEn": "USA",
"currencyNameFr": "Couronne suédoise",
"currencyCode": "USD",
"corridorLimit": "abc"
}
]
}
I want to find document whose productCode is ABC and currencyCode is USD in corridors array. How can I return only the matching object from corridors array like only only object that has USD currencyCode should come in result and not all array.
I tried running this query { productCode: 'ABC', corridors: { $elemMatch: { currencyCode: "USD"}}}. But it gives me the whole array and not the only matched element.
I don't think you can use $elemMatch in $project. Check this
Try the following query:
db.collection.aggregate([
{
$match : {"productCode" : "ABC"}
},
{
$unwind : "$corridors"
},
{
$match : { "corridors.currencyCode" : "USD"}
},
{
$group : {
_id : "$productCode",
corridors : {$addToSet : "$corridors"}
}
}]);
Outputs:
{
"_id" : "ABC",
"corridors" : [
{
"countryNameEn" : "USA",
"countryNameFr" : "Suède",
"countryCode" : "US",
"currencyNameEn" : "USA",
"currencyNameFr" : "Couronne suédoise",
"currencyCode" : "USD",
"corridorLimit" : "abc"
}
]
}
In the result you'll have _id instead of productCode. If you still want productCode, you can just include $project in the end.
Hope this helps!
Related
i have following bson data in mongoDB
{name : "c1"
arr : [
{
partyName : "p1",
poNumber : "789",
},
{
partyName : "p1",
poNumber : "700",
},
{
partyName : "p3",
poNumber : "889",
}
]
},
{name : "c2"
arr : [
{
partyName : "p1",
poNumber : "789",
},
{
partyName : "p2",
poNumber : "700",
},
{
partyName : "p3",
poNumber : "889",
}
]
}
i want all unique values of partyName of name: "c1" object like [p1,p3]. i tried this
const unique = await User.distinct({name :"c1","createPurchaseOrder.partyName"})
(note :- User is my schema name )
but it gives error, i tried to search on web but cant find solution please help me with this
One option is using $reduce:
db.collection.aggregate([
{$match: {name: "c1"}},
{$project: {
res: {
$reduce: {
input: "$arr",
initialValue: [],
in: {$setUnion: ["$$value", ["$$this.partyName"]]}
}
}
}
}
])
See how it works on the playground example
Query1
match for the document with name=c1
path to take the array with the partyName and union with the empty array to remove the duplicates
union => order can be lost in patyNames
Playmongo
aggregate(
[{"$match": {"name": {"$eq": "c1"}}},
{"$project":
{"_id": 0, "parties-unique": {"$setUnion": ["$arr.partyName", []]}}}])
Query2
the above is fast and simple but loses the order of the names
if you want to keep the original order, you can use this instead
reduce and add on array only if element doesn't already exists
Playmongo
aggregate(
[{"$match": {"name": {"$eq": "c1"}}},
{"$set":
{"parties-unique":
{"$reduce":
{"input": "$arr.partyName",
"initialValue": [],
"in":
{"$cond":
[{"$in": ["$$this", "$$value"]}, "$$value",
{"$concatArrays": ["$$value", ["$$this"]]}]}}}}}])
I have following bson data in mongoDB
{
name : "company 1",
createPurchaseOrder :[
{
partyName : "p1",
poNumber : "789",
},
{
partyName : "p2",
poNumber : "700",
},
{
partyName : "p3",
poNumber : "889",
},
{
partyName : "p1",
poNumber : "800",
},
{
partyName : "p1",
poNumber : "200",
},
]
}
I want objects from partyName for example if partyName is p1 then I want this
[
{
partyName: 'p1',
poNumber: '789',
},
{
partyName: 'p1',
poNumber: '800',
},
{
partyName: 'p1',
poNumber: '200',
},
];
I tried this
const user1 = await User.findOne({name : "company 1"},{createPurchaseOrder:{$elemMatch:{partyName:"p1"}}})
its gives me only first matched object, then I tried this
const user1 = await User.find({name : "company 1"},{createPurchaseOrder:{$elemMatch:{"partyName.$":"p1"}}})
it returned an empty array. please help me with this I want solution which gives me all objects which have matched value
You need to use the aggregation pipeline for this, the find operator is very limited with what structure transformation it can do and for example this use case can't be achieved using it.
Just use $filter instead:
db.collection.aggregate([
{
$match: {
name: "company 1"
}
},
{
$addFields: {
createPurchaseOrder: {
$filter: {
input: {
$ifNull: [
"$createPurchaseOrder",
[]
]
},
cond: {
$eq: [
"$$this.partyName",
"p1"
]
}
}
}
}
},
{
$unwind: "$createPurchaseOrder"
},
{
$replaceRoot: {
newRoot: "$createPurchaseOrder"
}
}
])
Mongo Playground
I want to use Jolt to transform a JSON dataset. The problem is that my entire dataset is treated like an array because it is originally transformed from XML. Here is an example of the first 3 records:
{
"XMLSOCCER.COM" : { "Team" :[{
"Team_Id" : "45",
"Name" : "Aberdeen",
"Country" : "Scotland",
"Stadium" : "Pittodrie Stadium",
"HomePageURL" : "http://www.afc.co.uk",
"WIKILink" : "http://en.wikipedia.org/wiki/Aberdeen_F.C.",
"Capacity" : "20866",
"Manager" : "Derek McInnes"
},{
"Team_Id" : "46",
"Name" : "St Johnstone",
"Country" : "Scotland",
"Stadium" : "McDiarmid Park",
"HomePageURL" : "http://www.perthstjohnstonefc.co.uk",
"WIKILink" : "http://en.wikipedia.org/wiki/St._Johnstone_F.C."
},{
"Team_Id" : "47",
"Name" : "Motherwell",
"Country" : "Scotland",
"Stadium" : "Fir Park Stadium",
"HomePageURL" : "http://www.motherwellfc.co.uk",
"WIKILink" : "http://en.wikipedia.org/wiki/Motherwell_F.C."
}}]}}
For a single record-set, I can use this spec which gives me the correct output:
[
{
"operation": "shift",
"spec": {
"XMLSOCCER.COM": {
"Team": {
"Team_Id": "Team_Id",
"Name": "Name",
"Country": "Country",
"Stadium": "Stadium",
"Capacity": "Capacity",
"Manager": "Manager"
}
}
}}]
But because my entire dataset is treated as a JSON array (an array under "Team"), I cannot figure out how to create the spec to work with this configuration. I appreciate any input. thanks!
Spec: Match into all the elements of the Team array, and then reference the element number of the team array for each key in the output.
[
{
"operation": "shift",
"spec": {
"XMLSOCCER.COM": {
"Team": {
"*": {
"Team_Id": "soccer[&1].Team_Id",
"Name": "soccer[&1].Name",
"Country": "soccer[&1].Country",
"Stadium": "soccer[&1].Stadium",
"Capacity": "soccer[&1].Capacity",
"Manager": "soccer[&1].Manager"
}
}
}
}
}
]
Here is an example of a nested document that I have in my collection:
{
"title" : "front-end developer",
"age" : 25,
"name" : "John",
"city" : "London",
"skills" : [
{
"name" : "js",
"project" : "1",
"scores" : [
{
max: 76,
date: date
},
{
max: 56,
date: date
}
]
},
{
"name" : "CSS",
"project" : "5",
"scores" : [
{
max: 86,
date: date
},
{
max: 36,
date: date
},
{
max: 56,
date: date
},
]
}
]
}
Is there a simple way of determining whether other documents have an identical/duplicate structure to the skills array only? e.g. has the same keys, value and array indexes? Any help would be greatly appreciated. Thanks!
Here's how you get that:
collection.aggregate({
"$group": {
"_id": "$skills",
"docs": {
"$push": "$$ROOT"
},
"count": {
$sum: 1
}
}
}, {
$match: {
"count": {
$gt: 1
}
}
})
If you are looking for developers with the same skillset, you can use the $all operator:
var john = db.developers.findOne(...);
var devs = db.developers.find({ 'skills.name': { $all: john.skills.map(x => x.name) } });
So I have some query to database (mongodb) which will order results by value field.
all := EValues{}
err := con.Find(bson.M{"name": "somename}).Sort("-value").All(&all)
Json output for this looks like:
"values": [
{
"user_name": "guest7485",
"value": 8911,
"value_date": "2016-03-09T14:40:34.512Z"
},
{
"user_name": "guest7485",
"value": 539,
"value_date": "2016-03-07T14:11:05.217Z"
},
{
"user_name": "guest7485",
"value": 221,
"value_date": "2016-03-07T14:11:08.853Z"
},
{
"user_name": "guest7485",
"value": 77,
"value_date": "2016-03-07T14:11:12.377Z"
}
]
In my json response I need to add parameter "position" which should be basically equal to 1 - first result, 2 - second result and so on, for all results. So my final output should be:
"values": [
{
"position": 1,
"user_name": "guest7485",
"value": 8911,
"value_date": "2016-03-09T14:40:34.512Z"
},
{
"position": 2,
"user_name": "guest7485",
"value": 539,
"value_date": "2016-03-07T14:11:05.217Z"
},
{
"position": 3,
"user_name": "guest7485",
"value": 221,
"value_date": "2016-03-07T14:11:08.853Z"
},
{
"position": 4,
"user_name": "guest7485",
"value": 77,
"value_date": "2016-03-07T14:11:12.377Z"
}
]
I'm wondering how to solve this with mgo and go in general, and I would be really greatfull if someone can give me the most efficient way to solve this.
Update:
Definition of Evalues is bellow:
type EValue struct {
ID bson.ObjectId `json:"-" bson:"_id,omitempty"`
Name string `json:"-" bson:"name"`
UserId bson.ObjectId `json:"-" bson:"userId"`
UserName string `json:"user_name" bson:"userName"`
Value int64 `json:"value" bson:"value"`
AddedTime time.Time `json:"value_date" bson:"addedTime"`
}
type EValues []EValue
Add a position field to EValue:
type EValue struct {
... other fields here
Position int `json:"position" bson:"-"`
}
Loop through db results and set the field:
for i := range all {
all[i].Position = i + 1
}
Marshal the result as JSON.
With MongDB 3.2 this can be done using the $unwind operator where you can pass an object with the field path and the field includeArrayIndex which will hold the array index:
pipeline = [
{ "$match": {"name": "somename"} },
{ "$unwind": { "path": "$values", "includeArrayIndex": "position" } },
{
"$project": {
"name": 1,
"newarray.position": "$position",
"newarray.user_name": "$values.user_name",
"newarray.value_date": "$values.value_date",
"newarray.value": "$values.value",
}
},
{
"$group": {
"_id": "$name",
"values": { "$push": "$newarray" }
}
}
]
db.test.aggregate(pipeline);
Output
> db.test.aggregate(pipeline).pretty();
{
"_id" : "somename",
"values" : [
{
"position" : NumberLong(0),
"user_name" : "guest8911",
"value_date" : "2016-03-09T14:40:34.512Z",
"value" : 8911
},
{
"position" : NumberLong(1),
"user_name" : "guest7485",
"value_date" : "2016-03-07T14:11:05.217Z",
"value" : 539
},
{
"position" : NumberLong(2),
"user_name" : "guest7485",
"value_date" : "2016-03-07T14:11:08.853Z",
"value" : 221
},
{
"position" : NumberLong(3),
"user_name" : "guest7485",
"value_date" : "2016-03-07T14:11:12.377Z",
"value" : 77
}
]
}
>
If this is not supported with the mgo driver, then a not so efficient approach would be to use Map-Reduce for this. The following mongo shell example demonstrates how you can run the operation:
Populate test collection:
db.test.insert({
"name": "somename",
"values": [
{
"user_name": "guest8911",
"value": 8911,
"value_date": "2016-03-09T14:40:34.512Z"
},
{
"user_name": "guest7485",
"value": 539,
"value_date": "2016-03-07T14:11:05.217Z"
},
{
"user_name": "guest7485",
"value": 221,
"value_date": "2016-03-07T14:11:08.853Z"
},
{
"user_name": "guest7485",
"value": 77,
"value_date": "2016-03-07T14:11:12.377Z"
}
]
})
Run the following map-reduce operation:
> mr = db.runCommand({
"mapreduce": "test",
"map": function() {
var arr = []
for(var i=0; i < this.values.length; i++){
var val = this.values[i];
val["position"] = i+1;
arr.push(val);
}
emit(this._id, arr);
},
"reduce" : function() {},
"out": "test_keys"
})
Query resulting collection:
> db[mr.result].find().pretty()
{
"_id" : ObjectId("56e18ab84b9018ec86d2a6bd"),
"value" : [
{
"user_name" : "guest8911",
"value" : 8911,
"value_date" : "2016-03-09T14:40:34.512Z",
"position" : 1
},
{
"user_name" : "guest7485",
"value" : 539,
"value_date" : "2016-03-07T14:11:05.217Z",
"position" : 2
},
{
"user_name" : "guest7485",
"value" : 221,
"value_date" : "2016-03-07T14:11:08.853Z",
"position" : 3
},
{
"user_name" : "guest7485",
"value" : 77,
"value_date" : "2016-03-07T14:11:12.377Z",
"position" : 4
}
]
}
>
Now given the listing above, you can assemble your query in mgo using MapReduce
job := mgo.MapReduce{
Map: "function(){var arr=[];for(var i=0;i<this.values.length; i++){var val=this.values[i];val['position']=i+1;arr.push(val);};emit(this._id,arr);}",
Reduce: "function() { }",
}
var result []struct { Id int "_id"; Value []EValue }
_, err := collection.Find(nil).MapReduce(job, &result)
if err != nil {
panic(err)
}
for _, item := range result {
fmt.Println(item.Value)
}
For more details, check the documentation: https://godoc.org/labix.org/v1/mgo#MapReduce: