1
votes

There are 3 CouchDB document types:

User

{ "_id: "user/author1", "type: "user", "name" : "Joe Doe"}

Review

{ "_id: "review/review1", "type" : "review", "content" : "..." }

Product

{ "_id": "product/awesomeproduct", "type" : "product", "reviews": [
   {
       "author": "author1",
       "review_id": "review/review1"
   },
   {
       "author": "author2",
       "review_id": "review/review2"
   }
  ]
}

How can I get all data from documents with single view?

I try to use linked documents, but it can be use (with include_docs=true) only one per emit:

(coffescript)

(doc) ->
  if doc.type is "product"
    for review in doc.reviews
       emit [doc._id, review.author],{_id: "user/" + review.author}
       emit [doc._id, review.author],{_id: review.review_id}

But I want something like this (get user and review doc in single emit):

 (doc) ->
  if doc.type is "product"
    for review in doc.reviews
       key = [doc._id, review.author]
       value = {"user:" {_id: "user/" + review.author},"review" :{_id: review.review_id}}
       emit key, value

Is there any possible way how to achieve it? How to join these types of documents in single view?

I want to get something like this result:

[
  key : ["product/awesomeproduct", "author1"], 
  value : {user: {_id: "user/author1"},review :{_id: "review/review1"}}
  doc: 
     {user: {"_id: "user/author1", "type: "user", "name" : "Joe Doe"},
      review: {"_id: "review/review1", "type" : "review", "content" : "..."}
     }

]
3
Ah ha, ok. I attached another answer below. - Colby Blair

3 Answers

4
votes

Seems like the classic case for a reduce function (sorry, no coffee for me :) ):

map

function(doc) {
    if(doc.type == "product") {
        for(review_i in doc.reviews) {
            var review = doc.reviews[review_i];
            emit([doc._id, review.author],{user: "user/" + review.author, review: review.review_id});
        }
    }
}

reduce

function(keys, values) {
    return(values);
}

Result (?reduce=true[&group=exact]):

key                                      value
["product/awesomeproduct", "author2"]    [{user: "user/author2", review: "review/review1"}]
["product/awesomeproduct", "author1"]    [{user: "user/author1", review: "review/review1"}]

What exactly do you want the emitted data to look like?

Response to comment above Should be possible, except the way you want to display the data in the view's returned document. include_docs means return the entire doc, the way it is. Seems like you are wanting to represent the data the same in the view and in the returned doc, so maybe change the structure of the document to be:

_id : ...
data : 
    {
        user: {"_id: "user/author1", "type: "user", "name" : "Joe Doe"},
        review: {"_id: "review/review1", "type" : "review", "content" : "..."}
    }

Otherwise, if you just want the view key,values to be the above mentioned structure, and don't care about the returned doc's structure:

map

function(doc) {
    if(doc.type == "product") {
        for(review_i in doc.reviews) {
            var review = doc.reviews[review_i];
            emit([doc._id, review.author],{
                            user: {_id: "user/" + review.author}, 
                            review: {_id: review.review_id}
                            });
        }
    }
}

Which should result in:

curl -X GET http://localhost:5984/testdb2/_design/test/_view/test?include_docs=true\&reduce=false

{
    "offset": 0,
    "rows": [
        {
            "doc": {
                "_id": "product/awesomeproduct",
                "_rev": "2-20f0c6cc663e03ab93f8646f09d87db2",
                "reviews": [
                    {
                        "author": "author1",
                        "review_id": "review/review1"
                    },
                    {
                        "author": "author2",
                        "review_id": "review/review1"
                    }
                ],
                "type": "product"
            },
            "id": "product/awesomeproduct",
            "key": [
                "product/awesomeproduct",
                "author1"
            ],
            "value": {
                "review": {
                    "_id": "review/review1"
                },
                "user": {
                    "_id": "user/author1"
                }
            }
        },
        {
            "doc": {
                "_id": "product/awesomeproduct",
                "_rev": "2-20f0c6cc663e03ab93f8646f09d87db2",
                "reviews": [
                    {
                        "author": "author1",
                        "review_id": "review/review1"
                    },
                    {
                        "author": "author2",
                        "review_id": "review/review1"
                    }
                ],
                "type": "product"
            },
            "id": "product/awesomeproduct",
            "key": [
                "product/awesomeproduct",
                "author2"
            ],
            "value": {
                "review": {
                    "_id": "review/review1"
                },
                "user": {
                    "_id": "user/author2"
                }
            }
        }
    ],
    "total_rows": 2
}

That doesn't really feel like it was what you were looking for, but its my best guess.

2
votes

Short anwser...you can't. Couchdb only allows you to emit a single document at a time.

There are 2 approaches to getting the data you need.

  1. Keep the structure as-is and perform the join manually yourself in your client code.
  2. Denormalize your data. Denormalizing here would be something like placing the reviews inside your product document.

Data modeling considerations is for Mongodb but the concepts about how to structure your data in a NoSQL environment are the same as they are for CouchDB

0
votes

You can do something like this below,

function (doc) {
  if(doc.type is "product" && doc.reviews.length > 0){
    
    for(x=0; x<doc.reviews.length; x++){
       key = [doc._id, review.author]
       value = {"user:" {_id: "user/" + review.author},"review" :{_id: review.review_id}}
       emit ([key, value])
    }
  }
}

and this will emit two docs in your relevant view.