'Firebase data structure query
After reading the documentation they recommend not using nested data structure. I am thinking this is the best way for my data but I am not sure how to add the tripId to the hotels collection.
My other option is having the user enter the "BOL" for each hotel entry... My problem with that method is how do I query the date to see all the hotels that match the "trip"?
EDIT:::::::: What I am trying to accomplish is when a user selects a trip it runs a query and displays the total of all the nights in a hotel associated with that trip.
users:
- gCFiwSLwBeg87N6GfeofFxIAZyi2
- UID: gCFiwSLwBeg87N6GfeofFxIAZyi2
- firstName: "James"
- email: "[email protected]"
trips:
- "random document id"
- bol: "1234567"
- startDate: "1/3/2022"
- destination: "Sacramento, CA"
- userID: "Users UID"
- foodTotal: "100"
- hotelTotal: "240"
- "random document id"
- bol: "256976"
- startDate: "2/5/2022"
- destination: "Orlando, FL"
- userID: "Users UID"
- foodTotal: "150"
- hotelTotal: "400"
hotels:
- "random document id"
- tripId: ?
- date: "1/4/2022"
- destination: "Sacramento, CA"
- userID: "Users UID"
- name: "Hotel Inn"
- cost: "120"
- "random document id"
- tripId: ?
- date: "1/5/2022"
- destination: "Sacramento, CA"
- userID: "Users UID"
- name: "Hotel Inn"
- cost: "120"
Here is the function I am currently using to add Hotels to firebase.
class FirebaseHotelRepository: HotelRepositoryProtocol {
private let db = Firestore.firestore()
func addHotel(hotel: Hotel, completion: @escaping (Result<Hotel?, Error>) -> Void) {
do {
var addedHotel = hotel
addedHotel.userId = Auth.auth().currentUser?.uid
let _ = try db.collection("hotels").addDocument(from: addedHotel)
} catch {
fatalError("Unable to encode trip: \(error.localizedDescription)")
}
}
}
class AddHotelViewModel: ObservableObject {
private let repo: HotelRepositoryProtocol
var name: String = ""
var location: String = ""
var date = Date()
var cost: String = ""
@Published var saved: Bool = false
init(repo: HotelRepositoryProtocol) {
self.repo = repo
}
func addHotel() {
let hotel = Hotel(bol: bol,
name: name,
location: location,
date: date,
cost: cost,
color: UIColor(color).hexStringFromColor())
repo.addHotel(hotel: hotel) { result in
switch result {
case .success(let savedHotel):
DispatchQueue.main.async {
self.saved = savedHotel == nil ? false : true
}
case .failure(let error):
print(error.localizedDescription)
}
}
}
}
Solution 1:[1]
Let start with clarifying this
not using nested data structure
Everything in Firebase is nested! - that's the way it works. That's in reference to DEEPLY nesting data as it makes it hard to get to/query etc so denormalizing or flattening the data is an often used practice.
There are a number of ways to get the data you want but let me re-state the question
I have trips and each trip has a number of associated hotels. My goal is to read the hotels for each trip to calculate a total cost for those hotels.
TL;DR Skip to the bottom: One Quick Thing section
Here's one structure
trip_0
trip_name: "Napa trip"
hotels:
hotel_0: true
hotel_1: true
hotel_2: true
with that structure, reading in trip_0 will provide the associated hotels. You then then iterate over that array (map) and read in hotel_0 and get it's cost, then hotel_1 and get it's cost. Add the costs up and you're done. No query required.
A second option is a reverse approach with a seperate collection that links hotels and trips - this requires a query to get the hotels associated with each trip
hotels_trips
hotel_0
trip_0: true
trip_1: true
trip_2: true
hotel_1
trip_1: true
with the above, run a query on the hotels_trips collection for hotels there trip_1 is true. Once you have the list of hotels you can read each one to get it's cost and add them up.
Another solution is a structure and a query using arrayContains:
hotels
hotel_0
hotel_name: "Hotel California"
cost: 500
trip_array
0: trip_0
1: trip_1
and then the code that will query the hotels collection for all hotels that contains trip_0
func queryHotels() {
let hotelCollection = self.db.collection("hotels")
hotelCollection.whereField("trip_array", arrayContains: "trip_0").getDocuments { querySnapshot, error in
if let err = error {
print(err.localizedDescription)
return
}
guard let docs = querySnapshot?.documents else { return }
for doc in docs {
let name = doc.get("name") as! String
print(name)
}
}
}
One quick thing: Arrays are typically challenging to work with in NoSQL databases like Firebase so I usually avoid them. You may want to consider using a Map instead of an array and then query on the path trip_map/trip_0 to see if it's true. The hotels structure would include this instead of the trip_array
trip_map
trip_0: true
trip_1: true
and the query would test the path to see if it's value is true
hotelCollection.whereField("trip_map.trip_0", isEqualTo: true).getDocuments { querySnapshot, error in
Solution 2:[2]
From this documentation:
In some cases, it can be useful to create a document reference with an auto-generated ID, then use the reference later. For this use case, you can call doc():
Here's a sample code that you can refer to:
let trips = db.collection("trips").addDocument(data: [
// trips' data
])
// This will append tripId to the `addedHotel`
addedHotel["tripId"] = trips.documentID
let hotels = db.collection("hotels").addDocument(from: addedHotel)
The above code should get the document ID of the trips' and you can manually append or construct the data that you should pass to the hotels` document.
You can find more relevant information here.
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|---|
| Solution 1 | Jay |
| Solution 2 | Marc Anthony B |
