CouchDB - Basic design for joins data - couchdb

Just playing around with CouchDb and CouchApp, what amazing tech ! Very surprised, seams to be very powerful. After playing and reading a lot, as I'm a old relational database user, I still questioning myself about how to design some basic things.
Here is my question :
1/ I have a document of type 'user' and document of type 'item'
2/ My Couchdb contains the following documents :
{ "_id": "...", "type": "user", "email":"u1#gmail.com" ... }
{ "_id": "...", "type": "user", "email":"u2#gmail.com" ... }
{ "_id": "...", "type": "user", "email":"u3#gmail.com" ... }
{ "_id": "...", "type": "user", "email":"u4#gmail.com" ... }
{ "_id": "...", "type": "item", "title":"My title",
created_by:"u1#gmail.com", modified_by:"u3#gmail.com" }
3/ Now I want a view or something to fetch document by type=item and _id with informations for each users (creator & modifier)
I have seen a way to emulate a simple join here : http://www.cmlenz.net/archives/2007/10/couchdb-joins
But I can't adapt it for two joins, I'am playing around with key format since few hours, testing lots things, but nothing works.
I think, I'm missing something important with CouchDb map/reduce, if someone has some help I will appreciate it.
PS : Don't answer me to insert 'user' document inside 'item' document. This not my question.
Relax, relax ... :-)

I think you should put _ids in created_by and modified_by:
{ "_id": "u1", "type": "user", "email":"u1#gmail.com" ... }
{ "_id": "u2", "type": "user", "email":"u2#gmail.com" ... }
{ "_id": "u3", "type": "user", "email":"u3#gmail.com" ... }
{ "_id": "u4", "type": "user", "email":"u4#gmail.com" ... }
{ "_id": "anitem", "type": "item", "title":"My title",
created_by:"u1", modified_by:"u3" }
so you can use the following map function and query it with ?key="anitem"&include_docs=true:
function(doc) {
if (doc.type === "item") {
emit(doc._id, 1);
emit(doc._id, { _id: doc.created_by });
emit(doc._id, { _id: doc.modified_by });
}
}
You can read Jan Lehnardt's post about it for more details.
As a side note, I generally put the type in the _id so it is easier to get unique keys, and you do not need a view if you only want to filter by type:
{ "_id": "user/username1", "email":"u1#gmail.com" ... }
{ "_id": "user/username2", "email":"u2#gmail.com" ... }
{ "_id": "user/username3", "email":"u3#gmail.com" ... }
{ "_id": "user/username4", "email":"u4#gmail.com" ... }
{ "_id": "item/itemid1", "title":"My title",
created_by:"user/username1", modified_by:"user/username3" }
and the map function is
function(doc) {
if (doc._id.slice(0, 4) === "item/") {
emit(doc._id, 1);
emit(doc._id, { _id: doc.created_by });
emit(doc._id, { _id: doc.modified_by });
}
}
UPDATE: Due to bug COUCHDB-1229, use of / in doc._id may cause problems. Depending on your use-case, it may be better to use another separator, e.g. : or _.

What do you mean here "two joins"? Is this "tableA join tableB ON ... AND ..."?
Now I want a view or something to fetch document by type=item and _id with informations for each users
This can be done without two/more joins.
Anyway, my advise is to divide your data in 2 databases: items & users. The example above suits only few simple tasks. But when your data grows big (say 10K users & 100K items) it becomes very hard to process your data and it's kinda sucks that all your documents differ from each other with only one field.

Related

Mongoose how to find specific value

