How to do filter by keyword in Firebase? - firebase

I have a Firebase database structure like this
{
"5ZhJG1NACDdG9WoNZWrBoYGkIpD3" : {
"Company" : {
"5ZhJG1NACDdG9WoNZWrBoYGkIpD3" : {
"authTime" : 1532061957,
"companyName" : "Scopic Software",
"contactName" : "Hoang Scopic",
"email" : "hoang.trinh#scopicsoftware.com",
"firebaseID" : "5ZhJG1NACDdG9WoNZWrBoYGkIpD3",
"isFirstLogin" : false,
"phoneNumber" : "1234567890",
"schoolName" : "MIT",
"teachers" : {
"aOpjnzHpDiZ7uwQQqJoinGvM9ZD3" : "0"
}
}
}
},
"AhZc9B02goOtZ6qBNhz9W0K6Esg2" : {
"Subscription" : {
"-LHlQ4OhijzzFY5HZOT4" : {
"firebaseID" : "-LHlQ4OhijzzFY5HZOT4",
"period" : {
"endAt" : "1533194625",
"startAt" : "1531985025"
},
"status" : "trial"
}
},
"Teacher" : {
"AhZc9B02goOtZ6qBNhz9W0K6Esg2" : {
"authTime" : 1532061932,
"email" : "hoang.trinhj#gmail.com",
"firebaseID" : "AhZc9B02goOtZ6qBNhz9W0K6Esg2",
"isFirstLogin" : false,
"name" : "Hoang Trinh",
"schoolName" : "HUST",
"subscriptions" : {
"-LHlQ4OhijzzFY5HZOT4" : "0"
}
}
}
},
"aOpjnzHpDiZ7uwQQqJoinGvM9ZD3" : {
"Subscription" : {
"-LHlWnpNZazBC5lpXLi0" : {
"firebaseID" : "-LHlWnpNZazBC5lpXLi0",
"period" : {
"endAt" : "1533196388",
"startAt" : "1531986788"
},
"status" : "trial"
}
},
"Teacher" : {
"aOpjnzHpDiZ7uwQQqJoinGvM9ZD3" : {
"Company" : {
"5ZhJG1NACDdG9WoNZWrBoYGkIpD3" : "0"
},
"authTime" : 1532060884,
"email" : "piavghoang#gmail.com",
"firebaseID" : "aOpjnzHpDiZ7uwQQqJoinGvM9ZD3",
"isFirstLogin" : false,
"subscriptions" : {
"-LHlWnpNZazBC5lpXLi0" : "0"
}
}
}
},
"ooR32SjABdOYEkWX6dzy4fE5Kym1" : {
"Admin" : {
"email" : "admin#test.com",
"firstName" : "Hoang",
"lastName" : "Trinh",
"password" : "123456xx"
},
"isAdmin" : true
}
}
This is data in an existing system, so I can't change its structure.
Now I need to write an API to list these records filtered by "email" attribute there (it's nested inside a key).
How can I do a search for this email?
There is one solution that I thought about. It's just getting the whole json back and process data in code (not using Firebase database query functions).
But I can't do that, because AFTER filtering, I need to do paging also.
In order to make the pagination, I need to use query functions like "sortByChild" or "limitToLast", so I have to use these query functions with filtering.
Thank you.
EDIT:
My current implementation for pagination:
getUsers: async (page, perPage, searchTerm) => {
const db = fbAdmin.db();
let dbRef = await db.ref();
if (searchTerm) {
// TODO: Add filter logic here
}
let totalItems = await dbRef.once('value');
let totalItemsNumber = await Object.keys(totalItems.toJSON()).length;
// Calculate for pagination
let lastItemId;
if ((page - 1) * perPage + 1 > totalItemsNumber) {
lastItemId = null;
} else {
let lastItems = await dbRef.orderByKey().limitToLast((page - 1) * perPage + 1).once('value');
lastItemId = Object.keys(lastItems.toJSON())[0];
}
// Do the pagination
let snap;
let data;
if (lastItemId) {
snap = await dbRef.orderByKey().endAt(lastItemId).limitToLast(perPage).once('value');
data = snap.toJSON();
} else {
data = {};
}
let users = Object.keys(data).map(key => {
if (data[key][KEY]) { // Check if it's an admin
return {
role: TYPE_ADMIN,
email: data[key][KEY].email,
name: data[key][KEY].firstName + " " + data[key][KEY].lastName
}
} else if (data[key][Company.KEY]) { // Check if it's a company member
return {
role: TYPE_COMPANY_MEMBER,
email: data[key][Company.KEY][key].email,
name: data[key][Company.KEY][key].name,
schoolName: data[key][Company.KEY][key].schoolName
}
} else if (data[key][Teacher.KEY]) { // Check if it's a teacher
if (data[key][Teacher.KEY][key][Company.KEY]) { // Check if it's a company teacher
return {
role: TYPE_COMPANY_TEACHER,
email: data[key][Teacher.KEY][key].email,
name: data[key][Teacher.KEY][key].name,
schoolName: data[key][Teacher.KEY][key].schoolName,
subscription: data[key][Subscription.KEY]
}
} else { // Check if it's an individual teacher
return {
role: TYPE_INDIVIDUAL_TEACHER,
email: data[key][Teacher.KEY][key].email,
name: data[key][Teacher.KEY][key].name,
schoolName: data[key][Teacher.KEY][key].schoolName,
subscription: data[key][Subscription.KEY]
}
}
} else {
return null;
}
});
return users || null;
},

Related

Query in list of objects, looking for some attributes of different objects (CosmosDB SQL API)

I have problems to make a query (CosmosDB SQL API), when I try to select an array of objects.
I want to know if all the arguments of my search are in different objects.
The next document should be found:
{
"_id" : "97e9272d-cd7e-4a26-3245-o4e67h29beqw",
"_type" : "Document",
"categories" : [
{
"_name" : "PruebaCategoria", (searching by this)
"Type" : "omega",
"Policy number" : "1234567890",
"date" : 1553081146391,
"boolean" : true
},
{
"_name" : "CategoryDemoTest",
"Type" : "free",
"Policy number" : "0987654321"
},
{
"_name" : "CategoryDemoTestDates",
"dateInitial" : 1553081146592, (and by this in range)
"dateEnd" : 1553081146594,
"Type" : "omega"
}
],
"partitionNumber" : 1,
"serial" : 1
}
I want to replicate the next query used with mongodb API:
{ "categories" : { "$all" : [{ "$elemMatch" : { "dateInitial" : { "$gt" : { "$numberLong" : "1553081146591" }, "$lt" : { "$numberLong" : "1553081146593" } } } }, { "$elemMatch" : { "_name" : "PruebaCategoria" } }] } }
I tried the following query, but it seems that they only match if only one object has all the elements I'm looking for:
"SELECT VALUE c FROM c JOIN cat in c.categories WHERE cat._name=\"PruebaCategoria\" AND cat.dateInitial > 1553081146591 AND cat.dateInitial < 1553081146593"
Exist a method for do this type of search (with CosmosDB SQL API)?
According to all Array Functions in cosmos db sql api, there is no such direct way to implement similar functions to "$all" plus "$elemMatch" in mongo db.
Provide stored procedure workaround here.
code:
function sample() {
var collection = getContext().getCollection();
var isAccepted = collection.queryDocuments(
collection.getSelfLink(),
'SELECT * FROM c',
function (err, feed, options) {
if (err) throw err;
if (!feed || !feed.length) {
var response = getContext().getResponse();
response.setBody('no docs found');
}
else {
var returnArray = [];
for(var i =0;i<feed.length;i++){
var nameFlag = false;
var dateInitialFlag = false;
for(var j = 0;j<feed[i].categories.length;j++){
if(nameFlag == false) {
if(feed[i].categories[j]._name == 'PruebaCategoria'){
nameFlag= true;
}
}
if(dateInitialFlag == false){
if(feed[i].categories[j].dateInitial <1553081146593 || feed[i].categories[j].dateInitial > 1553081146590){
dateInitialFlag= true;
}
}
if (nameFlag == true && dateInitialFlag == true) {
returnArray.push(feed[i]);
break;
}
}
}
var response = getContext().getResponse();
response.setBody(returnArray);
}
});
if (!isAccepted) throw new Error('The query was not accepted by the server.');
}
You also could pass arguments to SP so that you could filter the data dynamically.

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.

Firebase database query from multiple childs

I have some question regarding Firebase database structure. I am not familiar with it as I just started to learn it couple days ago. Basically 1 account can have many receipts, 1 receipt can have many items of different types, 1 receipt for 1 store and 1 receipt for 1 currency.
I have came up with the database design as below:
receipts {
accountID1 : {
receiptID1 : {
date:
store: {
storeName: store1
storeAddr: addr1
}
currency: {
currencyName: currency1
currentcySymbol: $
}
totalAmount: 50.00
}
receiptID2 : { ... }
}
accountID2 : { ... }
},
itemlists {
receiptID1: {
items: {
itemID1 : {
type: food
name: snack
price: 10.00
quantity: 2
}
itemID2 : { ... }
}
}
receiptID2: { ... }
},
receiptIDsByStore {
storeID1: {
receiptID1: true
receiptID2: true
}
},
receiptIDsByCurrency {
currencyID1: {
receiptID1: true
receiptID2: true
}
},
stores {
storeID1: {
storeName: store1
storeAddress: addr1
}
},
currencies {
currencyID1: {
currencyName: currency1
currencySymbol: $
}
}
itemIDsByType {
food: {
itemID1: true,
itemID2: true,
}
entertainment: {
itemID3: true,
itemID4: true,
}
}
So my question is:
Is there any redundancy mistake I made for the design above?
I can get the total amount of spend by each user from receipts, am I right? I can just query like receipts/accountID1 to get all the receipts then sum up the total amount.
How can I actually sum up the total spend by each user for each type of items? For instance, I wanted to find for food. So I query itemIDsByType/food, then get list of itemIDs, then from there query itemlists and check if receiptID is belonged to that particular account, it it is then get the unit price?
EDIT
receipts {
accountID1 : {
receiptID1 : {
date : "07/07/2017"
store : {
storeName : "store1"
storeAddr : "addr1"
}
currency : {
currencyName : "currency1"
currentcySymbol : "$"
}
totalAmount : "50.00"
items : {
itemID1 : true,
itemID2 : true,
}
}
receiptID2 : {
date : "08/07/2017"
store : {
storeName : "store1"
storeAddr : "addr1"
}
currency : {
currencyName : "currency1"
currentcySymbol : "$"
}
totalAmount : "20.00"
items : {
itemID3 : true,
itemID4 : true,
}
}
}
accountID2 : {
receiptID3 : {
date : "08/07/2017"
store : {
storeName : "store2"
storeAddr : "addr2"
}
currency : {
currencyName : "currency1"
currentcySymbol : "$"
}
totalAmount : "100.00"
items : {
itemID5 : true,
itemID6 : true,
}
}
}
},
items {
itemID1 : {
type : "food"
name : "snack"
unitprice : "10.00"
quantity : "2"
}
itemID2 : {
type : "entertainment"
name : "gaming equipment"
unitprice : "150.00"
quantity : "1"
}
itemID3 : {
type : "food"
name : "fruit juice"
unitprice : "4.00"
quantity : "1"
}
itemID4 : {
type : "entertainment"
name : "new year clothes"
unitprice : "100.00"
quantity : "1"
}
itemID5 : {
type : "healthcare"
name : "fever meds"
unitprice : "100.00"
quantity : "1"
}
itemID6 : {
type : "healthcare"
name : "flu meds"
unitprice : "100.00"
quantity : "1"
}
},
receiptIDsByStore {
storeID1 : {
receiptID1 : true,
receiptID2 : true,
}
storeID2 : {
receiptID3 : true,
}
},
receiptIDsByCurrency {
currencyID1 : {
receiptID1 : true,
receiptID2 : true,
receiptID3 : true,
}
},
stores {
storeID1 : {
storeName : "store1"
storeAddress : "addr1"
}
storeID2 : {
storeName : "store2"
storeAddress : "addr2"
}
},
currencies {
currencyID1 : {
currencyName : "currency1"
currencySymbol : "$"
}
},
itemIDsByType {
food : {
itemID1 : true,
itemID3 : true,
}
entertainment: {
itemID2 : true,
itemID4 : true,
}
healthcare : {
itemID5 : true,
itemID6 : true,
}
}
Your database is very well structured. One thing you need to remeber when it comes to Firebase database structuring a is to have the data as flatten as possible and to make everything for the view. If you want to learn more about Firebase database structure i recomand you reading this posts: NOSQL DATA MODELING TECHNIQUES and Structuring your Firebase Data correctly for a Complex App.
Regarding your questions, as you you'll probably see in those posts, having the same data in different location is not a mistake, actually it's the opposite, it's ca ommon practice in Firebase. Yes, you can just query like receipts/accountID1 and get all the receipts. If you need a count, you can just simply use getChildrenCount() method directly on the DataSnapshot. Nested queries are not prohibited in Firebase. You can query once, to get those ids and second to get the desired data according to those ids.
Not at least, if you have a SQL background i recomand you watching this youtube tutorial sesries, The Firebase Database For SQL Developers.
Hope it helps.

How to orderByChild in nested Child in angularfire?

My firebase database structure is as below. I would like to search with a particular "Info/Email", let's say "abc#abc.com".
{
"-KhWrBcYyMluJbK7QpnK" : {
"Info" : {
"Email" : "xyz#gmail.com"
},
"Settings" : {
"Status" : "Accepted"
}
},
"-KhX0tgQvDtDYqt4XRoL" : {
"Info" : {
"Email" : "abc#abc.com"
},
"Settings" : {
"Status" : "Accepted"
}
},
"-KhX1eo7uFnOxqncDXQ5" : {
"Info" : {
"Email" : "abc#abc.com"
},
"Settings" : {
"Status" : "Pending"
}
}
}
I added a rule too
"Invitation" : {
".indexOn": ["Info/Email","Settings/Status"]
}
My AngularFire code is as follows:
var rootRef = firebase.database().ref().child('Invitation');
var userInvitations = rootRef.child("Info").orderByChild("Email").equalTo("abc#abc.com");
var allInvitations = $firebaseArray(userInvitations);
But I am getting a FIREBASE WARNING: Using an unspecified index. Consider adding ".indexOn": "Email" at /Invitation/Info to your security rules for better performance. and of course I am not receiving any data.
What are the mistakes I made here? Also can I add multiple orderByChild, for example: if I want to find details of the record, which has "Info/Email" equal to "abc#abc.com" and "Settings/Status" equal to "Pending" ?
The Firebase API only allows you to filter children one level deep
So with reference to your data structure:
if it were to be this way,
{
"-KhWrBcYyMluJbK7QpnK" : {
"Email" : "xyz#gmail.com",
"Settings" : {
"Status" : "Accepted"
}
},
"-KhX0tgQvDtDYqt4XRoL" : {
"Email" : "abc#abc.com",
"Settings" : {
"Status" : "Accepted"
}
},
"-KhX1eo7uFnOxqncDXQ5" : {
"Email" : "abc#abc.com",
"Settings" : {
"Status" : "Pending"
}
}
}
You should write your rules this way instead.
"Invitation" : {
"Info" : {
".indexOn": "Email",
},
"Settings" : {
".indexOn": "Status",
}
}
Then you will be able to query the data
var allInvitations = [];
var rootRef = firebase.database().ref().child('Invitation');
var userInvitations = rootRef.orderByChild("Email").equalTo("abc#abc.com");
userInvitations.on('value', snap => {
var invitations = snap.val()
for (prop in invitations ) {
allInvitations.push(invitations[prop ]);
}
console.log(allInvitations);
})

Receive data many-to-many relation Firebase

I have an extended question to this question.
What if the player belong to more than one team?
I have this
json
"Players" : {
"-YRHd4IjrjsBXx__B" : {
"name" : "The best forward",
"creationDate" : "2016-02-26 15:50:39",
"teams" : {
"-KAByPeIz4IjrjsBXx__B" : true,
"-KEFPuCXcqOah_GJwsMCu" : true,
"-KEwuQxvGpYTEJ7YQ58-l" : true,
"-KKF8vPtf8J7cfqFh2PLm" : true
},
},
etc...
}
players-service.js
getPlayers: function(teamid) {
var Players = {};
var teamsIndex = ref.child('teams/' + teamid + '/players/');
var playersIndex = ref.child('players/');
teamsIndex.on('child_added', function(snapshot) {
var playerKey = snapshot.key;
playersIndex.child(playerKey).on('value', function(playersnap){
$timeout(function() {
console.log("key", playerKey);
players[playerKey] = playersnap.val();
});
});
});
teamIndex.on('child_removed', function(snapshot) {
$timeout(function(snapshot) {
delete players[snapshot.key()];
});
});
return players;
}
But it returns a list of object. I know that I could probably query/change the data structure to/in firebase and return it as a $firebaseArray which I prefer as I use angularfire.
You usually structure your data depending on how you want to retrieve them.
From my understanding (correct me if I'm wrong) you want to get all the players in a team. For this purpose I would use this structure:
"Players": {
"player1": {...},
"player2": {...},
"player3": {...}
},
"Teams': {
"team1": {...},
"team2": {...}
},
"TeamPlayers" : {
"team1": {
"player1": true,
"player2": true
},
"team2": {
"player1": true,
"player3": true
}
}
Or using an array
"TeamPlayers" : {
"team1": [
0: "player1",
1: "player2"
]
}

Resources