How to publish a mongodb array length as an additional collection field? - arrays

I have a mongodb collection with the following fields:
_id
name (string)
[items] (array of string)
secret (boolean)
I want to publish the all the _id, name fields and the item array length only (excluding the secret field) where the secret field is true.
I have read somewhere that I can add additional document properties in my find query, but my google foo does not work.
Here is what my publish method looks like without the additional items_count property:
Meteor.publish("all_items", function() {
return myItems.find(
{secret: true},
{fields:
{_id:1,name:1}
});
});
How can I create an additional field from the [item] length in my publication?
EDIT: it seems that I need to use an aggregate function and the $projectoperator. And it is not supported by meteor.
Can anyone confirm this to me (i.e. it is the only option and it is not supported)?

You can add aggregation framework support to Meteor, and then use a simple aggregation pipeline with $project stage as you mentioned, like to following:
myItems.aggregate(
[
{$match: {secret: true}},
{$project: {_id: 1, name: 1, items_count: {$size: '$items'}}}
]
)

Related

Saving conditions whether a user has done something in MongoDB: which field type should I use?

So I have this scenario where I want to save a condition whether if a user "has joined the website onboarding tour". So the question I want to ask is not how to save the field to MongoDB, but rather what field type should I use for it?
I want it to be reusable for another certain condition such as "has joined a campaign A", or "has visited special page B". Now I have 3 cases that you can look:
Case 1: Just a single boolean field for every case: One field for one condition, such as hasJoinedNewOnboarding: true or hasJoinedCampaignA: false. All I need to do is search by key: true.
Case 2: Use an array: One array for many conditions, such as ['hasJoinedNewOnboarding','hasJoinedCampaignA']. Let's say the field name is meta_data. All I need to do is search using $elemMatch like { meta_data: { $elemMatch: { 'hasJoinedNewOnboarding' } } }
Case 3: Use an object: One object for many conditions, such as Case 2: Use an array: One array for many conditions, such as { hasJoinedNewOnboarding: true, hasJoinedCampaignA: false }. Let's say the field name is meta_data. All I need to do is search it like { 'meta_data.hasJoinedNewOnboarding': true }
With that said, which one do you think is the best way to store the conditions in the database? Or do you have something in mind that is better than these 3 cases?
Thanks
There's not much difference when you want to query your data, it will always be either:
db.col.find({hasJoinedNewOnboarding: true})
or for the second approach:
db.col.find({arrayName: "hasJoinedNewOnboarding"})
Both ways are easy however I would recommend storing such events in an array because it's easier to aggregate the data when you don't need to refer to multiple key names in MongoDB,
For example, if you have a document like:
{
events: [
"hasJoinedNewOnboarding",
"hasJoinedCampaignA"
]
}
You can dynamically count how many users have done something by running following query:
db.collection.aggregate([
{
$unwind: "$events"
},
{
$group: {
_id: "$events",
count: { $sum: 1 }
}
}
])
Mongo Playground
Alternatively if you decide to use first or third approach the name of the event is represented by the name of the key in MongoDB's document so you can still easily count single event occurances but if you want to group all events dynamically you need to use $objectToArray operator which becomes more cumbersome.
So the recommended approach would be to keep them as an array of strings or an array of objects like:
{ events: [ { eventType: "NewOnboarding", date: ... } ] }

How can I perform a find in Mongo (meteor) that finds a document where an array matches? [duplicate]

