Couchbase view custom reduce - javascript

i'm not sure if the title of the questions fits, if you know a better one, let me know ;)
I just named it like this, because i'm thinking if i could solve my problem with a custom reduce function.
I have two types of objects:
Vehicles:
{
"id": "1G1JC5444R7252367",
"type": "Vehicle"
}
Users:
{
"company": "companyname",
"type": "User",
"parts": [
{
"company": "companyname",
"id": "1G1JC5444R7252367",
"active": true
},
{
"company": "companyname",
"id": "1G1135644R7252367",
"active": false
}
]
}
What i want is a View which returns me all vehicles of a certain company. But the company is only stored in the User object.
This is how far I got in the mapfunction:
function (doc, meta) {
if(doc.type == 'User'){
if(doc.parts){
Array.prototype.contains = function ( needle ) {
for (var i in this) {
if (this[i] == needle) return true;
}
return false;
};
var ids = new Array(doc.parts.length);
for(var k in doc.parts){
if(doc.parts[k].active) {
if(!vins.contains(doc.parts[k].id)) {
if (doc.parts[k].company && doc.parts[k].id ) {
ids.push(doc.parts[k].id);
emit(doc.parts[k].company, doc.parts[k].id);
}
}
}
}
}
}
}
But this only returns me the company as key and the id of the vehicle as value. So i get a User document. Can I somehow loop through the documents again in the map function and get all vehicles according to the ids in my ids array?
Saving the company in the vehicle itself also is not desired, because the company is not the vehicles company itself but the company of the parts.
Thanks for any help in forward.

A Couchbase view can only operate on the document presented to it. As you discovered, it can only partially do what you want.
The real problem isn't the view though but is your data model. You appear to have designed your data model as if you were using a relational database. The calculation you are attempting is a kind of join.
A fundamental concept with document databases is that a document should represent all of the information pertinent to some kind of event. This concept is what allows document databases to horizontally scale. You should not worry about data duplication. Locality of access is the key to an appropriate map-reduce data model.
I would redesign your data model.

Related

Retrieving data sorted by two child values [duplicate]

