0

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,
    }
}
vvvvv
  • 25,404
  • 19
  • 49
  • 81
QWERTY
  • 2,303
  • 9
  • 44
  • 85
  • Are the stores you are using different stores for one company or totally different companies? Also, you want to sum the total spend by each user, but there are no references to users within the posted structure. – Jay Jul 07 '17 at 16:11
  • @Jay yeah the stores are from different stores. As for the total spend, o can just query receipts/accountID1 then get the totalAmt for each receiptID inside, am I right? – QWERTY Jul 08 '17 at 02:26
  • @Jay regarding the third bullet point, I query itemIDsByType/food, then get the list of items, after that query receipts/accountID then compare the itemID. If matched, I query items/itemID to get the item details then store the result into another separate array. How can I actually 'GROUP BY' the spend amount for each category of a particular user by month? – QWERTY Jul 08 '17 at 06:33
  • For a sum; with the current structure it will be easy to iterate over all of the children of receipts/accountID1 - either with .childAdded if it's a large dataset or .value if it's small and retrieve the totalAmt from each node and sum them up, so yes, you are correct. For your second comment, as I mentioned in my first comment, there are no references to users in the posted structure and it's not clear what *each category* means. I think that may be a separate question which should include your users structure, what categories are and how they relate to the rest of the data. – Jay Jul 08 '17 at 12:39
  • @Jay I see I see. Thanks so much! – QWERTY Jul 09 '17 at 09:38
  • @Jay Hey I realized one problem from the structure above. Let's say person A purchase item A from store A under receipt 1. Then, person A purchase the same item A from same store under receipt 2. So for now, under items table, there will be duplicate record of item A. Is there anyway to restructure it such that this problem will not exist? – QWERTY Jul 15 '17 at 02:36
  • That's not a duplicate because the item appears under a different receipt so there's no problem there. The issue you will have is if they purchase multiple quantities of the same item on one receipt; like 3 itemID3's. – Jay Jul 15 '17 at 12:28
  • @Jay as for the multiple quantity one, I got a quantity attribute for each item so that should not be a problem. What my professor told me was the problem above when purchase same item under different receipt. So basically like receipt 1 got itemID1, then receipt 2 got itemID2 but itemID1 and itemID2 are exactly same item. I think that makes sense also and I tried to reconstruct it but I just could not get it work as the design seems pretty off. Do you think I should open another thread so that I could up vote your solution or explanation? – QWERTY Jul 15 '17 at 13:06
  • I am just not seeing that as a problem. A reference to an itemID is under a completely different receipt ID so that wouldn't cause any interference. It's too deep to be queried so it wouldn't be that either. Are the items in the items node the actual items the user can select from? If so, you would not have duplicates. Perhaps thats not what the items node is for? – Jay Jul 15 '17 at 13:16
  • @Jay Hey do you mind to take a look at this link: https://stackoverflow.com/questions/45118731/database-table-design-with-duplication-of-data – QWERTY Jul 15 '17 at 13:28

1 Answers1

3

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.

Alex Mamo
  • 130,605
  • 17
  • 163
  • 193
  • thanks so much for the reply! But just some questions, there is no redundancy in the design above? Because I am thinking if those receiptIDsByStore and receiptIDsByCurrency could be eliminated as they are already nested inside receipts. Do you mind to help me take a look at another design? Because while waiting for reply I have came out with another design but I not sure which one would be better. Also, for the third bullet point in the question, is that the right approach? – QWERTY Jul 07 '17 at 09:18
  • I have updated the question. In design two, I put a nested look up for items in each receipt. In order to query the third bullet point above, I query itemIDsByType/food, then get the list of items, after that query receipts/accountID then compare the itemID. If matched, I query items/itemID then store the result into another separate array. In term of performance and design theory, which design is better? – QWERTY Jul 07 '17 at 09:23
  • Seeing the second version of your database, i can say it's better. Why? Because it's flatten than the fist version. It is very good to have those item as a separate category because if you want to display all items you don't need to download the whole `itemlists` node. I repet, you need to structure you database as it fits your needs.Regarding of havind data in 2 different places, i insist to watch this video about [Denormalization](https://www.youtube.com/watch?v=vKqXSZLLnHA&vl=en). – Alex Mamo Jul 07 '17 at 09:50
  • thanks so much! But is there any way to further flatten the design in edited portion? Also, does that means that I can place as much as look up tables for the ease of retrieval? Does that violated the design rules? Sorry for the questions as I just learnt it a couple days ago and I am still practicing it. – QWERTY Jul 07 '17 at 10:50
  • There's no problem, you are here to learn. More than that, as i understand from you, i'm affraid it's not necessary. In Firebase there are no tables. Because Firebase is a NoSQL database we are speaking about nodes. Firebase is structured as pairs of key and values. If you follow Firebase rules, you can add as much data as you need in your database. Follow the links that i gave you in my answer. For sure will clarify you most of Firebase concepts. – Alex Mamo Jul 07 '17 at 11:05
  • 1
    @DeniseTan This is a great answer! I would like to add that while 'flattening' or 'denormalizing' your data is a common an good practice, don't do it just for the sake of doing it. Just like duplicating data - only do it when there's a need for it - sometimes it makes more sense to keep data tied to the parent node that contains it. It's situational and there is no 'rule' to Firebase structures. – Jay Jul 07 '17 at 16:44
  • @AlexMamo Hey I realized one problem from the structure above. Let's say person A purchase item A from store A under receipt 1. Then, person A purchase the same item A from same store under receipt 2. So for now, under items table, there will be duplicate record of item A. Is there anyway to restructure it such that this problem will not exist? – QWERTY Jul 15 '17 at 02:36
  • To avoid this, you need to use `push()` method to generate different ids even of the product is the same. You don't need to use product names, you need to use keys, because there are unique. – Alex Mamo Jul 17 '17 at 09:16