Query specific ID's in a firebase year/month/day nest - firebase

I have some data structured like the following in firebase. My client is built with angularjs + angularfire.
'all_records' is nested like this to split the data up and also as the majority of the querying is time orientated.
The 'all_records' nest is equal to: all_records > year > month > date > records.
I want to associate the records with the user that posted the record and therefore on record creation have saved the associated ID to the user in 'my_records'.
This example is obviously a very small dataset however to allow for scalability and potentially having having hundreds of thousands of records what would be the best way to query 'all_records' to get just the users 'my_records'.
Thanks
all_records : {
2015 : {
2 : {
3: {
-JgJJaLaL7LuuRL_IO7c : {
data: big data;
},
-JgJfaOsyfZe_mZPTApA : {
data: lots of data;
},
...
},
...
},
1 : {
28 : {
-JgJJaLaL7kddEl_ddfE : {
data: some more data;
}
},
27 : {
-JgJfadkdjEk_mZPTApA : {
data: even more;
}
},
...
},
...
},
2014 : {
12 : {
24: {
-JgJmnvkFdqc_y63lqcv : {
data: decemeber data;
},
-JgK6xbXY_E58lshaiVa : {
data: and a bit more data;
}
},
...
},
11 : {
...
},
...
}
}
user: {
name : James,
my_records : {
-JgK6xbXY_E58lshaiVa: true,
-JgJJaLaL7LuuRL_IO7c: true
}
}

Your user's my_records currently doesn't contain enough information to to directly map to the record. You'd need to add the date (year, month and day) to the information to allow a direct lookup.
I would instead structure things differently:
a list of just the records with their push-generated -J key
a list of users, with references to their records
a list/tree of dates, with references to the records for each date
Your application should never try to load the list of all records (#1). Instead it should load either the relevant user (from #2) or the relevant date(s) from (#3) and then load only the records that are referenced from there.
all_records : {
-JgJJaLaL7LuuRL_IO7c : { data: big data; },
-JgJfaOsyfZe_mZPTApA : { data: lots of data; },
-JgJJaLaL7kddEl_ddfE : { data: some more data; }
-JgJfadkdjEk_mZPTApA : { data: even more; }
}
dates: {
2015 : {
2 : {
3: {
-JgJJaLaL7LuuRL_IO7c : true,
-JgJfaOsyfZe_mZPTApA : true,
...
},
...
},
1 : {
28 : {
-JgJJaLaL7kddEl_ddfE : true
},
27 : {
-JgJfadkdjEk_mZPTApA : true
},
...
},
...
}
}
user: {
name : James,
my_records : {
-JgK6xbXY_E58lshaiVa: true,
-JgJJaLaL7LuuRL_IO7c: true
}
}
You might also want to consider structuring the dates as a simple list, instead of a tree:
dates: {
20150203: {
-JgJJaLaL7LuuRL_IO7c : true,
-JgJfaOsyfZe_mZPTApA : true,
...
},
20150128 : {
-JgJJaLaL7kddEl_ddfE : true
},
20150127 : {
-JgJfadkdjEk_mZPTApA : true
},
}
While it's tempting to build a tree for the dates, I doubt you'll get much benefit from it. The above structure is simpler and allow for a wider variety of queries, such as "give me all dates in Q1 of 2015": ref.child('dates').startAt('20150101').endAt('20150331').

Related

CosmosDB $elemMatch syntax error

I am getting a strange syntax error for some commands in the MongoDB API for CosmosDB. Say I have a collection called "Collection" with two documents:
{
"_id" : 1,
"arr" : [
{
"_id" : 11
},
{
"_id" : 12
}
]
}
{
"_id" : 2,
"arr" : [
{
"_id" : 21
},
{
"_id" : 22
}
]
}
If I try to run the query
db.getCollection('Collection').find( { _id : 2 }, { arr : { $elemMatch : { _id : 21 } } })
I get the result
{
"_t" : "OKMongoResponse",
"ok" : 0,
"code" : 9,
"errmsg" : "Syntax error, incorrect syntax near '10'.",
"$err" : "Syntax error, incorrect syntax near '10'."
}
But the command works perfectly fine on my locally hosted instance of MongoDB, returning the expected result:
{
"_id" : 2,
"arr" : [
{
"_id" : 21
}
]
}
Anyway, this is certainly not a syntax error, but there is no helpful error message. If this is not yet supported by CosmosDB, is there any way to only get certain embedded documents stored in an array?
If I try to use an aggregation pipeline to just extract the document in the array (I realize this should give a different result than the command above, but it would also work for my purposes), like so:
db.getCollection('Collection').aggregate([{ "$unwind" : "$arr" }, { "$match" : { "arr._id" : 21 } }] )
I get the result
{
"_t" : "OKMongoResponse",
"ok" : 0,
"code" : 118,
"errmsg" : "$match is currently only supported when it is the first and only stage of the aggregation pipeline. Please restructure your query to combine multiple $match stages into a single $match stage.",
"$err" : "$match is currently only supported when it is the first and only stage of the aggregation pipeline. Please restructure your query to combine multiple $match stages into a single $match stage."
}
So that doesn't work for me either.
Try this
db.collection.aggregate([
{
$match: {
"_id": 2
}
},
{
$project: {
arr: {
$filter: {
input: "$arr",
as: "ar",
cond: {
$eq: [
"$$ar._id",
21
]
}
}
}
}
}
])
Check it here

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.

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"
]
}

