Query in Firebase by value of childs [duplicate] - firebase

This question already has answers here:
Firebase search by child value
(2 answers)
Closed 3 years ago.
{
"users" : {
"1320104182" : {
"datas" : {
"bio" : "some_data",
"picture" : "some_data",
"username" : "some_data",
"website" : "some_data",
"followers" : 14,
}
},
"3271376571" : {
"datas" : {
"bio" : "some_data",
"picture" : "some_data",
"username" : "some_data",
"website" : "some_data",
"followers" : 10,
}
}
}
}
I'm new to Firebase and i'm trying to do multiple think here without any success so far.
How can i retrieve a user by his "username" without knowing the key ?
Or how can i order the users by followers ?
I tried everything i could find in the documentation for a few hours i'm desperate.

This seems fairly easy:
var ref = firebase.database().ref("users");
var query = ref.orderByChild("database/username").equalTo("some_data");
query.once("value", function(snapshot) {
snapshot.forEach(function(child) {
console.log(child.key, child.val().bio);
});
});

Firebase is not so good when you need fancy queries. You must handle everything in your client (JavaScript) which is not the best approach when dealing with large data. In this case, I'd suggest you something like this:
const nameToSearch = 'John';
firebase.ref('users').once('value') //get all content from your node ref, it will return a promise
.then(snapshot => { // then get the snapshot which contains an array of objects
snapshot.val().filter(user => user.name === nameToSearch) // use ES6 filter method to return your array containing the values that match with the condition
})
To order by followers, you can either also apply sort() (see example 1) or any of firebase default methods orderByChild() (see example 2), orderByKey (see example 3), or orderByValue (see example 4)
Example 1:
firebase.database().ref("users").once('value')
.then(snapshot => {
const sortedUsers = snapshot.sort((userA, userB) => {
if (userA.name < userB.name) {
return -1;
}
if (userA.name > userB.name) {
return 1;
}
return 0;
})
})
Example 2:
var ref = firebase.database().ref("dinosaurs");
ref.orderByChild("height").on("child_added", function(snapshot) {
console.log(snapshot.key + " was " + snapshot.val().height + " m tall");
});
Example 3:
var ref = firebase.database().ref("dinosaurs");
ref.orderByKey().on("child_added", function(snapshot) {
console.log(snapshot.key);
});
Example 4:
var scoresRef = firebase.database().ref("scores");
scoresRef.orderByValue().limitToLast(3).on("value", function(snapshot) {
snapshot.forEach(function(data) {
console.log("The " + data.key + " score is " + data.val());
});
});
Note: there might be typos in the examples, I wrote just to show you the idea of the concepts.
Check the following docs for more info:
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/filter
https://firebase.google.com/docs/reference/js/firebase.database.Query#orderByChild
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort
Hope it helps

Related

Firebase database transactional search and update

I have a collection in firebase real time database that is a pool of codes that can be used once per 'store'. I need to search for an unused code, then mark it reserved by a store in an atomic fashion. The problem is I can't figure out how to do a transactional search and update in firebase, and the unused code is being 'used' multiple times until it gets updated.
const getUnusedCode = (storeID) => {
const codeRef = rtdb.ref('codes');
return codeRef
.orderByChild(storeID)
.equalTo(null)
.limitToFirst(1)
.once('child_added')
.then(snap => {
//setting the map {[storeID]:true} reserves the code
return snap.ref.update({ [storeID]: true }).then(() => {
return snap.key;
});
});
};
Edit: Here is the structure of the 'codes' collection:
{
"-LQl9FFD39PAeN5DnrGE" : {
"code" : 689343821901,
"i" : 0,
"5s6EgdItKW7pBIawgulg":true,
"ZK0lFbDnXcWJ6Gblg0tV":true,
"uKbwxPbZu2fJlsn998vm":true
},
"-LQl9FOxT4eq6EbwrwOx" : {
"code" : 689343821918,
"i" : 1,
"5s6EgdItKW7pBIawgulg":true
},
"-LQl9FPaUV33fvkiFtv-" : {
"code" : 689343821925,
"i" : 2
},
"-LQl9FQEwKKO9T0z4LIP" : {
"code" : 689343821932,
"i" : 3,
"ZK0lFbDnXcWJ6Gblg0tV":true
},
"-LQl9FQsEVSNZyhgdHmI" : {
"code" : 689343821949,
"i" : 4,
"5s6EgdItKW7pBIawgulg":true,
"uKbwxPbZu2fJlsn998vm":true
}
}
In this data, "5s6EgdItKW7pBIawgulg" is a store id, and true means this code has been used for this store
When new items are being imported, this function may get called hundres of times a minute, and is returning duplicates since it's not an atomic search-then-update. Is this possible in Firebase?
From what I understand you have a structure like this
codes: {
"code1": {
storeid: "store1"
},
"code2": {
storeid: "store2"
}
}
And you're trying to transactionally update it per store.
If this is the only update you're trying to do, I'd highly recommend inverting your data structure:
codes: {
"store1": "code1",
"store2": "code2"
}
On this structure the transaction for a store is quite simple, since the path is known:
var storeRef = firebase.database().ref("codes").child("store1");
storeRef.transation(function(current) {
if (current) {
// remove the code from the database
return null;
}
else {
// abort the transaction, since the code no longer exists
return undefined;
}
});
If you can't change the data structure, I'd probably user your current code to find the DatabaseReference to the code, and then use a transaction within the callback to update:
codeRef
.orderByChild(storeID)
.equalTo(null)
.limitToFirst(1)
.once('child_added')
.then(snap => {
//setting the map {[storeID]:true} reserves the code
return snap.ref.transaction(function(current) {
if (!current || current[storeId]) {
// the node no longer exists, or it already was claimed for this store
return undefined; // abort the transaction
}
else {
current[storeId] = true;
return current;
}
})
});