{
"movies": {
"movie1": {
"genre": "comedy",
"name": "As good as it gets",
"lead": "Jack Nicholson"
},
"movie2": {
"genre": "Horror",
"name": "The Shining",
"lead": "Jack Nicholson"
},
"movie3": {
"genre": "comedy",
"name": "The Mask",
"lead": "Jim Carrey"
}
}
}
I am a Firebase newbie. How can I retrieve a result from the data above where genre = 'comedy' AND lead = 'Jack Nicholson'?
What options do I have?
Using Firebase's Query API, you might be tempted to try this:
// !!! THIS WILL NOT WORK !!!
ref
.orderBy('genre')
.startAt('comedy').endAt('comedy')
.orderBy('lead') // !!! THIS LINE WILL RAISE AN ERROR !!!
.startAt('Jack Nicholson').endAt('Jack Nicholson')
.on('value', function(snapshot) {
console.log(snapshot.val());
});
But as #RobDiMarco from Firebase says in the comments:
multiple orderBy() calls will throw an error
So my code above will not work.
I know of three approaches that will work.
1. filter most on the server, do the rest on the client
What you can do is execute one orderBy().startAt()./endAt() on the server, pull down the remaining data and filter that in JavaScript code on your client.
ref
.orderBy('genre')
.equalTo('comedy')
.on('child_added', function(snapshot) {
var movie = snapshot.val();
if (movie.lead == 'Jack Nicholson') {
console.log(movie);
}
});
2. add a property that combines the values that you want to filter on
If that isn't good enough, you should consider modifying/expanding your data to allow your use-case. For example: you could stuff genre+lead into a single property that you just use for this filter.
"movie1": {
"genre": "comedy",
"name": "As good as it gets",
"lead": "Jack Nicholson",
"genre_lead": "comedy_Jack Nicholson"
}, //...
You're essentially building your own multi-column index that way and can query it with:
ref
.orderBy('genre_lead')
.equalTo('comedy_Jack Nicholson')
.on('child_added', function(snapshot) {
var movie = snapshot.val();
console.log(movie);
});
David East has written a library called QueryBase that helps with generating such properties.
You could even do relative/range queries, let's say that you want to allow querying movies by category and year. You'd use this data structure:
"movie1": {
"genre": "comedy",
"name": "As good as it gets",
"lead": "Jack Nicholson",
"genre_year": "comedy_1997"
}, //...
And then query for comedies of the 90s with:
ref
.orderBy('genre_year')
.startAt('comedy_1990')
.endAt('comedy_2000')
.on('child_added', function(snapshot) {
var movie = snapshot.val();
console.log(movie);
});
If you need to filter on more than just the year, make sure to add the other date parts in descending order, e.g. "comedy_1997-12-25". This way the lexicographical ordering that Firebase does on string values will be the same as the chronological ordering.
This combining of values in a property can work with more than two values, but you can only do a range filter on the last value in the composite property.
A very special variant of this is implemented by the GeoFire library for Firebase. This library combines the latitude and longitude of a location into a so-called Geohash, which can then be used to do realtime range queries on Firebase.
3. create a custom index programmatically
Yet another alternative is to do what we've all done before this new Query API was added: create an index in a different node:
"movies"
// the same structure you have today
"by_genre"
"comedy"
"by_lead"
"Jack Nicholson"
"movie1"
"Jim Carrey"
"movie3"
"Horror"
"by_lead"
"Jack Nicholson"
"movie2"
There are probably more approaches. For example, this answer highlights an alternative tree-shaped custom index: https://stackoverflow.com/a/34105063
If none of these options work for you, but you still want to store your data in Firebase, you can also consider using its Cloud Firestore database.
Cloud Firestore can handle multiple equality filters in a single query, but only one range filter. Under the hood it essentially uses the same query model, but it's like it auto-generates the composite properties for you. See Firestore's documentation on compound queries.
I've written a personal library that allows you to order by multiple values, with all the ordering done on the server.
Meet Querybase!
Querybase takes in a Firebase Database Reference and an array of fields you wish to index on. When you create new records it will automatically handle the generation of keys that allow for multiple querying. The caveat is that it only supports straight equivalence (no less than or greater than).
const databaseRef = firebase.database().ref().child('people');
const querybaseRef = querybase.ref(databaseRef, ['name', 'age', 'location']);
// Automatically handles composite keys
querybaseRef.push({
name: 'David',
age: 27,
location: 'SF'
});
// Find records by multiple fields
// returns a Firebase Database ref
const queriedDbRef = querybaseRef
.where({
name: 'David',
age: 27
});
// Listen for realtime updates
queriedDbRef.on('value', snap => console.log(snap));
var ref = new Firebase('https://your.firebaseio.com/');
Query query = ref.orderByChild('genre').equalTo('comedy');
query.addValueEventListener(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
for (DataSnapshot movieSnapshot : dataSnapshot.getChildren()) {
Movie movie = dataSnapshot.getValue(Movie.class);
if (movie.getLead().equals('Jack Nicholson')) {
console.log(movieSnapshot.getKey());
}
}
}
#Override
public void onCancelled(FirebaseError firebaseError) {
}
});
Frank's answer is good but Firestore introduced array-contains recently that makes it easier to do AND queries.
You can create a filters field to add you filters. You can add as many values as you need. For example to filter by comedy and Jack Nicholson you can add the value comedy_Jack Nicholson but if you also you want to by comedy and 2014 you can add the value comedy_2014 without creating more fields.
{
"movies": {
"movie1": {
"genre": "comedy",
"name": "As good as it gets",
"lead": "Jack Nicholson",
"year": 2014,
"filters": [
"comedy_Jack Nicholson",
"comedy_2014"
]
}
}
}
For Cloud Firestore
https://firebase.google.com/docs/firestore/query-data/queries#compound_queries
Compound queries
You can chain multiple equality operators (== or array-contains) methods to create more specific queries (logical AND). However, you must create a composite index to combine equality operators with the inequality operators, <, <=, >, and !=.
citiesRef.where('state', '==', 'CO').where('name', '==', 'Denver');
citiesRef.where('state', '==', 'CA').where('population', '<', 1000000);
You can perform range (<, <=, >, >=) or not equals (!=) comparisons only on a single field, and you can include at most one array-contains or array-contains-any clause in a compound query:
Firebase doesn't allow querying with multiple conditions.
However, I did find a way around for this:
We need to download the initial filtered data from the database and store it in an array list.
Query query = databaseReference.orderByChild("genre").equalTo("comedy");
databaseReference.addValueEventListener(new ValueEventListener() {
#Override
public void onDataChange(#NonNull DataSnapshot dataSnapshot) {
ArrayList<Movie> movies = new ArrayList<>();
for (DataSnapshot dataSnapshot1 : dataSnapshot.getChildren()) {
String lead = dataSnapshot1.child("lead").getValue(String.class);
String genre = dataSnapshot1.child("genre").getValue(String.class);
movie = new Movie(lead, genre);
movies.add(movie);
}
filterResults(movies, "Jack Nicholson");
}
}
#Override
public void onCancelled(#NonNull DatabaseError databaseError) {
}
});
Once we obtain the initial filtered data from the database, we need to do further filter in our backend.
public void filterResults(final List<Movie> list, final String genre) {
List<Movie> movies = new ArrayList<>();
movies = list.stream().filter(o -> o.getLead().equals(genre)).collect(Collectors.toList());
System.out.println(movies);
employees.forEach(movie -> System.out.println(movie.getFirstName()));
}
The data from firebase realtime database is as _InternalLinkedHashMap<dynamic, dynamic>.
You can also just convert this it to your map and query very easily.
For example, I have a chat app and I use realtime database to store the uid of the user and the bool value whether the user is online or not. As the picture below.
Now, I have a class RealtimeDatabase and a static method getAllUsersOnineStatus().
static getOnilineUsersUID() {
var dbRef = FirebaseDatabase.instance;
DatabaseReference reference = dbRef.reference().child("Online");
reference.once().then((value) {
Map<String, bool> map = Map<String, bool>.from(value.value);
List users = [];
map.forEach((key, value) {
if (value) {
users.add(key);
}
});
print(users);
});
}
It will print [NOraDTGaQSZbIEszidCujw1AEym2]
I am new to flutter If you know more please update the answer.
ref.orderByChild("lead").startAt("Jack Nicholson").endAt("Jack Nicholson").listner....
This will work.

