Couchdb join two documents using key - javascript

I have two documents one with tree structure and the other one relation to the first doc. Im trying to join these two doc`s by fk and pk. I couldnt get the actual results and it displays all null values.
First doc
{
"name": "one",
"root": {
"level1" : {
"level2" : {
"level3" : {
"itemone": "Randomkey1",
"itemtwo": "Randomkey2
}
}
}
},
"type": "firstdoc"
}
Second doc
{
"name" : "two",
"mapBy" : "Randomkey1",
"type" : "senconddoc
}
I`ve written a map function, which lists all the keys given a level 1 or 2 or 3 . Now I want o join this first doc and second doc using the key. Ive tried two ways (first: Im getting all (Root, Randomkey), (docName, Randomkey1) but it doesnt do any join. Im looking for a result like
(Root, docName)
Could someone assist in fixing this
map
function(doc) {
if (doc.type === 'firstdoc' || doc.type === 'seconddoc' ) {
var rootObj = doc.Root;
for (var level1 in rootObj) {
var level2Obj = doc.Root[level1];
for (var level2 in level2Obj) {
var keys = new Array();
var level3Obj = level2Obj[level2];
for (var i in level3Obj) {
var itemObj = level3Obj[i];
for (var i in itemObj) {
keys.push(itemObj[i]);
emit(doc.name, [itemObj[i], 0]);
var firstDocName = doc.name;
//This is gives null values
if (doc.Type === 'senconddoc' && doc.mapBy === itemObj[i]) {
emit(firstDocName , doc);
}
}
}
}
}
}
//This just lists keys to me
if (doc.type === 'senconddoc') {
emit([doc.mapBy, 1] , doc);
}
}

To simulate joins you have to output a doc with an _id in it, the value of the _id needs to point to an actual _id of a document. Then you can make use of include_docs=true to pull in the related documents. Example with many-to-many here: http://danielwertheim.se/couchdb-many-to-many-relations/
If this is not applicable, you can make a two step manual join by first returning custom keys. Then make a second query against the all documents view, with multiple keys specified.

It is much late but For such kind of tree structure, documents should be kept separately such as
{
id="firstDoc",
type="rootLevel"
}
{
id="secondDoc",
type="firstLevel"
parent="firstDoc"
}
{
id="thirdDoc",
type="firstLevel",
parent="firstDoc"
}
Now different levels can be joined using the Map Reduce function, Make sure that you will use it in proper way, Also use Logging so that you will be able to know in which sequence map/reduce function are being called by CouchDB.
Further, map Function should only be used for emitting the required document suppose if you want to to emit your level3 then in emit's value part, root.level1.level2.level3 should be there.
For more detail about the join you can refer
CouchDB Join using Views (map/reduce)

Related

Javascript: How to account for undefined JSON which breaks for loop

I am having trouble and may be approaching this wrong so open to other solutions. From a fetch call, I am receiving a larger json array of objects which has further nesting in each object. I want to slim down the larger object array to only some values (for each object) and I am currently doing that by iterating over all of the objects in the larger array and taking the values I want from each object and then pushing to a newly created larger array. The below code works sometimes, however there are times where no data is present in some of the values from the json breaking my for loop. For exampledata.products[i].images[0].src in a given object is sometimes undefined, which breaks the loop saying "cant read property .src of undefined" and doesn't iterate all the way through.
Main questions?
1. How can I account for undefined values in any given key:value pair without breaking the loop
2. Is there a better way to go about this entirely?
I can edit the answer to include an example of the incoming json if that helps at all
Edit
I also want to not include any object which resulted in undefined image in the final array. Is there any way to prevent that object from being added or maybe filter it out later?
let productFilter = []
fetch('https://some.json')
.then(
function (response) {
response.json().then(function (data) {
for (var i = 0; i < data.products.length; i++) {
let filteredArray = {
"productId": data.products[i].id,
"productName": data.products[i].title,
"productImg": data.products[i].images[0].src
}
productFilter.push(filteredArray)
}
});
}
)
.catch(function (err) {
console.log('Fetch Error', err);
})
use a conditional expression:
"productImg": data.products[i].images[0] ? data.products[i].images[0].src : "put default image URL here"
If you don't want these objects in the array at all, just put a test around the entire code that adds the object.
if (data.products[i].images[0]) {
let filteredArray = {
"productId": data.products[i].id,
"productName": data.products[i].title,
"productImg": data.products[i].images[0].src
}
productFilter.push(filteredArray)
}
For exampledata.products[i].images[0].src in a given object is sometimes undefined, which breaks the loop saying "cant read property .src of undefined"
The error tells you that exampledata.products[i].images[0] is undefined, not exampledata.products[i].images[0].src itself. To deal with this, you can add a simple if statement inside your for loop:
let filteredArray = {
"productId": data.products[i].id,
"productName": data.products[i].title,
}
if (exampledata.products[i].images[0]) {
filteredArray.productImg = exampledata.products[i].images[0].src
}
Note that this solution will leave out the productImg key when there is no image. Alternatively, you can use Barmar's answer if you want to ensure the key always exists and have an appropriate default, whether it is a default URL or just undefined.
Suggestions:
Use a variable to shorten many lines in your code:
product = exampledata.products[i];
Now you can do product.id, etc.
Check out the map() function of Array. You can do the same thing you are doing here without writing all of the boilerplate code for the for loop.
You need to check if data.products[i] exists, otherwise you're attempting to reference properties of an undefined object. The same logic applies for the lower level reference to images[0].src
let productFilter = []
fetch('https://some.json')
.then(
function (response) {
response.json().then(function (data) {
for (var i = 0; i < data.products.length; i++) {
if (data.products[i]) {
let filteredArray = {
"productId": data.products[i].id,
"productName": data.products[i].title,
// "productImg": data.products[i].images[0].src
}
if (data.products[i].images[0]) {
filteredArray.productImg = data.products[i].images[0].src;
}
productFilter.push(filteredArray)
}
}
});
}
)
.catch(function (err) {
console.log('Fetch Error', err);
})

Object of Names / split into separate objects or arrays

I am looking for some general advice about how I should be thinking to go about doing this.
What I need to do is take an object that has "usernames" : userId, etc.. and split them into separate objects or arrays with each object only containing usernames that start from a certain letter.
So right now I have:
allusers = {"adam292":10302, "alex92":12902, "briannv999":10302, "sandra127":11102, "sam11":100 }
but I need to split them into their own objects or arrays like the following:
ausers = { "adam292":10302, "alex92":12902 }
busers = { "briannv999":10302 }
susers = {"sandra127":11102, "sam11":1002 }
I am doing this because I need to display a dialog box that also shows the letters a - z which would be links that you can click to display users that start with that letter.
Any advice is very much appreciated!
Here is one way to do it:
Working Fiddle
looping through the object we grab the first letter and check to see if we have a key for it in our users object, if not we make one and assign an array (containing the user data) to it, if yes we push to that array:
var users = {};
for (var user in allusers) {
var firstLetter = user.slice(0,1);
if (users[firstLetter]) {
users[firstLetter].push([user, allusers[user]]);
}
else {
users[firstLetter] = [[user, allusers[user]]];
}
}
The output of the code above using your example object is the following:
{
a: [["adam292", 10302], ["alex92", 12902]],
b: [["briannv999", 10302]],
s: [["sandra127", 11102], ["sam11", 100]]
}
you can do this in a loop:
letter2users = {}
for (var uname in allusers) {
if (!letter2users[uname[0]]) {
letter2users[uname[0]] = [];
}
letter2users[uname[0]].push(allusers[uname]);
}
# access this by using letter2users.a lettersusers.b

Get multiple arrays of mongodb documents and merge them for unique values

I need to get all tags, which are stored in an array for multiple documents. I can select all needed documents by:
Collection.find({ 'metadata.tags': { $exists: true } });
The data of the first document could look like
"metadata" : {
"type" : "photo",
"tags" : [
"test",
"anything",
"something",
"more"
]
}
The second one:
"metadata" : {
"type" : "photo",
"tags" : [
"different",
"content",
"something",
"more"
]
}
Some of the elements in the arrays are duplicated. I need just unique values, so the result in this example would have six elements.
I would think of something like this for removing duplicates:
var c = a.concat(b);
var tags = c.filter( function (item, pos) { return c.indexOf(item) == pos; } );
return tags;
But my first problem is how to get access of all arrays, which could be more then just two, as the find() will give me multiple documents. How do I get the arrays of all documents and merge them?
The second problem is to remove duplicates, as my example would just work for two arrays.
Update
I would do something like this:
var tags = [];
Media.find({ 'metadata.tags': { $exists: true } }).forEach(function(doc) {
tags = tags.concat(doc.metadata.tags);
});
var result = tags.filter( function (item, pos) { return tags.indexOf(item) == pos; } );
return result;
Maybe this could be optimized...
To solve your , mongoDB has already provided us a distinct command. Use it like below:
Collection.distinct('metadata.tags')
The above command will give you distinct values from the array. There wont be any duplicate elements. Hence your second problem will also be solved

How to use javascript to loop through key , values and add up one key's value when the other's match

I have a dataset of records that look like this :
[{
"d1d":"2015-05-28T00:00:00.000Z",
"d1h":0,
"d15m":0,
"ct":3
},
{
"d1d":"2015-05-28T00:00:00.000Z",
"d1h":0,
"d15m":0,
"ct":1
}
]
The ct value changes in every record. If d1d, d1h, and d15m are the same in one or more records, I need to combine those records into one with the sum of all the ct values.
I do have jquery, can I use grep for this?
I realize the server side could do a better job of getting me this data , but I have zero control over that.
You don't have to use jQuery for this, vanilla JavaScript will do.
I'll show you two solutions to your problem;
Example 1: Abusing Array#reduce as an iterator
var intermediaryArray = [];
dataset.reduce(function(prev, curr) {
if(prev.d1d === curr.d1d && prev.d1h === curr.d1h && prev.d15m === curr.d15m) {
intermediaryArray.push({
d1d: prev.d1d,
d1h: prev.d1h,
d15m: prev.d15m,
ct: prev.ct + curr.ct
});
} else {
// push the one that wasn't the same
intermediaryArray.push(curr);
}
// return current element so reduce has something to work on
// for the next iteration.
return curr;
});
Example 2: Using Array#Map and Array#Reduce in conjunction
This example utilises underscore.js to demonstrate the logic behind what you want to do.
.map() produces the new array of grouped objects.
.groupBy() produces an array of subarrays containing the objects that pass the predicate that all objects must share the same d1d or grouping function.
.reduce() boils all subarrays down to one value, your object with both cts added to each other.
var merged = _.map(_.groupBy(a, 'd1d'), function(subGroup) {
return subGroup.reduce(function(prev, curr) {
return {
d1d: prev.d1d,
d1h: prev.d1h,
d15m: prev.d15m,
ct: prev.ct + curr.ct
};
});
});
Here's one possible solution:
var dataset = [{
"d1d":"2015-05-28T00:00:00.000Z",
"d1h":0,
"d15m":0,
"ct":3
},
{
"d1d":"2015-05-28T00:00:00.000Z",
"d1h":0,
"d15m":0,
"ct":1
}
]
function addCt(dataset) {
var ctMap = {}
var d1d, d1h, d15m, ct, key, value
for (var ii=0, record; record=dataset[ii]; ii++) {
key = record.d1d+"|"+record.d1h+"|"+record.d15m
value = ctMap[key]
if (!value) {
value = 0
}
value += record.ct
ctMap[key] = value
}
return ctMap
}
ctMap = addCt(dataset)
console.log(ctMap)
// { "2015-05-28T00:00:00.000Z|0|0": 4 }
You may want to construct the key in a different way. You may want set the value to an object containing the d1d, d1h, d15m and cumulated ct values, with a single object for all matching d1d, d1h and d15m values.

Combining JSON Arrays with jQuery

I've spent all morning messing with this now and reading on here, but have found myself going round in circles!
I am trying to draw a chart using the excellent AmCharts Javascript Charts, to show me stock holding as a bar chart and stock turn as a line chart.
I cannot get both sets of data from one query to my database, and cannot use AmCharts StockChart as it is not time based data... therefore, I have two sets of data which need combining with Javascript.
The data is being pulled from a database and returned successfully as JSON arrays similar to this:
SALES DATA:
[{"brandName":"Fender","gearShiftedPerMonth":"35","retailSalesPerMonth":"55"},
{"brandName":"Gibson","gearShiftedPerMonth":"23","retailSalesPerMonth":"43"},
{"brandName":"Epiphone","gearShiftedPerMonth":"10","retailSalesPerMonth":"13"}]
STOCK DATA:
[{"brandName":"Gibson","stockValue":"1234"},
{"brandName":"Fender","stockValue":"975"},
{"brandName":"Epiphone","stockValue":"834"}]
Obviously the actual figures are made up in that example!
Now, what I need to do is to combine those to create this:
COMBINED DATA
[{"brandName":"Fender","gearShiftedPerMonth":"35","retailSalesPerMonth":"55","stockValue":"975"},
{"brandName":"Gibson","gearShiftedPerMonth":"23","retailSalesPerMonth":"43","stockValue":"1234"},
{"brandName":"Epiphone","gearShiftedPerMonth":"10","retailSalesPerMonth":"13","stockValue":"834"}]
What we have there is the Sales Dataset combined with Stock Dataset to add the additional data of stockValue added to the corresponding brandName record.
I have tried using $.extend but I can't figure out how to use it in this situation.
It is perhaps important to note that the data pairs might not necessarily be in the right order, and it is possible, though unlikely, that there might not be a match, so some kind of zeroing error catching must be implemented.
What you'll need to do first is transform the data into two objects, whose properties are the values you want to merge together:
{
"Fender" : {"gearShiftedPerMonth":"35","retailSalesPerMonth":"55"},
"Gibson" : {"gearShiftedPerMonth":"23","retailSalesPerMonth":"43"},
"Epiphone" : {"gearShiftedPerMonth":"10","retailSalesPerMonth":"13"}
}
and
{
"Gibson": {"stockValue":"1234"},
"Fender": { "stockValue":"975"},
"Epiphone": { "stockValue":"834"}
}
Once the transformation is done, you'll have two objects that you can merge using $.extend or other functions.
Update
For large sets, this gives results in nearly linear time:
var salesa = {}, stocka = {};
$.each(sales, function(i, e) {
salesa[e.brandName] = e;
});
$.each(stock, function(i, e) {
stocka[e.brandName] = e;
});
var combine = {};
$.extend(true, combine, salesa, stocka)
More speed can be tweaked if the merging happened during the second transformation callback ($each(stock...) instead of a separate call to $.extend() but it loses some of its obviousness.
I think what's he's trying to do is join the two datasets as if they were tables, joining by the brandName. From what I've been testing jQuery's $.extend() function does not take care of that, but merges objects according to their index in the Object arrays that it receives.
I think the matching of the key would need to be done manually.
stock = [{"brandName":"Fender","gearShiftedPerMonth":"35","retailSalesPerMonth":"55"},
{"brandName":"Gibson","gearShiftedPerMonth":"23","retailSalesPerMonth":"43"},
{"brandName":"Epiphone","gearShiftedPerMonth":"10","retailSalesPerMonth":"13"}];
value = [{"brandName":"Gibson","stockValue":"1234"},
{"brandName":"Fender","stockValue":"975"},
{"brandName":"Epiphone","stockValue":"834"}];
var results = [];
$(stock).each(function(){
datum1 = this;
$(value).each(function() {
datum2 = this;
if(datum1.brandName == datum2.brandName)
results.push($.extend({}, datum1, datum2));
});
});
Which would result in:
[{"brandName":"Fender","gearShiftedPerMonth":"35","retailSalesPerMonth":"55","stockValue":"975"},
{"brandName":"Gibson","gearShiftedPerMonth":"23","retailSalesPerMonth":"43","stockValue":"1234"},
{"brandName":"Epiphone","gearShiftedPerMonth":"10","retailSalesPerMonth":"13","stockValue":"834"}]
Instead of what the use of $.extend() returns:
[{"brandName":"Gibson","gearShiftedPerMonth":"35","retailSalesPerMonth":"55","stockValue":"1234"},
{"brandName":"Fender","gearShiftedPerMonth":"23","retailSalesPerMonth":"43","stockValue":"975"},
{"brandName":"Epiphone","gearShiftedPerMonth":"10","retailSalesPerMonth":"13","stockValue":"834"}]
If your example code reflects reality, then jQuery's $.extend will be the wrong tool for this.
It blindly copies data from one object to another. Notice that the order of your data is not consistent. The SALES DATA has Fender first, while the STOCK DATA has gibson first.
So jQuery's $.extend is mixing the two results. The "gearShifted" and "retailSales" for Fender is ending up with the "brandName" and "stockValue" for Gibson.
What you'll need is to iterate one array, and look up the "brandName" in the other, and then copy over the data you want. You could use $.extend for that part of it if you like...
var sales_data =
[{"brandName":"Fender","gearShiftedPerMonth":"35","retailSalesPerMonth":"55"},
{"brandName":"Gibson","gearShiftedPerMonth":"23","retailSalesPerMonth":"43"},
{"brandName":"Epiphone","gearShiftedPerMonth":"10","retailSalesPerMonth":"13"}]
var stock_data =
[{"brandName":"Gibson","stockValue":"1234"},
{"brandName":"Fender","stockValue":"975"},
{"brandName":"Epiphone","stockValue":"834"}]
var combined = $.map(sales_data, function(obj, i) {
return $.extend({}, obj, $.grep(stock_data, function(stock_obj) {
return obj.brandName === stock_obj.brandName
})[0]);
});
Note that this is not terribly efficient, but unless the data set is enormous, it shouldn't be an issue.
DEMO: http://jsfiddle.net/sDyKx/
RESULT:
[
{
"brandName": "Fender",
"gearShiftedPerMonth": "35",
"retailSalesPerMonth": "55",
"stockValue": "975"
},
{
"brandName": "Gibson",
"gearShiftedPerMonth": "23",
"retailSalesPerMonth": "43",
"stockValue": "1234"
},
{
"brandName": "Epiphone",
"gearShiftedPerMonth": "10",
"retailSalesPerMonth": "13",
"stockValue": "834"
}
]
In vanilla javascript you can do:
var sales = [{"brandName":"Fender","gearShiftedPerMonth":"35","retailSalesPerMonth":"55"},
{"brandName":"Gibson","gearShiftedPerMonth":"23","retailSalesPerMonth":"43"},
{"brandName":"Epiphone","gearShiftedPerMonth":"10","retailSalesPerMonth":"13"}];
var stock = [{"brandName":"Gibson","stockValue":"1234"},
{"brandName":"Fender","stockValue":"975"},
{"brandName":"Epiphone","stockValue":"834"}];
var combined = stock.slice(0);
for (var i = 0; i < stock.length; i++) {
for (var j = 0; j < sales.length; j++) {
if (stock[i].brandName === sales[j].brandName) {
for (var attrname in sales[j]) { combined[i][attrname] = sales[j][attrname]; }
}
}
}
JSON.stringify(combined)
produces
[
{"brandName":"Gibson","stockValue":"1234","gearShiftedPerMonth":"23","retailSalesPerMonth":"43"},
{"brandName":"Fender","stockValue":"975","gearShiftedPerMonth":"35","retailSalesPerMonth":"55"},
{"brandName":"Epiphone","stockValue":"834","gearShiftedPerMonth":"10","retailSalesPerMonth":"13"}
]

Categories