I'm trying to update a certain field in my mongodb collection 'users'
Example of a document:
{
_id: ObjectId("536d4d3307221f847fbbea49"),
groups: [
{
match_id: "dc3acf1b",
groupName: "World Cup, Group A",
score:[0,1]
},
{
match_id: "d4f14940",
groupName: "World Cup, Group A",
score:[2,1]
}],
name:"Foo Bar",
points:10
}
I'm trying to update only score fields that have a certain score..
here is what I want (position operator) :
db.users.update({'groups.match_id':i_match , 'groups.score':i_score},
{$inc:{points:1}});
Now I figure this would look for a user that has both those fields, but I want to look in the same groups object that had the first part of the query.
As in:
db.users.update({'groups.match_id':d4f14940 , 'groups.score':[2,1]},
{$inc:{points:1}});
should give the user +1 points
But:
db.users.update({'groups.match_id':dc3acf1b , 'groups.score':[2,1]},
{$inc:{points:1}});
Should not
You get my drift :)
Now I know from the positional operator that I should use the '$' sign, but I think that can only be used in the second paramater of the update() - the updating part..and not the finding part
note - The actual comparison of the score in this way (arrays) might not work..but that's not the issue here
What you want is the $elemMatch operator. This allows you to select array elements that require multiple fields to match a condition:
db.users.update(
{ "groups": {
"$elemMatch": {
"match_id": "d4f14940",
"score": [2,1]
}
}},
{ "$inc": { "points": 1 } }
)
So that allows you to match the document as the "groups" array contains an element that has both match_id and score that meet the conditions you supplied. If you change that match_id without altering the score, then the document does not match and the increment is not made to your points value.
So this has nothing to do with the positional $ operator in this case as you are not updating and array at a matched position, which is what that is for.
Related
I am trying to update items in an array with unique ObjectIds (meaning add an object ID to array object that are missing them)
If I have an array of shirt objects in my collection and I try this:
db.people.update({
$and : [
_id: ObjectId('5eeb44c6a042791d28a8641f'),
{
$or: [
{ 'shirts._id': { $eq:null } },
{ 'shirts._id':{ $exists:false } }
]
}
]
},{
$set: { 'shirts.$[]._id': new ObjectId() }
},{
"multi" : true
}
);
It generates IDENTICAL ObjectsIDs for each array element, I would put an unique index on this however, the use case probably wont see more then 2-3 items in the array with edge cases hitting 5-6, which seems like an abuse of an index
How can I update multiple records or multiple array objects with a unique ObjectId?
When you use $set you're telling mongo to set that value to all matching elements. If the elements in the array are already defined as schemas, mongo will issue new ObjectIds for each one of them automatically.
Alternatively, you can use forEach and iterate over each matching element creating a new ObjectId.
My data has several nested levels: root --> blocks --> children -->
"array of strings"
I want to use aggregate with $unwind to output some fields from the
"blocks" level and the lowest level, "array of strings".
My question: Based on the data diagram below, what is the code for using aggregate with $unwind to output these fields?
Fields from BLOCKS
block_id
block_type
definition
All entries from the lowest level array of strings
This is different than other postings I've found because I want some levels while skipping others and also because the lower two levels don't have names.
Duplicate rows in the output are not preferred, but I can dedupe at a later stage.
I uploaded an image of the data structure. If you can see the image, there's a blue box surrounding the fields I want.
Using "MongoDB Compass", I can see this structure
ROOT ARRAY
_id
edited_on
-->BLOCKS ARRAY (array of type object)
block_id
block_type
definition
edit_info (object)
edited_on
fields (object)
display_name
-->CHILDREN ARRAY (array of type array)
-->ARRAY (array of type string)
0: string
1: string
The first two arrays have names "blocks" and "children". The last two arrays don't have names.
This may help. I already have a query with $unwind that gives me some fields from the root & blocks levels.
mongo.exe MyDatabase --quiet --eval "printjson(db.modulestore.structures.aggregate( { $unwind: '$blocks' }, { $project: { _id: 1 , edited_on: 1 , 'definition': '$blocks.definition' , 'block_type': '$blocks.block_type' , 'block_id': '$blocks.block_id' , 'block_edited_on': '$blocks.edit_info.edited_on' , 'display_name': '$blocks.fields.display_name' } } , { $match: { block_type: 'openassessment' } } ).toArray())" > C:\Data\MyOutput.json
Doesn't clear your expectation , I guessed this may be help to you
`db.modulestore.structures.aggregate([
{
"$unwind": "$blocks"
},
{
"$unwind": "$fields.children"
},
{
"$project": {
"_id": 1,
"edited_on": 1,
"definition": "$blocks.definition",
"block_type": "$blocks.block_type",
"block_id": "$blocks.block_id",
"block_edited_on": "$blocks.edit_info.edited_on",
"display_name": "$blocks.fields.display_name"
}
},
{
"$match": {
"block_type": "openassessment"
}
}
]` )
I need help with filtering reactively minimongo collection with 3+ parameters. Firs, I have loaded minimongo from server's mongo collection with publish/subscribe. Now, I want to filter that collection and let user see only objects from collection that are same like filters. First of all I have search query filter that checks if input string is same as some filed in collection:
Job.find({ $or: [ { Name: regex }, { Description: regex }, ]});
This part is doing his job well. And now second filter: I have field in object that is true if that particular job is remote friendly and false if it is not, I wanted to if user enables that filter he only see just remote friendly job positions, and if he disables that, he can see all jobs available ( + search query of course):
if (remoteQuery === true ) {
return Job.find({ $or: [ { Name: regex }, { Description: regex }, ] , Remote: true});
}
This is working, but it is bad way for doing this. And now biggest problem comes with last filter: I have one more field that is storing "job" (collection object) type. Type can be 1, 2 , 3 or 4. So how could I say to minimongo e.g. "Show only jobs that have "Front-end" inside (search query), that are remote friendly, and have third filter 2 and 3 inside"
Job.find({ $or: [ { Name: "Front-end"}, { Description: "Front-end"}, ] , Remote: true, positionType: [2,3],});
Something like this? Thanks!
Sounds like you are looking for the MongoDB query $in operator:
The $in operator selects the documents where the value of a field equals any value in the specified array.
Therefore your 3rd query could look like:
Job.find({
positionType: {
$in: [2, 3] // Matches documents where `positionType` is either 2 or 3
},
// Other conditions…
});
I have the data like below:
{
info:{
id:0000,
name:"Iot name",
vendor:"some vendor",
},
location:[
{
lat:0,
lng:0,
status:3,
locID:"uniqueID0"
},{
lat:1,
lng:1,
status:0,
locID:"uniqueID1"
},{
lat:2,
lng:2,
status:1,
locID:"uniqueID2"
}
]}
Need example like findAndModify or something else similar, to find
in the iots collection, location with a unique ID and change his status.
e.g Find in collection iot, element with id=0000 and location with locID="uniqueID1" and set status for that location into 2
Thanks in advance
Apply the $set operator together with the $ positional operator in your update to change set the status field for that location to 2. The $ positional operator will identify the correct element in the array to update without explicitly specifying the position of the element in the array, thus your final update statement should look like:
db.iost.update(
{
"info.id": 0000,
"location.locID": "uniqueID1"
},
{
"$set": {
"location.$.status": 2
}
}
)
In the MongoDB aggregation framework, I was hoping to use the $unwind operator on an object (ie. a JSON collection). Doesn't look like this is possible, is there a workaround? Are there plans to implement this?
For example, take the article collection from the aggregation documentation . Suppose there is an additional field "ratings" that is a map from user -> rating. Could you calculate the average rating for each user?
Other than this, I'm quite pleased with the aggregation framework.
Update: here's a simplified version of my JSON collection per request. I'm storing genomic data. I can't really make genotypes an array, because the most common lookup is to get the genotype for a random person.
variants: [
{
name: 'variant1',
genotypes: {
person1: 2,
person2: 5,
person3: 7,
}
},
{
name: 'variant2',
genotypes: {
person1: 3,
person2: 3,
person3: 2,
}
}
]
It is not possible to do the type of computation you are describing with the aggregation framework - and it's not because there is no $unwind method for non-arrays. Even if the person:value objects were documents in an array, $unwind would not help.
The "group by" functionality (whether in MongoDB or in any relational database) is done on the value of a field or column. We group by value of field and sum/average/etc based on the value of another field.
Simple example is a variant of what you suggest, ratings field added to the example article collection, but not as a map from user to rating but as an array like this:
{ title : title of article", ...
ratings: [
{ voter: "user1", score: 5 },
{ voter: "user2", score: 8 },
{ voter: "user3", score: 7 }
]
}
Now you can aggregate this with:
[ {$unwind: "$ratings"},
{$group : {_id : "$ratings.voter", averageScore: {$avg:"$ratings.score"} } }
]
But this example structured as you describe it would look like this:
{ title : title of article", ...
ratings: {
user1: 5,
user2: 8,
user3: 7
}
}
or even this:
{ title : title of article", ...
ratings: [
{ user1: 5 },
{ user2: 8 },
{ user3: 7 }
]
}
Even if you could $unwind this, there is nothing to aggregate on here. Unless you know the complete list of all possible keys (users) you cannot do much with this. [*]
An analogous relational DB schema to what you have would be:
CREATE TABLE T (
user1: integer,
user2: integer,
user3: integer
...
);
That's not what would be done, instead we would do this:
CREATE TABLE T (
username: varchar(32),
score: integer
);
and now we aggregate using SQL:
select username, avg(score) from T group by username;
There is an enhancement request for MongoDB that may allow you to do this in the aggregation framework in the future - the ability to project values to keys to vice versa. Meanwhile, there is always map/reduce.
[*] There is a complicated way to do this if you know all unique keys (you can find all unique keys with a method similar to this) but if you know all the keys you may as well just run a sequence of queries of the form db.articles.find({"ratings.user1":{$exists:true}},{_id:0,"ratings.user1":1}) for each userX which will return all their ratings and you can sum and average them simply enough rather than do a very complex projection the aggregation framework would require.
Since 3.4.4, you can transform object to array using $objectToArray
See:
https://docs.mongodb.com/manual/reference/operator/aggregation/objectToArray/
This is an old question, but I've run across a tidbit of information through trial and error that people may find useful.
It's actually possible to unwind on a dummy value by fooling the parser this way:
db.Opportunity.aggregate(
{ $project: {
Field1: 1, Field2: 1, Field3: 1,
DummyUnwindField: { $ifNull: [null, [1.0]] }
}
},
{ $unwind: "$DummyUnwindField" }
);
This will produce 1 row per document, regardless of whether or not the value exists. You may be able tinker with this to generate the results you want. I had hoped to combine this with multiple $unwinds to (sort of like emit() in map/reduce), but alas, the last $unwind wins or they combine as an intersection rather than union which makes it impossible to achieve the results I was looking for. I am sadly disappointed with the aggregate framework functionality as it doesn't fit the one use case I was hoping to use it for (and seems strangely like a lot of the questions on StackOverflow in this area are asking) - ordering results based on match rate. Improving the poor map reduce performance would have made this entire feature unnecessary.
This is what I found & extended.
Lets create experimental database in mongo
db.copyDatabase('livedb' , 'experimentdb')
Now Use experimentdb & convert Array to object in your experimentcollection
db.getCollection('experimentcollection').find({}).forEach(function(e){
if(e.store){
e.ratings = [e.ratings]; //Objects name to be converted to array eg:ratings
db.experimentcollection.save(e);
}
})
Some nerdy js code to convert json to flat object
var flatArray = [];
var data = db.experimentcollection.find().toArray();
for (var index = 0; index < data.length; index++) {
var flatObject = {};
for (var prop in data[index]) {
var value = data[index][prop];
if (Array.isArray(value) && prop === 'ratings') {
for (var i = 0; i < value.length; i++) {
for (var inProp in value[i]) {
flatObject[inProp] = value[i][inProp];
}
}
}else{
flatObject[prop] = value;
}
}
flatArray.push(flatObject);
}
printjson(flatArray);