Filtering Firebase by multiple fields

I am trying to get filter Firebase using multiple fields. This is more or less my object in Firebase:
{
"id": "-id",
"category": "History",
"level": "High School",
"pointAmount": 128,
"pointBoost": 0,
"photoURL": "link"
},
{
"id": "-id",
"category": "Physics",
"level": "Primary School",
"pointAmount": 128,
"pointBoost": 0,
"photoURL": "link"
}
What I'm doing now, is using an array of checkboxes in React to grab the level and category to filter by. This part is done. My question is, how can I filter the elements coming in from the database? This is how I'm doing it right now:
componentDidMount() {
const assignmentsRef = firebase
.database()
.ref('Works')
.orderByChild('available')
.equalTo(true)
.limitToFirst(9);
assignmentsRef.on('value', snapshot => {
let assignments = snapshot.val();
let newState = [];
for (let assignment in assignments) {
newState.push({
id: assignment,
category: assignments[assignment].category,
level: assignments[assignment].level,
pointAmount: assignments[assignment].pointAmount,
pointBoost: assignments[assignment].pointBoost,
photoURL: assignments[assignment].photoURL,
workText: assignments[assignment].workText,
});
}
this.setState({
assignments: newState
});
});
}
So as you can see, I'm already doing orderByChild. Also there will be multiple variables which to filter by. For example: If I select History, and Physics I will get both objects. Same if I select History and Primary School, but if I select Physics I should only get the second object. How can I filter it? There will be over 10 filters.
It looks like you're trying to do an OR of both conditions. There isn't any built-in support for returning items that match one of a number of conditions. You will have to fire a separate query for each condition, and then merge the results from all queries client-side. This is not as slow as you may expect, since Firebase will pipeline the queries over a single connection.

Limiting fields returned by mongo dynamically

