Querying in Firebase by Child of Child

Firebase - Query Child of Child

Firebase Realtime Database queries work on a flat list of nodes. The key/value you are ordering/filtering on must be at a fixed path directly under each direct child node of the location you search.

The simplest solution here seems to be to add an additional data structure that maps the inviteCode values back to their category (and possibly other data about them). This is sometime referred to as a inverted index, and is quite common in NoSQL databases.

So you'd have:

invoteCodesLookup: {
"271429": {
category: "Maths",
...
}
}

You can keep this and the original data structure in sync with multi-path updates and security rules.

For more on this, see:

  • Firebase Query Double Nested
  • Firebase query if child of child contains a value
  • Many to Many relationship in Firebase
  • Firebase simple many to many relationship
  • Many to many relationships on Firebase
  • How do I load/retrieve a Many to Many relationship in Firebase Realtime Database

Querying in Firebase by child of child

I think this should work:

ref.queryOrderedByChild("categories/Oceania").queryEqualToValue(true)

How to get child of child value from firebase whose parent value is unknown in java

After reading the various answers on this topic linked above by Mr. @Frank van Puffelen and spending some times over it, the problem is finally solved now without changing my database structure.
Below is screenshot of the result which I wanted:

Sample Image

Here is my modified and working code :

private void retrieveData() {

final String shift = kvName.getText().toString();
final String employeeCode = empCode.getText().toString();

final DatabaseReference aparRef = dbRef.child("Apar").child(shift);

aparRef.addValueEventListener(new ValueEventListener() {
@Override
public void onDataChange(@NonNull DataSnapshot dataSnapshot) {

list = new ArrayList<>();

if (dataSnapshot.exists()) {

for (DataSnapshot dataS1 : dataSnapshot.getChildren()) {

if (dataS1.hasChild(employeeCode)) {

AparData aparData = dataS1.child(employeeCode).getValue(AparData.class);
list.add(aparData);

rvAPAR.setHasFixedSize(true);
rvAPAR.setLayoutManager(new LinearLayoutManager(Apar.this));
rvAPAR.setItemAnimator(new DefaultItemAnimator());
aparAdapter = new AparAdapter(Apar.this, list);
rvAPAR.setAdapter(aparAdapter);

} else {
Toast.makeText(Apar.this, "No Data Found!!", Toast.LENGTH_SHORT).show();
}

}
} else {
Toast.makeText(Apar.this, "Not Data Found!!", Toast.LENGTH_SHORT).show();
}
}

@Override
public void onCancelled(@NonNull DatabaseError databaseError) {

}
});

}

Query firebase realtime database where child has property

Your current data structure makes it easy to find all the users for a specific game. It does not however make it easy to find all the games for a specific user. To allow that, you'll want to add an addition data structure that inverts the information.

So that'd look something like this:

player_games: {
"XDYNyN8il6TDsM4LuttwDzNuytj1": {
"-M5vf...U5zK": true
},
"NxH14...mxY2": {
"-M5vf...U5zK": true
}
}

Also see:

  • Firebase query if child of child contains a value
  • Firebase Query Double Nested

I recommend you also study the Firebase documentation on structuring your database, specifically the section on avoiding nested data. By mixing entity types as you currently do, you'll likely run into problems with security, and scalability.

The most idiomatic way to model your many-to-many relationship in the Firebase database is with four top-level lists:

players: {
$playerId: { ... }
}
games: {
$gameId: { ... }
}
player_games: {
$playerId: {
$gameId: true
}
}
game_players: {
$gameId: {
$playerId: true
}
}

Also see:

  • Many to Many relationship in Firebase

Firebase query if child of child contains a value

Your current data structure is great to look up the participants of a specific chat. It is however not a very good structure for looking up the inverse: the chats that a user participates in.

A few problems here:

  • you're storing a set as an array
  • you can only index on fixed paths

Set vs array

A chat can have multiple participants, so you modelled this as an array. But this actually is not the ideal data structure. Likely each participant can only be in the chat once. But by using an array, I could have:

participants: ["puf", "puf"]

That is clearly not what you have in mind, but the data structure allows it. You can try to secure this in code and security rules, but it would be easier if you start with a data structure that implicitly matches your model better.

My rule of thumb: if you find yourself writing array.contains(), you should be using a set.

A set is a structure where each child can be present at most once, so it naturally protects against duplicates. In Firebase you'd model a set as:

participants: {
"puf": true
}

The true here is really just a dummy value: the important thing is that we've moved the name to the key. Now if I'd try to join this chat again, it would be a noop:

participants: {
"puf": true
}