Firebase equalto dynamic nested child

With a structure of
/archive: {
$userId: {
$archiveKey: {
foo: 1
},
...
},
...
}
Where $userId references a user id and $archiveKey is dynamic, created by .push().
Is it possible to query the archive ref and get all archiveObjects where foo = 1 ? Or do I need to fetch down the whole table, manually dig into $userId and extract the archiveObjects I'm looking for?
Firebase queries can nowadays query nested paths, but the paths cannot be dynamic.
So if you know the uid, you can query that user's archives with:
ref.child(authData.uid).orderByChild('foo').equalTo(1).on(...
If you don't know the uid, then you'll have to create a data structure that allows you to do the lookup:
archive_category_to_uids: {
foo: {
1: {
uid1: true,
uid2: true
}
}
}
A more common way is to separate the archives into their own top-level list and have both users and categories refer to that:
users: {
userId1: {
archiveKey1: true,
...
},
...
},
archives: {
archiveKey1: {
foo: 1,
uid: uid1
},
...
},
archiveCategories: {
foo: {
1: {
archiveKey1: true,
archiveKey2: true
}
}
}
Now you can get find the archives with:
ref.child('archiveCategories/foo/1').once('value', function(keys) {
keys.forEach(function(key) {
ref.child('archives').child(key.key()).once('value', function(snapshot) {
console.log(snapshot.val());
});
};
});
This process is called denormalization and is quite common in NoSQL databases. You're modeling the data for how your application needs to consume it. For more on this and other common patterns, I recommend reading this article on NoSQL data modeling.

Looping through firebase unique keys and adding data to each

I have firebase database in this form.
"items":{
"-Jp9VLYBwENfNVLKYyCG" : {
"-Jp9VMKkTUggcMrUCO8S" : {
"author" : "facebook:#########",
"created" : 1431474107638,
},
"-Jp9VobJP0qbSbbEdyW0" : {
"author" : "facebook:#########",
"created" : 1431474227548
}
},
"-JpEbifKeRj-_H7TWhQ2" : {
"-JpEbrnT6SFRFc6U_HWX" : {
"author" : "facebook:#########",
"created" : 1431559961683
},
"-JpEby5mZuQMsU-9YCpk" : {
"author" : "facebook:##########",
"created" : 1431559987495
}
}
}
I want to add the following object to each $id containing author and created key-value pairs.
upVote: {
active: true,
total: 0
},
downVote: {
active: true,
total: 0
}
I am first trying to fetch the values using DataSnapshot. But it doesn't print the keys in console. What is wrong here?
var ref = new Firebase(FBURL + "/items");
ref.once("value", function(snapshot) {
console.log("snapshot key: " + snapshot.key());
snapshot.forEach(function(childSnapshot){
var key = childSnapshot.key();
var val = childSnapshot.val();
console.log("child snapshot key: " + key);
childSnapshot.forEach(function(deepSnap){
console.log("deep shot key: " + deepSnap.key());
});
});
});
RESOLVED The problem was that I had some security rules set up with read and write permissions. Therefore wasn't allowing me to read the element from the user scope that I was calling the script. It was one gotcha like problems.
After this step, it was easy to update the database with values:
var deepRef = ref.child(key).child(deepSnap.key());
deepRef.update({upVote: {
active: true,
total: 0
},downVote: {
active: true,
total: 0
}});

Resources