I use Meteor to query a mongo collection. It has for example the following entry:
{
"_id": "uCfwxKXyZygcWQeiS",
"gameType": "foobar",
"state": "starting",
"hidden": {
"correctAnswer": "secret",
"someOtherStuff": "foobar"
},
"personal": {
"Y73uBhuDq2Bhk4d8W": {
"givenAnswer": "another secret",
},
"hQphob8s92gbEMXbY": {
"givenAnswer": "i have no clue"
}
}
}
What I am trying to do now is:
don't return the values behind "hidden"
from the "personal" embedded document only return the values for the asking user
In code it would look something like this:
Meteor.publish('game', function() {
this.related(function(user) {
var fields = {};
fields.hidden = 0;
fields.personal = 0;
fields['personal.' + this.userId] = 1;
return Games.find({}, {fields: fields});
}, Meteor.users.find(this.userId, {fields: {'profile.gameId': 1}}));
}
Obviously this won't work, because MongoDB won't allow mixed includes and excludes. On the other hand, I cannot switch to "specify only the included fields", because they can vary from gameType to gameType and it would become a large list.
I really hope that you can help me out of this. What can I do to solve the problem?
Typical example of where to use the directly controlled publication features (the this.added/removed/changed methods).
See the second example block a bit down the page at http://docs.meteor.com/api/pubsub.html#Meteor-publish.
With this pattern you get complete control of when and what to publish.

Retrieving only the parent elements from firebase : Angularfire

The image shows the structure of my database.
I want to print 1, 2 ... (so on) i.e. the parent element names alone. But couldn't understand how to do that.
The Firebase Database is essentially one JSON object.
This object is in a tree structure. If you read from one location in the tree, you'll get each piece of data underneath it.
Take a look at this sample database.
{
"items": {
"1": {
"title": "Hi"
},
"2": {
"title": "Bye"
}
}
}
There is no way with the JavaScript SDK or AngularFire, to only read the parent keys of 1 and 2 under "items".
If you only want to read the parent keys, you'll need to create an index for them in the Firebase database.
{
"items": {
"1": {
"title": "Hi"
},
"2": {
"title": "Bye"
}
},
"itemKeys": {
"1": "Hi",
"2": "Bye"
}
}
Now you can create a reference at the itemKeys location and pass that to a $firebaseArray() or $firebaseObject().
var ref = new Firebase('<my-firebase-app>.firebaseio.com/itemKeys');
var syncArray = $firebaseArray(ref);
If you're concerned with keeping two separate data structures consistent, check out the client-side fan-out feature.
shallow=true
If you are using REST API add this to the end of your request url. Like this
https://docs-examples.firebaseio.com/rest/retrieving-data.json?shallow=true

Constructing a tree from an array

I am a beginner, prompt algorithm for constructing a tree from an array of the form:
var data = [
{id: 1,level:1,left_key:1,right_key:12, caption: "Books"},
{id: 2,level:2,left_key:2,right_key:11, caption: "Programming"},
{id: 3,level:3,left_key:3,right_key:4, caption: "Languages"},
{id: 4,level:3,left_key:5,right_key:10, caption: "Databases"},
{id: 5,level:4,left_key:6,right_key:7, caption: "MongoDB"},
{id: 6,level:4,left_key:8,right_key:9, caption: "dbm"}
];
The data format is taken from here link.
From the data in this format on request from the Mongodb database to be built tree species:
<ol>
<li>
<span> Books </span>
<ol>
<li>
<span> Programming </span>
</li>
</ol>
</li>
</ol>
I can not understand the principle of tree traversal.
P.S. I'd like to do without the third-party libraries
I guess you need something like this
function make_tree() {
var tree = document.getElementById("tree");
data.forEach(function(elem){
var elem_parent = document.getElementById("tree_" + elem.level);
if(elem_parent){
var cur_element = document.createElement("li");
cur_element.setAttribute('id','tree_' + elem.level + "_" + elem.id);
cur_element.innerHTML = "<span>" + elem.caption + "</span>";
elem_parent.appendChild(cur_element);
}
else {
var cur_parent = document.createElement("ol");
cur_parent.setAttribute('id','tree_' + elem.level);
var cur_element = document.createElement("li");
cur_element.setAttribute('id','tree_' + elem.level + "_" + elem.id);
cur_element.innerHTML = "<span>" + elem.caption + "</span>";
cur_parent.appendChild(cur_element);
parent_before = document.getElementById("tree_" + (elem.level - 1));
if (parent_before) parent_before.appendChild(cur_parent);
else tree.appendChild(cur_parent);
}
});
}
EXAMPLE FIDDLE
The better answer to this is to simply store the tree structure within your MongoDB document:
{
"0": {
"caption": "Books",
"items": {
"0": {
"caption": "Programming",
"items": {
"0": {
"caption": "Languages",
},
"1": {
"caption": "Databases",
"items": {
"0": {
"caption": "MongoDB"
},
"1": {
"caption": "dbm"
}
}
}
}
}
}
}
}
That has a natural order to it and provides in a single read a very easy way to get a structure that can be traversed in the concept of building a menu.
It is also very simple to update, so to add a new item to, say the "Languages" level, then you have a clear path to update:
db.collection.update(
{},
{
"$set": {
"0.items.0.items.0.items.0.caption": "C++"
}
}
)
And to add another:
db.collection.update(
{},
{
"$set": {
"0.items.0.items.0.items.1.caption": "Pascal"
}
}
)
Now this as with all tree structures does require some knowledge of the level you are inserting at. The design here ( and horribly over-engineered ) is at least allowing easy updates. It is a structure that would be notoriously bad to query but that is not the point.
But of course the limitation here is for "small" tree structures (which is still under 16MB and a quite a lot of tree), but for anything that was particularly large that is where larger model implementations come in
The thing with processing trees in the way as presented in the sample data is that each node needs to be pulled in one at at time. So exactly as stated in the documentation you are pulling results like this:
var databaseCategory = db.categories.findOne( { _id: "Databases" } );
db.categories.find({
left: { $gt: databaseCategory.left },
right: { $lt: databaseCategory.right }
});
So there is a a way of pulling all the children out from a given node, but now consider what you need to do in order to add any new items:
So in order to add something in the languages node as was done earlier, not only are you dealing with inserting a new value, but also updating all the other node values in the tree to represent there position as shown in the example documentation.
There are ways around that by "bucketing" the values on nodes in order to allow for insertion and removal, but much as the whole subject of modelling tree structures like this, is way out of scope for an answer on this site.
So for your given use case of producing a menu, you are better off using the native data structure features of your storage and simply storing and calling the data with a native structure to how you are going to use it.

Categories