And when you'd join:

participants: {
"john": true,
"puf": true
}

This is the most direct representation of your requirement: a collection that can only contain each participant once.

You can only index known properties

With the above structure, you could query for chats that you are in with:

ref.child("chats").orderByChild("participants/john").equalTo(true)

The problem is that this requires you to define an index on `participants/john":

{
"rules": {
"chats": {
"$chatid": {
"participants": {
".indexOn": ["john", "puf"]
}
}
}
}
}

This will work and perform great. But now each time someone new joins the chat app, you'll need to add another index. That's clearly not a scaleable model. We'll need to change our data structure to allow the query you want.

Invert the index - pull categories up, flattening the tree

Second rule of thumb: model your data to reflect what you show in your app.

Since you are looking to show a list of chat rooms for a user, store the chat rooms for each user:

userChatrooms: {
john: {
chatRoom1: true,
chatRoom2: true
},
puf: {
chatRoom1: true,
chatRoom3: true
}
}

Now you can simply determine your list of chat rooms with:

ref.child("userChatrooms").child("john")

And then loop over the keys to get each room.

You'll like have two relevant lists in your app:

  • the list of chat rooms for a specific user
  • the list of participants in a specific chat room

In that case you'll also have both lists in the database.

chatroomUsers
chatroom1
user1: true
user2: true
chatroom2
user1: true
user3: true
userChatrooms
user1:
chatroom1: true
chatroom2: true
user2:
chatroom1: true
user2:
chatroom2: true

I've pulled both lists to the top-level of the tree, since Firebase recommends against nesting data.

Having both lists is completely normal in NoSQL solutions. In the example above we'd refer to userChatrooms as the inverted index of chatroomsUsers.

Cloud Firestore

This is one of the cases where Cloud Firestore has better support for this type of query. Its array-contains operator allows filter documents that have a certain value in an array, while arrayRemove allows you to treat an array as a set. For more on this, see Better Arrays in Cloud Firestore.

How to Query Firebase by child and equalTo in Android?

To be able to query the database by a specific property, you need to add all child names that exist in that particular hierarchy. In your query, you are missing a child. Between the "restaurantId" node and the actual Order object, which holds the "orderStatus" property, there is one more node, which is "LPG ... CQ2", and I'm assuming the UID of the logged-in user. To make it work, please use the following lines of code:

String uid = FirebaseAuth.getInstance().getCurrentUser().getUid();
DatabaseReference rootRef = FirebaseDatabase.getInstance().getReference();
DatabaseReference uidRef = rootRef.child("Orders").child("Restaurant01").child(uid);
Query orderStatusQuery = uidRef.orderByChild("orderStatus").equalTo(0);
ValueEventListener valueEventListener = new ValueEventListener() {
@Override
public void onDataChange(DataSnapshot dataSnapshot) {
for(DataSnapshot ds : dataSnapshot.getChildren()) {
String userName = ds.child("userName").getValue(String.class);
Log.d("TAG", userName);
}
}

@Override
public void onCancelled(@NonNull DatabaseError databaseError) {
Log.d("TAG", databaseError.getMessage()); //Don't ignore potential errors!
}
};
orderStatusQuery.addListenerForSingleValueEvent(valueEventListener);

The result in the logcat will be:

User

Edit:

According to your comment, what you are looking for it's actually not possible with your actual database structure. Besides that, filtering on the client it's definitely not an option in this scenario.

However, the best I can think of is to create another node that stores all orders, of all users. In this way, you can query using ".orderByChild("orderStatus").equalTo(0)" and you'll get the desired results. This practice is called denormalization and is a common practice when it comes to Firebase. For a better understanding, I recommend you see this video, Denormalization is normal with the Firebase Realtime Database.

How do I retrieve firebase data based on the key in a nested child?

Firebase Realtime Database queries operate on a flat list of child nodes directly under the path that you query.

So the value you order/filter on has to be at a fixex path under each immediate child node. Since that isn't the case for your uid, you can't query across all trips for the UID of all users of those trips.

You can query across one trip for a UID of a user (and then get back that user), or you can query across all trips for properties of the trip itself, such as its title or budget.

If you want to query across all users on all trips, consider keeping an additional list where you have the UID of the user as the key, and then all their trips under there:

"user_trips": {
"uid1": {
"tripid1": true,
"tripid2": true
},
"uid2": {
"tripid2": true,
"tripid3": true
}
}

Also see:

  • Firebase Query Double Nested
  • Firebase query if child of child contains a value
  • Many to Many relationship in Firebase


Related Topics



Leave a reply



Submit