How to add empty startkey array index in couchDB view? - couchdb

Currently I'm using hyperledger fabric with couchDB container. I have a CouchDB view :
function (doc) {
if(doc.groups.length > 0) {
doc.groups.forEach(function (tag) {
emit([doc.id, tag.fname, tag.lname, tag.no], tag);
});
}
}
I want to filter data using the start & end keys.
Can we use an empty index in startkey array for filter on view?
Here is example for the same:
http://0.0.0.0:7984/mychannel_team/_design/team_view/_view/team_view_filter
?skip=0&limit=21&reduce=false&startkey=[1,"isha",{},"45"]&endkey=[1,"isha",{},"45"]

Yes, by querying the view with startkey=[""]. One could use null too (3.2.2.5. Collation Specification).
Do note the parameters
startkey=[1,"isha",{},"45"]&endkey=[1,"isha",{},"45"]
is poorly constructed in the sense that the use of {} seems to imply a wildcard, which is does not.
These parameters are equivalent to the prior
startkey=[1,"isha",{}]&endkey=[1,"isha",{}]
Because any field value following {} is ignored. Think of {} as a match-all, not a field/placeholder wildcard.
As another matter: regarding the OP's map function, the first field of the complex key will always be null. It is probable the intention is to emit doc._id rather than doc.id.

Related

How to make $elemMatch work for json array data in mango query?