This question already has answers here:
MongoDB Find Exact Array Match but order doesn't matter
(7 answers)
Closed 7 years ago.
I'm trying to return a Cursor in Meteor (using MongoDB).
I'm looking to find documents (MessageThreads collection) where a field of participants exactly matches an array I pass in.
Below is a sample MessageThread document
{
"_id": "YYSELCguhLurTeyNY",
"creator": "RLmnidY6GypdqDXZu",
"participants": [
"SNhRq4vQpwFBjnTSH",
"RLmnidY6GypdqDXZu"
],
}
When I perform an addMessage method, I'm trying to check first if a thread exists where participants exactly matches the array I pass in. This array will be formulated from the new message form tofield.
So, the documents should only be returned if all my array of participants are inside the documents participants field but no other. Eg: If a third person existed in the document who wasn't part of the new message to field then that document should not be returned.
Currently this is my query, which is obviously too simplistic.
existingThread = MessageThreads.findOne(participants: participants)
Any pointers? Thank you
EDIT: I'm having an issue using the provided duplicate answer (but not yet allowed to comment on that other thread)
For some reason existingThread is still finding a document if the array is different but the size is true.
EDIT 2:
Below is the code for my entire method in the event that it can help decipher where I am going wrong. In coffeescript (please excuse the tabbing, can't get it working in SO, sorry).
Meteor.methods
newMessage: (recipientIds, messageContent) ->
if !Meteor.userId()
return false
userId = Meteor.userId()
check recipientIds, [String]
check messageContent, String
participants = recipientIds
participants.push(userId)
participantCount = _.size(participants)
existingThread = MessageThreads.findOne participants:
$size: participantCount
$in: participants
if existingThread?
console.log "Thread exists: #{existingThread._id}"
MessageThreads.update existingThread,
$addToSet: messages: {sender: userId, content: messageContent}
$set: lastUpdated: new Date()
else
newThreadId = MessageThreads.insert
creator: userId
participants: participants
messages: [
{
sender: userId
content: messageContent
createdAt: new Date()
}
]
lastUpdated: new Date()
return newThreadId
You need the $all operator in your query which selects the documents where the value of a field is an array that contains all the specified elements. As you want to return a cursor, find() method is more appropriate since it returns a cursor. It does not immediately access the database or return documents. To access the documents in the cursor, cursors provide fetch() to return all matching documents, map() and forEach() to iterate over all matching documents, observe() and observeChanges() to register callbacks when the set of matching documents changes.
For your case, an example implementation would look something like this:
var existingThreadsCursor = MessageThreads.find({ "participants": { "$all": participants} });
var count = 0;
existingThreadsCursor.forEach(function (thread){
console.log("Thread with participants: " + JSON.stringify(thread.participants));
count++;
});

How to build a associative-array type O(1) field n mongodb schema

I have started on Mongo only recently, and am adapting myself to thinking document instead of a table based approach.
I have a Schema that looks like (Node.js):
var UserInfo = new mongoose.Schema({
id: Number,
name: String,
friends: [{
id: Number,
name: String,
}]
}, {
collection: 'userInfo'
});
The issue is that if I know my User's id and his friend's id too, I will have to iterate through the entire friends[] to find the friend. In other words, the entire operation is not O(1).
If I were storing this in a JS data structure, I will use an associative array for friends, some something like myuser.friends[his_friend_id] will give me a O(1) access to the friend. How do I achieve the same O(1) in Mongo?
I'm not familiar with mongoose, but if there are issues with creating the structure as you think it should be, then there is good reason for that. My current thinking is that the keys in a nosql schema should never be dynamic as it makes searching difficult, if not impossible.
eg with your current schema you would be able to a search for a user in the friends attribute with something like
userInfo.find({ 'friends.id' = userId })
You can also do
userInfo.find({ id: userId, 'friends.id' = friendId })
but I'm not sure that you could return only the individual friend's data from the search anyway.
If you never need to do similar, then it would appear that you must be storing the relationship in both user's documents, duplicating the data. In that case you might want to consider a collection which simply maps friends to friends, or only store the relationships initiated by a user in that user's friends list.
To answer the actual question, I don't think you want to change your schema, but rather go with loading the user and just doing a filter/first/someother type array search on the list of friends to find the match.
The entire operation of retrieving the user document and getting the friend will never be O(1), because lookups in MongoDB aren't O(1). But you can avoid searching the friends array for the friend. Use $ projection:
> db.test.drop()
> db.test.insert({ "_id" : 0, "friends" : [{ "_id" : 1 }, { "_id" : 2 }, { "_id" : 3 }] })
> db.test.find({ "_id" : 0, "friends._id" : 2 }, { "friends.$" : 1 })
{ "_id" : 0, "friends" : [ { "_id" : 2 } ] }
What I was looking for was a sub-document
Finding a sub-document
Each document has an _id. DocumentArrays have a special id method for
looking up a document by its _id.
var doc = parent.children.id(id);

MongoDB: Query and retrieve objects inside embedded array?

Let's say I have the following document schema in a collection called 'users':
{
name: 'John',
items: [ {}, {}, {}, ... ]
}
The 'items' array contains objects in the following format:
{
item_id: "1234",
name: "some item"
}
Each user can have multiple items embedded in the 'items' array.
Now, I want to be able to fetch an item by an item_id for a given user.
For example, I want to get the item with id "1234" that belong to the user with name "John".
Can I do this with mongoDB? I'd like to utilize its powerful array indexing, but I'm not sure if you can run queries on embedded arrays and return objects from the array instead of the document that contains it.
I know I can fetch users that have a certain item using {users.items.item_id: "1234"}. But I want to fetch the actual item from the array, not the user.
Alternatively, is there maybe a better way to organize this data so that I can easily get what I want? I'm still fairly new to mongodb.
Thanks for any help or advice you can provide.
The question is old, but the response has changed since the time. With MongoDB >= 2.2, you can do :
db.users.find( { name: "John"}, { items: { $elemMatch: { item_id: "1234" } } })
You will have :
{
name: "John",
items:
[
{
item_id: "1234",
name: "some item"
}
]
}
See Documentation of $elemMatch
There are a couple of things to note about this:
1) I find that the hardest thing for folks learning MongoDB is UN-learning the relational thinking that they're used to. Your data model looks to be the right one.
2) Normally, what you do with MongoDB is return the entire document into the client program, and then search for the portion of the document that you want on the client side using your client programming language.
In your example, you'd fetch the entire 'user' document and then iterate through the 'items[]' array on the client side.
3) If you want to return just the 'items[]' array, you can do so by using the 'Field Selection' syntax. See http://www.mongodb.org/display/DOCS/Querying#Querying-FieldSelection for details. Unfortunately, it will return the entire 'items[]' array, and not just one element of the array.
4) There is an existing Jira ticket to add this functionality: it is https://jira.mongodb.org/browse/SERVER-828 SERVER-828. It looks like it's been added to the latest 2.1 (development) branch: that means it will be available for production use when release 2.2 ships.
If this is an embedded array, then you can't retrieve its elements directly. The retrieved document will have form of a user (root document), although not all fields may be filled (depending on your query).
If you want to retrieve just that element, then you have to store it as a separate document in a separate collection. It will have one additional field, user_id (can be part of _id). Then it's trivial to do what you want.
A sample document might look like this:
{
_id: {user_id: ObjectId, item_id: "1234"},
name: "some item"
}
Note that this structure ensures uniqueness of item_id per user (I'm not sure you want this or not).

mongodb - retrieve array subset

what seemed a simple task, came to be a challenge for me.
I have the following mongodb structure:
{
(...)
"services": {
"TCP80": {
"data": [{
"status": 1,
"delay": 3.87,
"ts": 1308056460
},{
"status": 1,
"delay": 2.83,
"ts": 1308058080
},{
"status": 1,
"delay": 5.77,
"ts": 1308060720
}]
}
}}
Now, the following query returns whole document:
{ 'services.TCP80.data.ts':{$gt:1308067020} }
I wonder - is it possible for me to receive only those "data" array entries matching $gt criteria (kind of shrinked doc)?
I was considering MapReduce, but could not locate even a single example on how to pass external arguments (timestamp) to Map() function. (This feature was added in 1.1.4 https://jira.mongodb.org/browse/SERVER-401)
Also, there's always an alternative to write storedJs function, but since we speak of large quantities of data, db-locks can't be tolerated here.
Most likely I'll have to redesign the structure to something 1-level deep, like:
{
status:1,delay:3.87,ts:138056460,service:TCP80
},{
status:1,delay:2.83,ts:1308058080,service:TCP80
},{
status:1,delay:5.77,ts:1308060720,service:TCP80
}
but DB will grow dramatically, since "service" is only one of many options which will append each document.
please advice!
thanks in advance
In version 2.1 with the aggregation framework you are now able to do this:
1: db.test.aggregate(
2: {$match : {}},
3: {$unwind: "$services.TCP80.data"},
4: {$match: {"services.TCP80.data.ts": {$gte: 1308060720}}}
5: );
You can use a custom criteria in line 2 to filter the parent documents. If you don't want to filter them, just leave line 2 out.
This is not currently supported. By default you will always receive the whole document/array unless you use field restrictions or the $slice operator. Currently these tools do not allow filtering the array elements based on the search criteria.
You should watch this request for a way to do this: https://jira.mongodb.org/browse/SERVER-828
I'm attempting to do something similar. I tried your suggestion of using the GROUP function, but I couldn't keep the embedded documents separate or was doing something incorrectly.
I needed to pull/get a subset of embedded documents by ID. Here's how I did it using Map/Reduce:
db.parent.mapReduce(
function(parent_id, child_ids){
if(this._id == parent_id)
emit(this._id, {children: this.children, ids: child_ids})
},
function(key, values){
var toReturn = [];
values[0].children.forEach(function(child){
if(values[0].ids.indexOf(product._id.toString()) != -1)
toReturn.push(child);
});
return {children: toReturn};
},
{
mapparams: [
"4d93b112c68c993eae000001", //example parent id
["4d97963ec68c99528d000007", "4debbfd5c68c991bba000014"] //example embedded children ids
]
}
).find()
I've abstracted my collection name to 'parent' and it's embedded documents to 'children'. I pass in two parameters: The parent document ID and an array of the embedded document IDs that I want to retrieve from the parent. Those parameters are passed in as the third parameter to the mapReduce function.
In the map function I find the parent document in the collection (which I'm pretty sure uses the _id index) and emit its id and children to the reduce function.
In the reduce function, I take the passed in document and loop through each of the children, collecting the ones with the desired ID. Looping through all the children is not ideal, but I don't know of another way to find by ID on an embedded document.
I also assume in the reduce function that there is only one document emitted since I'm searching by ID. If you expect more than one parent_id to match, than you will have to loop through the values array in the reduce function.
I hope this helps someone out there, as I googled everywhere with no results. Hopefully we'll see a built in feature soon from MongoDB, but until then I have to use this.
Fadi, as for "keeping embedded documents separate" - group should handle this with no issues
function getServiceData(collection, criteria) {
var res=db[collection].group({
cond: criteria,
initial: {vals:[],globalVar:0},
reduce: function(doc, out) {
if (out.globalVar%2==0)
out.vals.push({doc.whatever.kind.and.depth);
out.globalVar++;
},
finalize: function(out) {
if (vals.length==0)
out.vals='sorry, no data';
return out.vals;
}
});
return res[0];
};

Resources