web-dev-qa-db-fra.com

Requête Firebase si l'enfant de l'enfant contient une valeur

La structure de la table est:

  • chats
  • -> randomId
  • -> -> participants
  • -> -> -> 0: 'nom1'
  • -> -> -> 1: 'name2'
  • -> -> chatItems

etc

Ce que j'essaie de faire est d'interroger la table de discussions pour rechercher toutes les discussions qui contiennent un participant par une chaîne de nom d'utilisateur passée.

Voici ce que j'ai jusqu'à présent:

 subscribeChats(username: string) {
    return this.af.database.list('chats', {
        query: {
            orderByChild: 'participants',
            equalTo: username, // How to check if participants contain username
        }
    });
 }
51
John

Votre structure de données actuelle est idéale pour rechercher les participants à un chat spécifique. Ce n’est toutefois pas une très bonne structure pour rechercher l’inverse: les discussions auxquelles un utilisateur participe.

Quelques problèmes ici:

  • vous stockez un set sous forme de tableau
  • vous ne pouvez indexer que sur des chemins fixes

Ensemble vs tableau

Une discussion peut avoir plusieurs participants, vous avez donc modélisé cela comme un tableau. Mais ce n’est pas la structure de données idéale. Il est probable que chaque participant ne peut participer qu'une seule fois à la discussion. Mais en utilisant un tableau, j'aurais pu:

participants: ["puf", "puf"]

Ce n’est clairement pas ce que vous avez à l’esprit, mais la structure de données le permet. Vous pouvez essayer de sécuriser cela dans le code et les règles de sécurité, mais ce serait plus facile si vous débutiez avec une structure de données qui correspond implicitement mieux à votre modèle.

Ma règle générale: si vous vous trouvez en train d'écrire array.contains(), vous devriez utiliser un ensemble .

Un ensemble est une structure dans laquelle chaque enfant peut être présent au maximum une fois, ce qui le protège naturellement des doublons. Dans Firebase, vous modéliseriez un ensemble comme suit:

participants: {
  "puf": true
}

Le true ici n’est en réalité qu’une valeur factice: l’important est que nous ayons déplacé le nom vers la clé. Maintenant, si j'essayais de rejoindre ce chat à nouveau, ce serait un noop:

participants: {
  "puf": true
}

Et quand vous rejoindriez:

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

Il s'agit de la représentation la plus directe de votre besoin: une collection qui ne peut contenir qu'une seule fois chaque participant.

Vous ne pouvez indexer que les propriétés connues

Avec la structure ci-dessus, vous pourrait interroger les discussions avec lesquelles vous participez:

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

Le problème est que cela nécessite que vous définissiez un index sur `participants/john":

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

Cela fonctionnera et fonctionnera très bien. Mais maintenant, chaque fois qu'une nouvelle personne rejoint l'application de chat, vous devez ajouter un autre index. Ce n'est clairement pas un modèle évolutif. Nous aurons besoin de changer notre structure de données pour permettre la requête que vous voulez.

Inverser l'index - extraire les catégories, aplatir l'arbre

Deuxième règle empirique: modélisez vos données pour refléter ce que vous affichez dans votre application .

Puisque vous souhaitez afficher une liste de salles de conversation pour un utilisateur, stockez-les pour chaque utilisateur:

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

Maintenant, vous pouvez simplement déterminer votre liste de salons de discussion avec:

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

Et puis boucle sur les clés pour obtenir chaque chambre.

Vous aimerez avoir deux listes pertinentes dans votre application:

  • la liste des salles de discussion pour un utilisateur spécifique
  • la liste des participants dans un forum de discussion spécifique

Dans ce cas, vous aurez également les deux listes dans la base de données.

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

J'ai tiré les deux listes au niveau supérieur de l'arborescence, car Firebase recommande de ne pas imbriquer de données.

Avoir les deux listes est complètement normal dans les solutions NoSQL. Dans l'exemple ci-dessus, nous ferions référence à userChatrooms en tant qu'index inversé de chatroomsUsers.

Firestore Cloud

C'est l'un des cas où Cloud Firestore prend mieux en charge ce type de requête. Son opérateur array-contains Permet de filtrer les documents ayant une certaine valeur dans un tableau, tandis que arrayRemove vous permet de traiter un tableau comme un ensemble. Pour plus d'informations à ce sujet, voir Better Arrays in Cloud Firestore .

71
Frank van Puffelen