Firebase: combine query and child event listener

I have a structure similar to this
{
posts : {
post1Key: {
title : "First Post",
comments : {
comment1Key : {},
comment2Key : {},
comment3Key : {}
}
}
}
...
}
And I'm using the "child updated" events on the key "/posts/post1Key/comments" to listen for new comments.
How can I combine Firebase Query and the event listeners.
Edit: I know this is possible with Firebase, however, I'm using nativescript-plugin-firebase
You can very well combine a Query defined with an orderBy... method and a listener with the on() method: see detailed doc here
For example you could do something like
var query = db.ref('node1').orderByChild('firstName');
query.on('value', function(dataSnapshot) {
dataSnapshot.forEach(function(childSnapshot) {
var childKey = childSnapshot.key;
console.log(childKey);
var childData = childSnapshot.val();
console.log(childData);
});
});
Each time a new sub node is added under "node1" you will get the children list ordered by firstName
As at the time of writing, this isn't possible using the nativescript-plugin-firebase. The workaround is to do the sorting/filtering in the eventHandler after you have received the data.
var onChildEvent = function(result) {
//
// Sort: result.value here
//
};
// listen to changes in the /users path
firebase.addChildEventListener(onChildEvent, "/users").then(
function(listenerWrapper) {
var path = listenerWrapper.path;
var listeners = listenerWrapper.listeners; // an Array of listeners added
// you can store the wrapper somewhere to later call 'removeEventListeners'
}
);

firebase realtime database query

I have the following structure:
{
"Campaign" : {
"-KtghP_NMOFrjN_RrI6f" : {
"Projects" : {
"-Kz5g4j8dKgxtqQsPfN1" : {
"createByName" : "Michal",
"profileId" : "-KtlDwI3Bq4Bi7R23kya"
},
"-KzGCaLblTxzu4Nje15Z" : {
"createByName" : "Roy",
"profileId" : "-Kxx_egu9h4GOrxqM1nB"
}
}
},
"-KyjE0HPNSg27Kpurq8l" : {
"Projects" : {
"-KzBUZBsI947HckV296O" : {
"createByName" : "Roy",
"profileId" : "-Kxx_egu9h4GOrxqM1nB"
}
}
}
},
"UserProfile" : {
"-Kxx_egu9h4GOrxqM1nB" : {
"MyProjects" : {
"-KzGC3Yn4bAAorcwDhUT" : {
"CampaignId" : "-KyjE0HPNSg27Kpurq8l",
"projectId" : "-KzBUZBsI947HckV296O"
},
"-KzGCaeTqMm_g1Jq6u6i" : {
"CampaignId" : "-KtghP_NMOFrjN_RrI6f",
"projectId" : "-KzGCaLblTxzu4Nje15Z"
}
},
"firstName" : "roy"
}
}
}
i have the profile id.
I need to get the different projects of the user, under the different Campaigns..
I want to get the list of MyProjects by the userid, and then iterate (with the keys i got from MyProjects) over the campaigns -(key)-> projects -(key)-> profileId and compare them...
(hope it was clear enough...)
HOW DO I DO THAT?
Ok, after some more research, i went a different way.
it seems like i don't need "MyProjects".
var userId = "-Kxx_egu9h4GOrxqM1nB";
var campaignRef = this.db.app.database().ref('Campaign'); //root
var projectsRef = campaignRef.child('Projects');
projectsRef.orderByChild('profileId').equalTo(userId).once("value", (snap) => {
console.log(snap.val());
});
but still i get NULL..
(tried "child_added" and .on in different combinations - nothing...)
After logging in, and pulling the Campaign object, you can try running the following code:
// user is an object from the userProfile. You can also just directly access the object and get what you need. Really, it's up to you. I'll assume that you also stored the user ID (uid) here too.
var user = {},
campaigns = {},
uid = {},
myProjects = [];
function getProjects() {
for (var i in campaigns) {
for (var k in campaigns[i]) {
// Make sure that the profileId of the project and the uid match, then push object into myProjects array
campaigns[i][k].profileId === uid ? myProjects.push(campaigns[i][k]) : 'nada';
}
}
}
In the myProjects array will you find your projects.

