According to the Firebase documentation, we should avoid nesting data. With this in mind, I wonder what is the best practice, for my database, to store then find this data.
I have to store some "activities". Each "Activity" belongs to a "notion" and each "notion" belongs to a "type".
With this in mind, what is the best way to store all the activities for a fast access?
"Type_1" : {
"Notion_1" : {
"Activity_1" : "....",
"Activity_1" : "...."
},
"Notion_2" : {
"Activity_1" : "....",
"Activity_2" : "...."
}
},
"Type_2" : {
"Notion_1" : {
"Activity_1" : "....",
"Activity_1" : "...."
},
"Notion_2" : {
"Activity_1" : "....",
"Activity_2" : "...."
}
}
}
or
"Type_1_notion_1_activity_1" : { "content" : "...." },
"Type_1_notion_1_activity_2" : { "content" : "...." },
"Type_1_notion_2_activity_1" : { "content" : "...." },
"Type_1_notion_2_activity_2" : { "content" : "...." },
"Type_2_notion_1_activity_1" : { "content" : "...." },
"Type_2_notion_1_activity_2" : { "content" : "...." },
"Type_2_notion_2_activity_1" : { "content" : "...." },
"Type_2_notion_2_activity_2" : { "content" : "...." },
I wanted to use this to get the data
firebase.database().ref("/main/" + activityUID).once('value', function(snap) {...}
Where activityUID is either ["Type_1_notion_1_activity_1"] or ["Type_1/Notion_1/Activity_1"].
Is it a good method? Thank you!
Related
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;
},
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.
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);
})
I have a page that is calling addCheckin() method which is inside a controller. In the controller, I am trying to create a reference as follows:
var ref = firebase.database().ref("users/" + $scope.whichuser + "/meetings/" +$scope.whichmeeting + "/checkins");
$scope.whichuser and $scope.whichmeeting are the $routeParams that I am passing from another route.
Here's my checkin controller-
myApp.controller("CheckinsController",
['$scope','$rootScope','$firebaseArray','$routeParams','$firebaseObject',
function($scope,$rootScope,$firebaseArray,$routeParams,$firebaseObject){
$scope.whichuser = $routeParams.uid;
$scope.whichmeeting = $routeParams.mid;
var ref = firebase.database().ref("users/" + $scope.whichuser + "/meetings/" +$scope.whichmeeting + "/checkins");
$scope.addCheckin = function(){
var checkinInfo = $firebaseArray(ref);
var data={
firstname:$scope.firstname,
lastname:$scope.lastname,
email:$scope.email,
date:firebase.database.ServerValue.TIMESTAMP
}
checkinInfo.$add(data);
}
}]);/*controller*/
There are two errors that I am getting here-
Error 1:
Error: permission_denied at /users/Vp2P1MqKm7ckXqV2Uy3OzTnn6bB3/meetings: Client doesn't have permission to access the desired data.
Error 2:
Error: permission_denied at /users/Vp2P1MqKm7ckXqV2Uy3OzTnn6bB3/meetings/-KT5tqMYKXsFssmcRLm6/checkins: Client doesn't have permission to access the desired data.
And this is what I am trying to achieve:
Here's my JSON tree of the database:
{
"users" : {
"43ZU72ndoKb9LSEjBODKXe3nrou1" : {
"email" : "roy#123.com",
"firstname" : "Roy",
"lastname" : "Marshal",
"regUser" : "43ZU72ndoKb9LSEjBODKXe3nrou1"
},
"BHcYb40FC8YlCKL54ngyMhxPx2q1" : {
"date" : 1475574109981,
"email" : "sky#on.com",
"firstname" : "Sky",
"lastname" : "Thakur",
"meetings" : {
"-KTE3_45wXBqd-_yR1xv" : {
"date" : 1475574125198,
"name" : "ARET"
}
},
"regUser" : "BHcYb40FC8YlCKL54ngyMhxPx2q1"
},
"Vp2P1MqKm7ckXqV2Uy3OzTnn6bB3" : {
"email" : "jon#me.com",
"firstname" : "John",
"lastname" : "Cena",
"meetings" : {
"-KT5tqMYKXsFssmcRLm6" : {
"date" : 1475437094667,
"name" : "hourly"
},
"-KTAvH-ZkWyxrJrK_SDb" : {
"date" : 1475521356544,
"name" : "Hourly"
},
"-KTAwV69txdeT4i_horE" : {
"date" : 1475521676006,
"name" : "asdfg"
},
"-KTAwkH35EyPQZ17DmN9" : {
"date" : 1475521742244,
"name" : "qwerty"
},
"-KTAwsrqe24e8wFfRxpN" : {
"date" : 1475521777422,
"name" : "asfdsf"
},
"-KTAyYPSZLQUtOJ4g8Ak" : {
"date" : 1475522213781,
"name" : "Yearly"
},
"-KTB05PfXZLLk9Of3_Nb" : {
"date" : 1475522881473,
"name" : "Meet"
},
"-KTBG2cP9YLoP4lalN5M" : {
"date" : 1475527064378,
"name" : "JOI"
}
},
"regUser" : "Vp2P1MqKm7ckXqV2Uy3OzTnn6bB3"
}
}
}
My database rules are:
{
"rules": {
".read": true,
".write": true
}
}
I also posted this question before, but didn't get any appropriate solution.
I am still working with previous API, however I just came across one little issue. I am trying to query data from Firebase based on the limitToLast(100), however the .val() of snapshot is always null.
Here is sample data structure of my Messaging:
“Messaging” :{
"10153530036627802" : {
"10155174395220402" : {
"LastUpdate" : 1429295238531,
"Messages" : {
"1428338024339" : {
"date" : 1428338024339,
"message" : "Yo",
"sender" : "Uksz"
},
"1428338024488" : {
"date" : 1428338024487,
"message" : "Yo",
"sender" : "Uksz"
},
"1429023827304" : {
"date" : 1429023827303,
"message" : "Fygh",
"sender" : "Uksz"
},
"1429023828469" : {
"date" : 1429023828468,
"message" : "Fygh",
"sender" : "Uksz"
},
"1429295238532" : {
"date" : 1429295238531,
"message" : "Hgh",
"sender" : "Uksz"
}
},
"Name" : "Matt",
"Picture" : "https://scontent.xx.fbcdn.net/xxxx"
},
NEXT_USER_ID
And my code is:
var firebaseRef = new Firebase ("https://xxxx.firebaseio.com/Messages/");
var messageRef = firebaseRef.child(userId).child(recipient).child("Messages");
var messagesRef2 = messageRef.limitToLast(10);
messagesRef2.on("value", function(snapshot){
console.log(snapshot.val())
})
Whenever I am getting snapshot.val(), its value is equal to null. Where did I go wrong?
Thanks,
Uksz