Couchdb views return rows in rows - node.js

I am trying to create a view that returns the name with the price changes from the doc.
"name": "USD"
"price_changes": {
"0min": 0,
"15min": 0,
"30min": 0,
"60min": 0,
"90min": 0,
"120min": 0,
"150min": 0,
"180min": 0,
"210min": 0,
"240min": 0,
"270min": 0,
...
What I want is a view that returns all the data in "price_changes".
{"id":"c95718ebd00b13bc9a8b03a0e5005d51",
"key": {"0min":0,"15min":0,"30min":0,"60min":0,"90min":0,"120min":0,"150min":0,...}
{"id":"c95718ebd00b13bc9a8b03a0e5006906",
"key": {"0min":0,"15min":0,"30min":0,"60min":0,"90min":0,"120min":0,"150min":0,...}
...
And views that return a set of the rows, like only "0mins", "15mins" and "30mins" for each name.
Like:
"id":"c95718ebd00b13bc9a8b03a0e5005d51",
"name": "USD",
"key": {"0min":0,"15min":0,"30min":0}
Separately
"id":"c95718ebd00b13bc9a8b03a0e5006906",
"name": "GBP",
"key": {"0min":0,"15min":0,"30min":0}
What I have so far is (but doesn't return what I want):
function (doc) {
var mins, value;
if (doc.price_changes) {
for (mins in doc.price_changes) {
value = doc.price_changes[mins];
emit(doc.id, [value], mins);
}
}
}
Any idea guys? I can't really find anything on returning data like this.

Firstly, in your view map function, you're emitting three things:
emit(doc.id, [value], mins);
Which is not what it should be. As documented, you need to emit key/value pairs (well, of course, key or value can be an array of many things):
Map functions accept a single document as the argument and
(optionally) emit() key/value pairs that are stored in a view.
According to what you say:
What I want is a view that returns all the data in "price_changes".
My understanding is that you need the following map function:
function (doc) {
var mins, value;
if (doc.price_changes) {
for (mins in doc.price_changes) {
value = doc.price_changes[mins];
emit(mins, [value, doc._id]);
}
}
}
And it returns:
$ curl -k -X GET https://admin:****#192.168.1.106:6984/sample-repl/_design/price/_view/price
{"total_rows":11,"offset":0,"rows":[
{"id":"so_q","key":"0min","value":[0,"so_q"]},
{"id":"so_q","key":"120min","value":[0,"so_q"]},
{"id":"so_q","key":"150min","value":[0,"so_q"]},
{"id":"so_q","key":"15min","value":[0,"so_q"]},
{"id":"so_q","key":"180min","value":[0,"so_q"]},
{"id":"so_q","key":"210min","value":[0,"so_q"]},
{"id":"so_q","key":"240min","value":[0,"so_q"]},
{"id":"so_q","key":"270min","value":[0,"so_q"]},
{"id":"so_q","key":"30min","value":[0,"so_q"]},
{"id":"so_q","key":"60min","value":[0,"so_q"]},
{"id":"so_q","key":"90min","value":[0,"so_q"]}
]}
I'm not sure if the above result is what you want, please let me know if I'm wrong.
Thanks! Just wondering how would I return say the first 3 (like: 0min,
15min, 30min)
To get the first 3, you can use limit query parameter like this, as you can see, it will return the first 3:
$ curl -k -X GET https://admin:****#192.168.1.106:6984/sample-repl/_design/price/_view/price?limit=3
{"total_rows":11,"offset":0,"rows":[
{"id":"so_q","key":"0min","value":[0,"so_q"]},
{"id":"so_q","key":"120min","value":[0,"so_q"]},
{"id":"so_q","key":"150min","value":[0,"so_q"]}
]}
In general, you can play around with query parameters to get what you want out of the view/index.

Related

CouchDB Count Reduce with timestamp filtering

Let's say I have documents like so:
{
_id: "a98798978s978dd98d",
type: "signature",
uid: "u12345",
category: "cat_1",
timestamp: UNIX_TIMESTAMP
}
My goal is to be able to count all signature's created by a certain uid but being able to filter by timestamp
Thanks to Alexis, I've gotten to this far with a reduce _count function:
function (doc) {
if (doc.type === "signature") {
emit([doc.uid, doc.timestamp], 1);
}
}
With the following queries:
start_key=[null,lowerTimestamp]
end_key=[{},higherTimestamp]
reduce=true
group_level=1
Response:
{
"rows": [
{
"key": [ "u11111" ],
"value": 3
},
{
"key": [ "u12345" ],
"value": 26
}
]
}
It counts the uid correctly but the filter doesn't work properly. At first I thought it might be a CouchDB 2.2 bug, but I tried on Cloudant and I got the same response.
Does anyone have any ideas on how I could get this to work with being ale to filter timestamps?
When using compound keys in MapReduce (i.e. the key is an array of things), you cannot query a range of keys with a "leading" array element missing. i.e. you can query a range of uuids and get the results ordered by timestamp, but your use-case is the other way round - you want to query uuids by time.
I'd be tempted to put time first in the array, but unix timestamps are not so good for grouping ;). I don't known the ins and outs of your application but if you were to index a date instead of a timestamp like so:
function (doc) {
if (doc.type === "signature") {
var date = new Date(doc.timestamp)
var datestr = date.toISOString().split('T')[0]
emit([datestr, doc.uuid], 1);
}
}
This would allow you to query a range of dates (to the resolution of a whole day):
?startkey=["2018-01-01"]&endkey=["2018-02-01"]&group_level=2
albeit with your uuids grouped by day.

CouchDB View - Filter by List Field Attribute (doc.objects.[0].attribute)

I need to create a view that lists the values for an attribute of a doc field.
Sample Doc:
{
"_id": "003e5a9742e04ce7a6791aa845405c17",
"title", "testdoc",
"samples": [
{
"confidence": "high",
"handle": "joetest"
}
]
}
Example using that doc, I want a view that will return the values for "handle"
I found this example with the heading - Get contents of an object with specific attributes e.g. doc.objects.[0].attribute. But when I fill in the attribute name, e.g. "handle" and replace doc.objects with doc.samples, I get no results:
Toggle line numbers
// map
function(doc) {
for (var idx in doc.objects) {
emit(doc.objects[idx], attribute)
}
}
That will create an array of key-value-pairs where the key is alway the value of handle. Replace null with a value you want e.g. doc.title. If you want to get the doc attached to every row use the query parameter ?include_docs=true while requesting the view.
// map
function (doc) {
var samples = doc.samples
for(var i = 0, sample; sample = samples[i++];) {
emit(sample.handle, null)
}
}
Like this ->
function(doc) {
for (var i in doc.samples) {
emit(doc._id, doc.samples[i].handle)
}
}
It will produce a result based on the doc._id field as the key. Or, if you want your key to be based on the .handle field you reverse the parameters in emit so you can search by startKey=, endKey=.

mongodb's find over nodejs 2d index with $near returns as an empty find

I'm trying to get some results from my places collection which are close to a coordinate. For this purpose, I'm using a 2d index over a location.position attribute which has a lng and lat attribute.
The problem is that every coordinate I put, it returns the same 60 results (the whole collection).
The docs in my collection are all from near to my GPS coordinate and, however, if I put a 0.0, 0.0 coordinate or any other pair, it returns always the same.
In fact, the query returns exactly the same if I use an empty find().
The index seems to be created correctly as it is not returning any error.
An object in my collection is like this:
{ "_id" : "80293840923...",
"name": "myname",
"location" : {
"position" : { "lng": -196988, "lat": 43.30594 }
"address": "example",
"city": "example" }
}
The index is created like this:
places_collection.ensureIndex({"location.position": "2d"}, {name: "index2dLocation"}, function(error, indexName){
callback("", "Indice creado: " + indexName)
});
The search would be like this:
places_collection.find(
{ "location.position" : { $near : [ parseFloat(-123.98) , parseFloat(162.342) ] } }
).toArray(function(error, results) {
if( error ){ callback(error, "")
}else{ console.log(results.length); callback(null, results); }
});
And finally, as said before, my search returns always the same result. It doesn't care if I put one coordinate or another. Even it doesn't care if I make a empty find().
Any help here please? I don't know how to make it work.
Thank you very much.
Finally, the solution was to add the $maxDistance property to avoid all the docs which are more far than the radius in the $maxDistance.
If you don't put that property and you only have 10 docs located near to one coordinate, if you make a find in a very far coordinate, it will still return the same 10 docs because you don't have more docs near to the new coordinate.
In addition, it's important to say that $maxDistance's unit is in degrees, so here was my solution: https://stackoverflow.com/a/7841830/974828
To convert degrees to kilometers, you have to divide by 111.12.
So here is the result:
db.places.find( { loc : { $near : [50,50] , $maxDistance : 1/111.12 } } )

A view in CouchDB to find recent forum threads with zero replies

Let's say we have the following data model for a hypothetical forum:
// Post
{
"_id": 1,
"type": "post",
"text": "",
"timestamp": 1,
}
// Reply
{
"_id": 2,
"post_id": 1,
"type": "reply",
"text": "",
"timestamp": 2,
}
All replies are flat (there are no replies to replies, all replies are to a post)
The stream of past posts and replies is unbounded
Ideally, I want to find the most recent threads without any replies.
So far I have these map/reduce functions:
map: function(doc) {
if (doc.type == "post") {
emit(doc._id, 0);
}
if (doc.type == "reply") {
emit(doc.post_id, 1);
}
},
reduce: function(keys, vals, rereduce) {
return sum(vals);
}
If I run this and group by key, it gives me a list of all threads, where value is 0 for unreplied ones. So far, so good.
But,
given that the stream is theoretically unbounded, I cannot sort or filter it in the application or CouchDB's list/filter functions, because they apply to the returned (and already truncated) dataset;
changing the key or group level destroys the grouping I want, post ID has to be the group key.
Question: How do I find N most recent threads with no replies, how do I sort the reduced view by the timestamp of the post?
Easier question: How do I find at all if there are threads with no replies (boolean solution)? This implies filtering the reduced view, so that only zero-valued rows are left.
I think the easier implementation is for you to add additional field reply_count,
default to zero,
when a reply is replied,
reply_count+=1
come to search for post with zero replies,
the map function can be as simple as :
function (doc) {
if (doc.type == "post")
{
emit([doc.reply_count, doc.timestamp], null);
}
}
query :
descending=true
startkey=[0,9999999999]
endkey=[0,0]
include_docs=true

CouchDB, MapReduce: query a time slice

For a monitoring an application with CouchDB I need to sum up a field of my data (for example the time needed to execute a method that has been logged).
That's no problem for me with map-reduce, but I need to sum up only the data recorded in a special time slice.
Example records:
{_id: 1, methodID:1, recorded: 100, timeneeded: 10},
{_id: 2, methodID:1, recorded: 200, timeneeded: 11},
{_id: 3, methodID:2, recorded: 200, timeneeded: 2},
{_id: 4, methodID:1, recorded: 300, timeneeded: 6},
{_id: 5, methodID:2, recorded: 310, timeneeded: 3},
{_id: 6, methodID:1, recorded: 400, timeneeded: 9}
Now I would like to get just the sum of timeneeded of all records that have been recorded in the range of 200 to 350 and grouped by methodID. (That would be 17 for methodID:1 and 5 for methodID:2.)
How can I do that?
I now tried it with a list function that's using WickedGrey's idea. See my functions here:
map function:
function(doc) {
emit([ doc.recorded], {methodID:doc.methodID, timeneeded:doc.timeneeded});
}
list function:
"function(head, req) {
var combined_values = {};
var row;
while (row = getRow()) {
if( row.values.methodID in combined_values) {
combined_values[ row.values.methodID] +=row.values.timeneeded;
}
else {
combined_values[ row.values.methodID] = row.values.timeneeded;
}
}
for(var methodID in combined_values){
send( toJSON({method: methodID, timeneeded:combined_values[methodID]}) );
}
}"
Now I have to problems:
1. I always get the results as a file and my firefox asks me if I want to download it, instead of viewing it in the browser like when I query a classic view.
2. As I understand the thing, the results are now calculated on the fly, in the list function. I expect this to be not really fast with hundrets of millions of records... Any ideas how to get it faster?
Thank you for your help!
andy
You can't use a map key to filter by one set of criteria, but group by another in CouchDB. However, you can filter the keys by time range, and group with a reduce function. Try something like this:
function map(doc) {
emit(doc.recorded, {doc.methodID: doc.timeneeded});
}
function reduce(key, values, rereduce) {
var combined_values = {};
for (var i in values) {
var totals = values[i];
for (var methodID in totals) {
if (methodID in combined_values) {
combined_values[methodID] += totals[methodID];
}
else {
combined_values[methodID] = totals[methodID];
}
}
}
return combined_values;
}
That should allow you to specify a start/end key, and with group_level=0 should get you a value containing the dictionary that you're looking for.
Edit: Also, this thread might be of interest:
http://couchdb-development.1959287.n2.nabble.com/reduce-limit-error-td2789734.html
It discusses an option to turn off the reduce must shrink message, and further down the list provides other ways of achieving the same goal: using a list function. That might be a better approach that what I've outlined here. :(
function map(doc) {
if(doc.methodID && doc.recorded && doc.timeneeded) {
emit([doc.methodID,doc.recorded], doc.timeneeded);
}
}
//reduce
_sum

Resources