After a lot of reading, I am stuck.
the code I posted here, is the implementation of a store database I am trying to make.
in every store, we have some fields. I am interested in doing something with the items array, that contains JSON variables.
I want to filter the items through three filters, firstly by the store ID, secondly by the category ID, and the last filter will be the semi category ID.
I want to send the data from the front end, meaning I supply STOREID, the CategoryID, and the SemiCategoryID.
after receiving the data at the back end side, I am expecting to receive only the relevant items according to the data supplied by the front end.
{
"_id": {
"$oid": "5a1844b5685cb50a38adf5bb" --> **ID of the STORE**
},
"name": "ACE",
"user_id": "59e4c41105d1f6227c1771ea",
"imageURL": "none",
"rating": 5,
"items": [
{
"name": "NirCohen",
"categoryID": "5a0c2d292235680012bd12c9",
"semiCatID": "5a0c2d5a2235680012bd12ca",
"_id": {
"$oid": "5a1958181cd8a208882a80f9"
}
},
{
"name": "he",
"categoryID": "5a0c2d292235680012bd12c9",
"semiCatID": "5a0c2d5a2235680012bd12ca",
"_id": {
"$oid": "5a1973c40e561e08b8aaf2b2"
}
},
{
"name": "a",
"categoryID": "5a0c2d292235680012bd12c9",
"semiCatID": "5a0c2d5a2235680012bd12ca",
"_id": {
"$oid": "5a197439bc1310314c4c583b"
}
},
{
"name": "aaa",
"categoryID": "5a0c2d292235680012bd12c9",
"semiCatID": "5a0c2d5a2235680012bd12ca",
"_id": {
"$oid": "5a197474558a921bb043317b"
}
},
],
"__v": 9
}
and I want the Backend to return the filtered items according to the query.
The problem is, I am not managing to get the CORRECT query.
your help will be much appreciated,
thank you in advance.
If I understand you correctly, you are doing something like this:
Store.find({}).then(..);
If you only want to find the stores where categoryID is equal to the variable myCategory, you could filter them out by using:
Store.find({semiCatID: myCategory}).then(..);
Please let me know if this is not what you are after, then we can keep trying to figure this out together.
EDIT: So you are sending the variables StoreID, CategoryID and SemiCategoryID from the frontend. Receive them in the backend, and want to filter your database collection matching all three fields?
If so.. then I think all you have to do is change your current query:
store.findOne({ _id: req.body.userID }, (err, store) => { console.log(store); });
To something like:
store.findOne({
_id: req.body.userID,
storeID: req.body.StoreID,
categoryID: req.body.CategoryID,
semiCategoryID: req.body.SemiCategoryID
}, (err, store) => { console.log(store); });
This way, the objects you get back from mongo must match all four criterias given from the frontend.
As far as I Understood your question here is my answer to it you can use findById
Store.findById({//store id}).then(..);
or
Store.findOne({_id:ObjectID(storeID)}).then(..);

MongoDB: Query model and check if document contains object or not, then mark / group result

I have a Model called Post, witch contains an property array with user-ids for users that have liked this post.
Now, i need to query the post model, and mark the returned results with likedBySelf true/false for use in by client - is this possible?
I dont have to store the likedBySelf property in the database, just modify the results to have that property.
A temporary solution i found was to do 2 queries, one that finds the posts that is liked by user x, and the ones that have not been liked by user x, and en map (setting likedBySelf true/false) and combine the 2 arrays and return the combined array. But this gives some limitations to other query functions such as limit and skip.
So now my queries looks like this:
var notLikedByQuery = Post.find({likedBy: {$ne: req.body.user._id}})
var likedByQuery = Post.find({likedBy: req.body.user._id})
(I'm using the Mongoose lib)
PS. A typical post can look like this (JSON):
{
"_id": {
"$oid": "55fc463c83b2d2501f563544"
},
"__t": "Post",
"groupId": {
"$oid": "55fc463c83b2d2501f563545"
},
"inactiveAfter": {
"$date": "2015-09-25T17:13:32.426Z"
},
"imageUrl": "https://hootappprodstorage.blob.core.windows.net/devphotos/55fc463b83b2d2501f563543.jpeg",
"createdBy": {
"$oid": "55c49e2d40b3b5b80cbe9a03"
},
"inactive": false,
"recentComments": [],
"likes": 8,
"likedBy": [
{
"$oid": "558b2ce70553f7e807f636c7"
},
{
"$oid": "559e8573ed7c830c0a677c36"
},
{
"$oid": "559e85bced7c830c0a677c43"
},
{
"$oid": "559e854bed7c830c0a677c32"
},
{
"$oid": "559e85abed7c830c0a677c40"
},
{
"$oid": "55911104be2f86e81d0fb573"
},
{
"$oid": "559e858fed7c830c0a677c3b"
},
{
"$oid": "559e8586ed7c830c0a677c3a"
}
],
"location": {
"type": "Point",
"coordinates": [
10.01941398718396,
60.96738099591897
]
},
"updatedAt": {
"$date": "2015-09-22T08:45:41.480Z"
},
"createdAt": {
"$date": "2015-09-18T17:13:32.426Z"
},
"__v": 8
}
#tskippe you can use a method like following to process whether the post is liked by the user himself and call the function anywhere you want.
var processIsLiked = function(postId, userId, doc, next){
var q = Post.find({post_id: postId});
q.lean().exec(function(err,res){
if(err) return utils.handleErr(err, res);
else {
if(_.find(doc.post.likedBy,userId)){ //if LikedBy array contains the user
doc.post.isLiked = true;
} else {
doc.post.isLiked = false;
}
});
next(doc);
}
});
}
Because you are using q.lean() you dont need to actually persist the data. You need to just process it , add isLiked field in the post and send back the response. **note that we are manuplating doc directly. Also you chan tweek it to accept doc containing array of posts and iterating it and attach an isLiked field to each post.
I found that MongoDB's aggregation with $project tequnique was my best bet. So i wrote up an aggregation like this.
Explanation:
Since i want to keep the entire document, but $project purpose is to modify the docs, thus you have to specify the properties you want to keep. A simple way of keeping all the properties is to use "$$ROOT".
So i define a $project, set all my original properties to doc: "$$ROOT", then create a new property "likedBySelf", which is marked true / false if a specified USERID is in the $likedBy set.
I think that this is more clean and simple, than querying every single model after a query to set a likedBySelf flag. It may not be faster, but its cleaner.
Model.aggregate([
{ $project: {
doc: "$$ROOT",
likedBySelf: {
$cond: {
"if": { "$setIsSubset": [
[USERID],
"$likedBy"
]},
"then": true,
"else": false
}
}
}}
]);

Add and remove from array in single query

I want to make a query in which i want to know either the user like or unlike my status, now i want to make it on single query so that I will not call the DB 2 times from my NODEJS server, do any have solution of my problem.
For Add We are using
collection.update({ _id: id },
{ $pull: {
'user_id': 'xxxx-xxxx-xxxx-xxxx' }
}
);
For Remove We are using
collection.update({ _id: id },
{ $push: {
'user_id': 'xxxx-xxxx-xxxx-xxxx' }
}
);
Now I want to use both of them in one query like if apply present in fruit array remove it if not add it.
MongoDB does not allow both a $pull and $push or any other operation to update the same "path" ( therefore single array ) in a single statement. This is mainly to do with the logic handling server side where the update operations are never considered to be ordered in a statement.
Example:
{
"responses": [
{ "user": "Tom", "status": "like" },
{ "user": "Sarah", "status": "unlike" }
]
}
Not that it would make much sense, but you cannot do this:
db.collection.update(
{},
{
"$pull": { "responses": { "user": "Tom", "status": "like" },
"$push": { "responses": { "user": "Tom", "status": "unlike" }
}
)
As the single operation here contains both $push and $pull on the "same path" as "responses". Regardless of you you contruct the statement, neither is required to execute in any order at all.
While we could "match" the position for "Tom" and change his "status" to "unlike" instead, a better model is to do this:
{
"likes": ["Tom"],
"unlikes": ["Sarah"],
"likesTotal": 1,
"unlikesTotal": 1,
"totalScore": 0
}
What this means if I want to change the "vote" for "Tom" then you make a construct like this, with the help of Bulk operations to enable a single request and response:
var bulk = db.collection.initializeOrderedBulkOp();
// Cast "Tom's" unlike where they had a "like" already
bulk.find({
"likes": "Tom",
"unlikes": { "$ne": "Tom" }
}).updateOne({
"$pull": { "likes": "Tom" },
"$push": { "unlikes": "Tom" },
"$inc": {
"likesTotal": -1,
"unlikesTotal": 1
}
]);
// Cast "Tom's" new vote where nothing was there at all
bulk.find({
"unlikes": { "$ne": "Tom" },
"likes": { "$ne": "Tom" }
}).updateOne({
"$push": { "unlikes": "Tom" },
"$inc": {
"unlikesTotal": 1,
"totalScore": -1
}
});
bulk.execute();
This produces a really nice pattern. Not only is each update operation here basically "atomic" in that by acting on separate document properties each modifier is allowed to execute without conflict. But also as a "Bulk" operation, the request for "both" update operations that meet all possible conditions here are sent in a single request and received in a single response.
Of course your "client" logic should also be aware of the current status for who has "liked/disliked" on a particular item, but enforcing this in the general API is good practice.
It keeps arrays in check, and also keeps useful counters in check for general data and general querying purposes, without the need to "calculate" lengths of arrays or matching types.

mongodb update push array

I have the following schema. I am using node.js with mongodb
attributes: {
type: { type: 'string' },
title: { type:'string' },
description: { type:'string' },
is_active: { type:'boolean',defaultsTo:true },
createdBy: { type:'json' },
attachments:{ type:'array' }
}
arr = [{
'id':attResult.id,
'subtype':type,
'title' : attResult.title,
'body' : attResult.body,
'filetype' : attResult.filetype
}];
I am trying to push a attachments into the 'attachments' array that will be unique to the document.
This is the my query.
books.update(
{ id: refid },
{ $push: { attachments: arr } }
).done(function (err, updElem) {
console.log("updElem" + JSON.stringify(updElem));
});
What is the problem in my query,no error but not updated attachments.
I want my result to be this:
{
"_id" : 5,
"attachments": [
{
"id": "xxxxxxx",
"subtype": "book",
"title": "xxxx",
"body": "xxxx" ,
"filetype" : "xxxxx"
},
{
"id": "xxxxxxx",
"subtype": "book",
"title": "xxxx",
"body": "xxxx",
"filetype": "xxxxx"
}
]
}
Someone who trying to push the element into an array is possible now, using the native mongodb library.
Considering the following mongodb collection object
{
"_id" : 5,
"attachments": [
{
"id": "xxxxxxx",
"subtype": "book",
"title": "xxxx",
"body": "xxxx" ,
"filetype" : "xxxxx"
},
{
"id": "xxxxxxx",
"subtype": "book",
"title": "xxxx",
"body": "xxxx",
"filetype": "xxxxx"
}
]
}
arr = [{
'id':'123456',
'subtype':'book',
'title' : 'c programing',
'body' :' complete tutorial for c',
'filetype' : '.pdf'
},
{
'id':'123457',
'subtype':'book',
'title' : 'Java programing',
'body' :' complete tutorial for Java',
'filetype' : '.pdf'
}
];
The following query can be used to push the array element to "attachments" at the end. $push or $addToSet can be used for this.
This will be inserting one object or element into attachments
db.collection('books').updateOne(
{ "_id": refid }, // query matching , refId should be "ObjectId" type
{ $push: { "attachments": arr[0] } } //single object will be pushed to attachemnts
).done(function (err, updElem) {
console.log("updElem" + JSON.stringify(updElem));
});
This will be inserting each object in the array into attachments
db.collection('books').updateOne(
{ "_id": refid }, // query matching , refId should be "ObjectId" type
{ $push: { "attachments":{$each: arr} } } // arr will be array of objects
).done(function (err, updElem) {
console.log("updElem" + JSON.stringify(updElem));
});
Looking at your question a little bit more I'm betting that you are actually using "sails" here even though your question is not tagged as such.
The issue here is that the waterline ODM/ORM has it's own ideas about what sort of operations are actually supported since it tries to be agnostic between working with SQL/NoSQL backends and sort of demands a certain may of doing things.
The result is that updates with $push are not really supported at present and you need more of a JavaScript manipulation affair. So in fact you need to manipulate this via a .findOne and .save() operation:
books.findOne(refid).exec(function(err,book) {
book.attachments.push( arr[0] );
book.save(function(err){
// something here
});
});
Part of that is "waterline" shorthand for what would otherwise be considered an interchangeable use of _id and id as terms, where just specifying the id value as a single argument implies that you are referring to the id value in your query selection.
So unless you replace the waterline ODM/ORM you are pretty much stuck with this AFAIK until there is a decision to maintain this logic in a way that is more consistent with the MongoDB API or otherwise allow access to the "raw" driver interface to perform these .update() operations.
For reference though, and has been alluded to, your general "shell" syntax or what would otherwise be supported in MongoDB specific drivers is like this with the deprecation of the $pushAll operator and the intention being to merge the functionality with the $push and $addToSet operators using the $each modifier:
db.collection.update(
{ "_id": ObjectId(refid) }, // should be important to "cast"
{
"$push": {
"attachments": {
"$each": arr
}
}
}
)
So that syntax would work where it applies, but for you I am thinking that in "sails" it will not.
That gives you some food for thought, and some insight into the correct way to do things.
You are trying to insert an array as an element into your array. You may want to look at $pushAll as a short term solution. This operator is deprecated however see here.
Alternatively you can simply iterate over your array, and each iteration push an element from your array into attachments (this is the recommended approach by Mongo devs).

M2M links in CouchDB with non-primary keys

I have a bunch of "meeting" documents and a bunch "user" documents. A
user may have multiple emails and a meeting may be between multiple
people identified by emails.
I need to lookup users by meeting ID and meeting by user ID by way of
the emails.
My documents look like this right now:
{
"type": "meeting"
"_id": "MEETINGID",
"emails": ["test1#example.com", "test2#example.com"]
// Lots of others things
}
{
"type": "user",
"_id": "USERID",
"emails": ["a#example.com", "test1#example.com"]
// Lots of others things
}
I need to keep them linked via email.
But I could certainly split them out like this if it would help:
{
"type": "user",
"_id": "USERID",
// Lots of others things
}
{
"type": "user-email",
"_id": "USERID",
"email": "a#example.com"
}
{
"type": "user-email",
"_id": "USERID",
"email": "test1#example.com"
}
I need views that produce something like this:
{
"key": "MEETING",
"document": {"_id": "USERID"}
}
{
"key": "USERID",
"document": {"_id": "MEETINGID"}
}
Is this going to be possible? I have a horrible feeling I'm going to
be making load of queries to do this :(
you can write a map like
"meetingByEmail":
function (doc) {
if(doc.type=="meeting") {
for (var curEmail in doc.emails) {
emit (doc.emails[curEmail],null);
}
}
}
call:
_view/byEmail?key="test1#example.com":
Result:
{"total_rows":4,"offset":0,"rows":[
{"id":"f2338c8e69d1da02c94a2104b6000e77","key":"test1#example.com","value":null},
{"id":"f2338c8e69d1da02c94a2104b6000e88","key":"test1#example.com","value":null}
]}
(f2338c8e69d1da02c94a2104b6000e77 is ID from a Meeting, user test1 has joined two meetings)
vice versa (for user)
function(doc) {
if(doc.type=="user") {
for (var curEmail in doc.emails) {
emit (doc.emails[curEmail],null);
}
}
}
the call
user/_view/byEmail?key="test1#example.com"
result:
{"total_rows":2,"offset":1,"rows":[
{"id":"user1","key":"test1#example.com","value":null}
]}
The simple answer is "you can't" unfortunately. CouchDB doesn't allow stuff like that.

Resources