Запрос Firebase, если дочерний элемент дочернего элемента содержит значение

Структура таблицы:

  • чатов
  • → randomId
  • → - > Участники
  • → - > → 0: 'name1'
  • → - > → 1: 'name2'
  • → - > chatItems

и т.д.

То, что я пытаюсь сделать, - это запросить таблицу чатов, чтобы найти все чаты, которые содержат участника, переданной в строке имени пользователя.

Вот что я до сих пор:

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

Ответы

Ответ 1

Несколько проблем здесь:

  • вы сохраняете набор как массив
  • вы можете индексировать только по фиксированным путям

Установить vs array

В чате может быть несколько участников, поэтому вы смоделировали это как массив. Но на самом деле это не идеальная структура данных. Вероятно, каждый участник может быть только в чате один раз. Но, используя массив, я мог бы:

participants: ["puf", "puf"]

Это явно не то, что вы имеете в виду, но структура данных позволяет это. Вы можете попытаться обеспечить это в коде и правилах безопасности, но было бы проще, если бы вы начали с структуры данных, которая неявно соответствует вашей модели.

Мое правило: , если вы пишете array.contains(), вы должны использовать набор.

Набор - это структура, в которой каждый ребенок может присутствовать не более одного раза, поэтому он естественным образом защищает от дубликатов. В Firebase вы будете моделировать набор как:

participants: {
  "puf": true
}

true здесь действительно просто фиктивное значение: важно то, что мы переместили имя в ключ. Теперь, если я попытаюсь снова присоединиться к чату, это будет noop:

participants: {
  "puf": true
}

И когда вы присоединитесь:

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

Это наиболее прямое представление вашего требования: коллекция, которая может содержать только каждого участника один раз.

Вы можете индексировать только фиксированные пути

С вышеуказанной структурой вы можете запросить чаты, в которых вы находитесь:

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

Проблема заключается в том, что для этого требуется, чтобы вы определяли индекс на `members/john ':

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

Это будет работать и отлично работать. Но теперь каждый раз, когда кто-то новый присоединяется к чат-приложению, вам нужно добавить еще один индекс. Это явно не масштабируемая модель. Нам нужно изменить нашу структуру данных, чтобы разрешить требуемый запрос.

Инвертировать индексы - тянуть категории вверх, сглаживание дерева

Второе правило: смоделируйте свои данные, чтобы отразить то, что вы показываете в своем приложении.

Поскольку вы хотите показать список чатов для пользователя, храните чаты для каждого пользователя:

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

Теперь вы можете просто определить свой список чатов с помощью:

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

И затем перебирайте ключи, чтобы получить каждую комнату.

Вам понравится иметь два соответствующих списка в вашем приложении:

  • список чатов для определенного пользователя
  • список участников в определенном чате

В этом случае вы также будете иметь оба списка в базе данных.

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

Я вытащил оба списка на верхний уровень дерева, так как Firebase рекомендует против вложенности данных.

Наличие обоих списков абсолютно нормально в решениях NoSQL. В приведенном выше примере мы будем ссылаться на userChatrooms как инвертированный индекс chatroomsUsers.