问题
I realise there is many questions in regards to replicating joins with NoSql document databases such as FireStore, however i'm unable to find a thorough solution utilising Dart/Flutter with FireStore.
I have done some research i feel that in the following example i would be looking for a 'many to many' relationship (please correct me if this is wrong) as there may be a future need to look at all profiles as well as all connections.
In firebase, i have two root level collections (profile & connection):
profile
> documentKey(Auto Generated)
> name = "John Smith"
> uid = "xyc4567"
> documentKey(Auto Generated)
> name = "Jane Doe"
> uid = "abc1234"
> documentKey(Auto Generated)
> name = "Kate Dee"
> uid = "efg8910"
connection
> documentKey(Auto Generated)
> type = "friend"
> profileuid = "abc1234"
> uid = "xyc4567"
> documentKey(Auto Generated)
> type = "family"
> profileuid = "abc1234"
> uid = "efg8910"
For this example the 'connection' documents have been created hypothetically for the user John Smith (uid: xyc4567) when he connected to Jane Doe (uid: abc1234) and Kate Dee (uid: efg8910).
Here is the relational SQL i'm looking to replicate to show a list of profiles which John Smith has connected with:
Select * FROM profile, connection
WHERE profile.uid = connection.profileuid
AND profile.uid = "xyc4567"
In flutter my flutter app i have a fireStore query starting point:
stream: Firestore.instance.collection('profile')
.where('uid', isEqualTo: "xyc4567").snapshots(),
Obviously it only returns from one collection. How do i join the collections in a many to many relationship?
回答1:
Unfortunately, there is no JOIN
clause in Cloud Firestore nor in others NoSQL databases. In Firestore queries are shallow. This means that they only get items from the collection that the query is run against. There is no way to get documents from two top-level collection in a single query. Firestore doesn't support queries across different collections in one go. A single query may only use properties of documents in a single collection.
So the most simple solution I can think of is to query the database to get the uid
of a user from the profile
collection. Once you have that id, make another database call (inside the callback), and get the corresponding data that you need from the connection
collection using the following query:
stream: Firestore.instance.collection('connection').where('uid', isEqualTo: "xyc4567").snapshots(),
Another solution would be to create a subcollection named connection
under each user and add all connection
objects beneath it. This practice is called denormalization
and is a common practice when it comes to Firebase. If you are new to NoQSL databases, I recommend you see this video, Denormalization is normal with the Firebase Database for a better understanding. It is for Firebase realtime database but same rules apply to Cloud Firestore.
Also, when you are duplicating data, there is one thing that need to keep in mind. In the same way you are adding data, you need to maintain it. With other words, if you want to update/detele an item, you need to do it in every place that it exists.
回答2:
I did some like this to join results from two colections objects and categories.
i did two StreamBuilders to show in a list, in the first one i got the categories and put in a map, then i query the objects and get the category object from the map using the categoryID:
StreamBuilder<QuerySnapshot>(
stream: Firestore.instance
.collection('categoryPath')
.snapshots(),
builder: (BuildContext context,
AsyncSnapshot<QuerySnapshot> categorySnapshot) {
//get data from categories
if (!categorySnapshot.hasData) {
return const Text('Loading...');
}
//put all categories in a map
Map<String, Category> categories = Map();
categorySnapshot.data.documents.forEach((c) {
categories[c.documentID] =
Category.fromJson(c.documentID, c.data);
});
//then from objects
return StreamBuilder<QuerySnapshot>(
stream: Firestore.instance
.collection('objectsPath')
.where('day', isGreaterThanOrEqualTo: _initialDate)
.where('day', isLessThanOrEqualTo: _finalDate)
.snapshots(),
builder: (BuildContext context,
AsyncSnapshot<QuerySnapshot> objectsSnapshot) {
if (!objectsSnapshot.hasData)
return const Text('Loading...');
final int count =
objectsSnapshot.data.documents.length;
return Expanded(
child: Container(
child: Card(
elevation: 3,
child: ListView.builder(
padding: EdgeInsets.only(top: 0),
itemCount: count,
itemBuilder: (_, int index) {
final DocumentSnapshot document =
objectsSnapshot.data.documents[index];
Object object = Object.fromJson(
document.documentID, document.data);
return Column(
children: <Widget>[
Card(
margin: EdgeInsets.only(
left: 0, right: 0, bottom: 1),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.all(
Radius.circular(0)),
),
elevation: 1,
child: ListTile(
onTap: () {},
title: Text(object.description,
style: TextStyle(fontSize: 20)),
//here is the magic, i get the category name using the map
of the categories and the category id from the object
subtitle: Text(
categories[object.categoryId] !=
null
? categories[
object.categoryId]
.name
: 'Uncategorized',
style: TextStyle(
color: Theme.of(context)
.primaryColor),
),
),
),
],
);
}),
),
),
);
I'm not sure if is what you want or is clear but i hope it help you.
来源:https://stackoverflow.com/questions/54650261/firestore-replicating-a-sql-join-for-nosql-and-flutter