I have a field in my application like below.
{
"Ct": "HH",
Val:{
"Count":"A",
"Branch":"A"
}
}
When I'm trying to retrieve this using below command in CouchDB, I'm unable to retrieve records.
{
"selector" : {
"Val":{
"$elemMatch":{
"Count":"A"
}
}
}
From the CouchDB documentation,$elemMatch[1]
Matches and returns all documents that contain an array field with at
least one element that matches all the specified query criteria.
Val.Count is not an array field so $elemMatch is not appropriate.
Consider the CouchDB documentation regarding subfield queries[2]:
1.3.6.1.3. Subfields
A more complex selector enables you to specify the values for field of
nested objects, or subfields. For example, you might use a standard
JSON structure for specifying a field and subfield.
Example of a field and subfield selector, using a standard JSON
structure:
{
"imdb": {
"rating": 8
}
}
An abbreviated equivalent uses a dot notation to combine the field and
subfield names into a single name.
{
"imdb.rating": 8
}
Specifically,
selector: {
"Val.Count": "A"
}
1 CouchDB: 1.3.6.1.7. Combination Operators
2 CouchDB: 1.3.6.1.3. Subfields

Remove a single object from an array of objects in a single document of a collection

I have a problem with which I am struggling for quite some time.Suppose my document is like this
{"owner":"princu7", "books":[{"name":"the alchemist"}, {"name":"the alchemist"}, {"name":"the alchemist"}].
Now what do I do if I have to just remove one single element from the books array based on the matching of the name?I did it like this
var bookName="the alchemist";
var obj={"name":bookName}
db.collection("colName").update({"owner":"princu7"}, {$pull:{books:obj}}, {multi:false})
But the problems is that it removes all the entries in the array which have the name matching to "the alchemist". What I wanted was this
{"owner":"princu7", "books":[{"name":"the alchemist"}, {"name":"the alchemist"}
But what I got was this
{"owner":"princu7", "books":[]}
Upon reading the documentation, it says that pull removes all the instances that match the required condition so maybe that's why it's removing all other entries of the array which match the condition.So what should I do here.Thanks for reading.Really appreciate your help.
See this issue:
https://jira.mongodb.org/browse/SERVER-1014
You cannot achieve what you are trying to do in a single update. A way would be to modify that record in your application & save the changes.
You could use collection.updateOne() with upsert set to true to re-write the record in place. The idea is you get the original document, modify it in your app logic, then re-apply it to the database after removing the element from the array.
function upsert(collection, query, json) {
var col = db.collection(collection);
col.updateOne(query
, { $set : json }
, { upsert : true }
, function (err, result) {
if(err) {
log('error = ', err);
} else {
// no error, call the next function
}
}
);
};
By design, mongodb's $pull operator removes from an existing array all instances of a value or values that match the specified condition.Therefore it will remove all matching {"name":"the alchemist"} elements from the array.
I guess I would use $pop, which only removes the first matching element.
db.collection("colName")
.update({"owner": "princu7"}
, {$pop: {"books": {$match: {"name": "the alchemist"}}}}
, {multi:false}
)

Sorting CouchDB result by value

I'm brand new to CouchDB (and NoSQL in general), and am creating a simple Node.js + express + nano app to get a feel for it. It's a simple collection of books with two fields, 'title' and 'author'.
Example document:
{
"_id": "1223e03eade70ae11c9a3a20790001a9",
"_rev": "2-2e54b7aa874059a9180ac357c2c78e99",
"title": "The Art of War",
"author": "Sun Tzu"
}
Reduce function:
function(doc) {
if (doc.title && doc.author) {
emit(doc.title, doc.author);
}
}
Since CouchDB sorts by key and supports a 'descending=true' query param, it was easy to implement a filter in the UI to toggle sort order on the title, which is the key in my results set. Here's the UI:
List of books with link to sort title by ascending or descending
But I'm at a complete loss on how to do this for the author field.
I've seen this question, which helped a poster sort by a numeric reduce value, and I've read a blog post that uses a list to also sort by a reduce value, but I've not seen any way to do this on a string value without a reduce.
If you want to sort by a particular property, you need to ensure that that property is the key (or, in the case of an array key, the first element in the array).
I would recommend using the sort key as the key, emitting a null value and using include_docs to fetch the full document to allow you to display multiple properties in the UI (this also keeps the deserialized value consistent so you don't need to change how you handle the return value based on sort order).
Your map functions would be as simple as the following.
For sorting by author:
function(doc) {
if (doc.title && doc.author) {
emit(doc.author, null);
}
}
For sorting by title:
function(doc) {
if (doc.title && doc.author) {
emit(doc.title, null);
}
}
Now you just need to change which view you call based on the selected sort order and ensure you use the include_docs=true parameter on your query.
You could also use a single view for this by emitting both at once...
emit(["by_author", doc.author], null);
emit(["by_title", doc.title], null);
... and then using the composite key for your query.

How can I query multiple key criteria?

Using couchdb, with the following json:
{"total_rows":3,"offset":0,"rows":[ {"id":"bc26e5eae7f8c8c3486818e7e7971df0","key":{"user":"lili#abc.com","pal":["igol ≠ eagle"],"fecha":"10/5/2014"},"value":null},{"id":"cf0dc2e2874776958c59f2f544b5a750","key":{"user":"lili#abc.com","pal":["kat ≠cat"],"fecha":"10/6/2014"},"value":null},{"id":"df4ec96088ed52096db064f2ebd2310b","key":{"user":"dum#ghi.com","pal":["dok ≠ duck"],"fecha":"10/7/2014"},"value":null}]}
I would like to query for specific user AND specific date:
for example:
?user="lili#def.com"&fecha:"10/6/2014"
I also tried:
?user%3Dlili%40def.com%26fecha%3A10%2F6%2F2014
Needless to say, it isn't currently working as I expected (all results are shown, not only the register needed).
my view func is:
function(doc) {
if (doc.USER){
emit({user:doc.USER, pal:doc.palabras, fecha:doc.fecha});
}
}
Regards.
Remember that CouchDB views are simply key/value lookups that are built at index-time, not query time. At the minute you are emitting a key with no value. If you want to look something up by two values, you'll need to emit a composite key (an array):
function(doc) {
if (doc.USER) {
emit([doc.USER, doc.fecha], doc);
}
}
Then you can look up matching documents by passing the array as the key:
?key=%5B%22lili%40def.com%22%2C%20%2210%2F6%2F2014%22%5D
There are optimisations you can make to this (e.g. emitting a null value and using include_docs to reduce the size of the view) but this should set you off on the right track.
I do the same thing as Ant P but I tend to use strings.
function ( doc ) {
if ( doc.USER ) {
emit( 'user-' + doc.USER + '-' + doc.fecha, doc );
}
}
I would also highly recommend emitting null instead of doc as a value.
Remember, you can always emit more than once depending on what kind of queries you need.
For example, if you're looking for all posts by a specific user between two dates, you could do the following view.
function ( doc ) {
if ( doc.type == "post" ) {
emit( 'user-' + doc.nombre, null );
emit( 'fecha-' + doc.fecha, null );
}
}
Then you would query the view twice _view/posts?key="user-miUsario", and _view/posts?start_key="fecha-1413040000000"&end_key="fecha-1413049452904". Then, once you have all of the ids from both views, you take the intersection and use _all_docs to get your original documents.
You end up making three requests but it saves disk space in the view, the payloads are smaller because you return null, and your code is simpler because you can query the same view multiple ways.

How do I perform a parameterized query on CouchDB

I would like to use CouchDB to store some data for me and then use RESTful api calls to get the data that I need. My database is called "test" and my documents all have a similar structure and look something like this (where hello_world is the document ID):
"hello_world" : {"id":123, "tags":["hello", "world"], "text":"Hello World"}
"foo_bar" :{"id":124, "tags":["foo", "bar"], "text":"Foo Bar"}
What I'd like to be able to do is have my users send a query such as: "Give me all the documents that contain the words 'hello world', for example. I've been playing around with views but it looks like they will only allow me to move one or more of those values into the "key" portion of the map function. That gives me the ability to do something like this:
http://localhost:5984/test/_design/search/_view/search_view?key="hello"
But this doesn't allow me to let my users specify their query string. For example, what if they searched for "hello world". I'd have to do two queries: one for "hello" and one for "world" then I'd have to write a bunch of javascript to combine the results, remove duplicates, etc (YUCK!). What I really want is to be able to do something like this:
http://localhost:5984/test/_design/search/_view/search_view?term="hello world"
Then use the parameter "hello world" in the views map/reduce functions to find all the documents that contain both "hello" and "world" in the tags array. Is this sort of thing even possible with CouchDB? Is there another way to accomplish this inside a view that I'm not thinking of?
CouchDB Views do not support facetted search or fulltext search or result intersection. The couchdb-lucene plugin lets you do all these things.
http://github.com/rnewson/couchdb-lucene/tree/master
Technically this is possible if you emit for each document each set of the powerset of the tags of the document as the key. The key set element must be ordered and your query whould have to query the tags ordered, too.
function map(doc) {
function powerset(array) { ... }
powerset_of_tags = powerset(doc.tags)
for(i in powerset_of_tags) {
emit(powerset_of_tags[i], doc);
}
}
for the doc {"hello_world" : {"id":123, "tags":["hello", "world"], "text":"Hello World"} this would emit:
{ key: [], doc: ... }
{ key: ['hello'], doc: ... }
{ key: ['world'], doc: ... }
{ key: ['hello', 'world'], doc: ... }
Although is this possible I would consider this a rather arkward solution. I don't want to imagine the disk usage of the view for a larger number of tags. I expect the number of emitted keys to grow like 2^n.
under the hood, couchdb stores data by b-tree thus you should use views to pre-process, the limitation in this case that is you can not search regex. The alternative, you can search by prefixes or suffixes from the key in views.
Note: don't use emit(key, doc), it will clone document, you should use emit(key, null) or emit(key) and add "include_docs = true" when query.
You can use yours tags as key to query.
//view function
function (doc) {
if (doc.type === "hello") {
emit(doc);
}
}
//mango query
db
.query(your_view_name,
{ startkey: startkey, endkey: endkey, include_docs: true });
Note:
endkey = startkey + "\uffff";
startkey = "h", "he", "hell"...
Plus: don't never use mango query to query regex if you don't want performance go to the hell, sences. I fixed performance issue from 2 minutes to 2 seconds by view function.

Resources