How to query two types of records in CouchDB

I’m having issues getting two dependant types of data from a PouchDB database.
I have a list of cars that I get like so:
localDB.query(function(doc) {
if (doc.type === ‘list’) {
emit(doc);
}
}, {include_docs : true}).then(function(response) {
console.log(“cars”, response);
// Save Cars List to app
for(var i = 0; i < response.rows.length; i++) {
addToCarsList(response.rows[i].id, response.rows[i].carNumber);
}
console.log(“Cars List: " + carsListToString());
return response;
}).then(function(listRecord) {
listRecord.rows.forEach(function(element, index){
console.log(index + ' -> ', element);
localDB.query(function(doc) {
console.log("filtering with carNb = " + element.carNb);
if (doc.type === 'defect' && doc.listId == getCurrentListId() && doc.carNb == element.carNb ) {
emit(doc);
}
}, {include_docs : false}).then(function(result){
console.log("defects", result);
}).catch(function(err){
console.log("an error has occurred", err);
});
});
}).catch(function(err) {
console.log('error', err);
});
Here's what happens. After getting the list of cars, then for each cars I would like to query the defects and store then in some arrays. Then when all that querying is done, I want to build the UI with the data saved.
But what's happening is that the forEach gets processed quickly and does not wait for the inner async'd localDb.query.
How can I query some documents based on an attribute from a parent query? I looked into promises in the PouchDB doc but I can't understand how to do it.
(please forget about curly quotes and possible lint errors, this code was anonymized by hand and ultra simplified)
The method you are looking for is Promise.all() (execute all promises and return when done).
However, your query is already pretty inefficient. It would be better to create a persistent index, otherwise it has to do a full database scan for every query() (!). You can read up on the PouchDB query guide for details.
I would recommend installing the pouchdb-upsert plugin and then doing:
// helper method
function createDesignDoc(name, mapFunction) {
var ddoc = {
_id: '_design/' + name,
views: {}
};
ddoc.views[name] = { map: mapFunction.toString() };
return ddoc;
}
localDB.putIfNotExists(createDesignDoc('my_index', function (doc) {
emit([doc.type, doc.listId, doc.carNb]);
})).then(function () {
// find all docs with type 'list'
return localDB.query('my_index', {
startkey: ['list'],
endkey: ['list', {}],
include_docs: true
});
}).then(function (response) {
console.log("cars", response);
// Save Cars List to app
for(var i = 0; i < response.rows.length; i++) {
addToCarsList(response.rows[i].id, response.rows[i].carNumber);
}
console.log("Cars List: " + carsListToString());
return response;
}).then(function (listRecord) {
return PouchDB.utils.Promise.all(listRecord.rows.map(function (row) {
// find all docs with the given type, listId, carNb
return localDB.query('my_index', {
key: ['defect', getCurrentListId(), row.doc.carNb],
include_docs: true
});
}));
}).then(function (finalResults) {
console.log(finalResults);
}).catch(function(err){
console.log("an error has occurred", err);
});
I'm using a few tricks here:
emit [doc.type, doc.listId, doc.carNb], which allows us to query by type or by type+listId+carNb.
when querying for just the type, we can do {startkey: ['list'], endkey: ['list', {}]}, which matches just those with the type "list" because {} is the "higher" than strings in CouchDB object collation order.
PouchDB.utils.Promise is a "hidden" API, but it's pretty safe to use if you ask me. It's unlikely we'll change it.
Edit Another option is to use the new pouchdb-find plugin, which offers a simplified query API designed to replace the existing map/reduce query() API.
Another approach would be to pull both the list docs and the defect docs down at the same time then merge them together using a reduce like method that will convert them into an array of objects:
{
_id: 1,
type: 'list',
...
defects: [{
type: 'defect'
listId: 1
...
}]
}
By pulling the list and the defects down in one call you save a several calls to the pouchdb query engine, but you do have to iterate through every result to build your collection of lists objects with and embedded array of defects.
// This is untested code so it may not work, but you should get the idea
var _ = require('underscore');
// order documents results by list then defect
var view = function (doc) {
if (doc.type === 'list') {
emit([doc._id, doc.carNumber, 1);
} else if (doc.type === 'defect') {
emit([doc.listId, doc.carNb, 2])
}
}
localDB.query(view, { include_docs: true })
.then(function(response) {
return _(response.rows)
.reduce(function(m, r) {
if (r.key[2] === 1) {
// initialize
r.doc.defects = [];
m.push(r.doc)
return m;
}
if (r.key[2] === 2) {
var list = _(m).last()
if (list._id === r.key[0] && list.carNumber === r.key[1]) {
list.defects.push(r.doc);
}
return m;
}
}, []);
})
.then(function(lists) {
// bind to UI
});
With couch, we found reducing calls to the couch engine to be more performant, but I don't know if this approach is better for PouchDB, but this should work as a solution, especially if you are wanting to embed several collections into one list document.

Fetching denormalized data with firebase, angularFire

I'm trying to fetch all flowers data which belongs to a certain user, in this case simplelogin:69.
I'm starting with fetching all flower keys from the user, like this:
/users/simplelogin:69/flowers/
var ref = new Firebase("https://URL.firebaseio.com/users/"+uid+"/flowers");
var sync = $firebase(ref);
Now im stuck figuring out a clean way to fetch all the flower data by looping thrue every flower key from simplelogin:69 without looping thrue EVERY key in /flowers/ (in example below i only have three flower keys but in production i might have 10k).
I tried FirebaseIndex and firebase-util, but can't get it to work properly. Do anyone have any tips or anything? I've read previous posts here on stack but most seems out of date or not really suited for what im going for. Would really appriciate anything that can be solved with AngularFire.
Kind regards,
Elias
{
"flowers" : {
"-JiU57sFAfQwYtIq-LCl" : {
"image" : "test",
"name" : "test",
"type" : "Roses",
"uid" : "simplelogin:69"
},
"-JiU9-3ajlnFLpyUmBvL" : {
"image" : "dasdasd",
"name" : "sadasdas",
"type" : "Roses",
"uid" : "simplelogin:69"
},
"-JiUF-mioK3jQCYy6ZiG" : {
"image" : "ss",
"name" : "ss",
"type" : "Lilies",
"uid" : "simplelogin:69"
}
},
"users" : {
"simplelogin:69" : {
"flowers" : {
"-JiU57sFAfQwYtIq-LCl" : true,
"-JiU9-3ajlnFLpyUmBvL" : true,
"-JiUF-mioK3jQCYy6ZiG" : true
}
},
"simplelogin:70" : {
},
"simplelogin:71" : {
}
}
}
Got it to work now, thanks to #Kato 's answer on thread:
Firebase data normalized. How should I fetch a collection based on this structure? (tried it before creating this thread but didnt get it to work, so made som small changes and now it works).
Posting the solution for anyone stubling upon the same situation:
$scope.flowers = {};
var flowerRef = new Firebase('https://URL.firebaseio.com/flowers/');
var keyRef = new Firebase('https://URL.firebaseio.com/users/'+checkAuth.auth.uid+'/flowers');
keyRef.on('child_added', function(snap) {
var flowerId = snap.key();
flowerRef.child(flowerId).on('value', function(snap) {
$timeout(function() {
if( snap.val() === null ) {
delete $scope.flowers[flowerId];
}
else {
$scope.flowers[flowerId] = snap.val();
}
});
});
});
keyRef.on('child_removed', function(snap) {
var flowerId = snap.key();
$timeout(function(snap) {
delete $scope.flowers[flowerId];
});
});
This really is a tough issue with Firebase. If you implement a custom factory object for the user's flower list, you could dynamically request new flower data as the list changes.

Resources