Запрос на основе нескольких предложений where в Firebase

244
{
"movies": {
    "movie1": {
        "genre": "comedy",
        "name": "As good as it gets",
        "lead": "Jack Nicholson"
    },
    "movie2": {
        "genre": "Horror",
        "name": "The Shining",
        "lead": "Jack Nicholson"
    },
    "movie3": {
        "genre": "comedy",
        "name": "The Mask",
        "lead": "Jim Carrey"
    }
  }  
 }

Я новичок в Firebase. Как я могу получить результат из данных выше, где genre = 'comedy'Иlead = 'Jack Nicholson' ?

Какие у меня есть варианты?

47d_
источник

Ответы:

239

Используя API запросов Firebase , вы можете попробовать это:

// !!! THIS WILL NOT WORK !!!
ref
  .orderBy('genre')
  .startAt('comedy').endAt('comedy')
  .orderBy('lead')                  // !!! THIS LINE WILL RAISE AN ERROR !!!
  .startAt('Jack Nicholson').endAt('Jack Nicholson')
  .on('value', function(snapshot) { 
      console.log(snapshot.val()); 
  });

Но, как говорит @RobDiMarco из Firebase в комментариях:

множественные orderBy()вызовы приведут к ошибке

Так что мой код выше не будет работать .

Я знаю три подхода, которые будут работать.

1. фильтровать большинство на сервере, делать все остальное на клиенте

Что вы можете сделать, это выполнить один orderBy().startAt()./endAt()на сервере, вытащить оставшиеся данные и отфильтровать их в коде JavaScript на вашем клиенте.

ref
  .orderBy('genre')
  .equalTo('comedy')
  .on('child_added', function(snapshot) { 
      var movie = snapshot.val();
      if (movie.lead == 'Jack Nicholson') {
          console.log(movie);
      }
  });

2. добавьте свойство, которое объединяет значения, по которым вы хотите фильтровать

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

"movie1": {
    "genre": "comedy",
    "name": "As good as it gets",
    "lead": "Jack Nicholson",
    "genre_lead": "comedy_Jack Nicholson"
},...

По сути, вы строите свой собственный многостолбцовый индекс и можете запросить его с помощью:

ref
  .orderBy('genre_lead')
  .equalTo('comedy_Jack Nicholson')
  .on('child_added', function(snapshot) { 
      var movie = snapshot.val();
      console.log(movie);
  });

Дэвид Ист написал библиотеку QueryBase, которая помогает создавать такие свойства .

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

"movie1": {
    "genre": "comedy",
    "name": "As good as it gets",
    "lead": "Jack Nicholson",
    "genre_year": "comedy_1997"
},...

А затем запросите комедии 90-х годов с:

ref
  .orderBy('genre_year')
  .startAt('comedy_1990')
  .endAt('comedy_2000')
  .on('child_added', function(snapshot) { 
      var movie = snapshot.val();
      console.log(movie);
  });

Если вам нужно отфильтровать не только год, добавьте другие части даты в порядке убывания, например "comedy_1997-12-25". Таким образом, лексикографический порядок, который Firebase использует для строковых значений, будет таким же, как и хронологический порядок.

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

Особый вариант этого реализован библиотекой GeoFire для Firebase . Эта библиотека объединяет широту и долготу местоположения в так называемый геохэш , который затем можно использовать для выполнения запросов диапазона в реальном времени в Firebase.

3. создать пользовательский индекс программно

Еще одна альтернатива - сделать то, что мы все сделали до добавления этого нового API запросов: создать индекс в другом узле:

  "movies"
      // the same structure you have today
  "by_genre"
      "comedy"
          "by_lead"
              "Jack Nicholson"
                  "movie1"
              "Jim Carrey"
                  "movie3"
      "Horror"
          "by_lead"
              "Jack Nicholson"
                  "movie2"

Есть, вероятно, больше подходов. Например, в этом ответе выделен альтернативный пользовательский индекс в форме дерева: https://stackoverflow.com/a/34105063

Фрэнк ван Пуффелен
источник
3
Когда это официально будет выпущено на следующей неделе, несколько orderBy()вызовов вызовут ошибку, потому что клиенты в настоящее время дают неожиданные результаты. Возможно, это совпадение сработало в вашем тесте, но не предназначено для общей работы (хотя мы бы хотели добавить его!).
Роб ДиМарко
18
Это все еще актуально в 2016 году с Firebase V3? Нет ли лучших способов сделать это?
Пирс
27
Почему мы не можем использовать .equalTo('comedy')вместо .startAt('comedy').endAt('comedy')?
JCarlosR
41
Это 2018 год, нет ли простого решения для этого? Это похоже на запрос 101 в реляционной базе данных. И учитывая все комментарии к видео Дэвида о SQL # 8, я бы ожидал, что Google уже исправит это. Я пропустил какое-то обновление?
Снейк
10
@ Змей февраль 2019 года, и проблема до сих пор не устранена .... Google слишком медленный.
Supertecnoboff
48

Я написал личную библиотеку, которая позволяет упорядочивать по нескольким значениям, причем все упорядочения выполняются на сервере.

Встречайте Querybase!

Querybase берет ссылку на базу данных Firebase и массив полей, по которым вы хотите индексировать. Когда вы создаете новые записи, он автоматически обрабатывает генерацию ключей, которые допускают множественные запросы. Предостережение заключается в том, что он поддерживает только прямую эквивалентность (не меньше или больше, чем).

const databaseRef = firebase.database().ref().child('people');
const querybaseRef = querybase.ref(databaseRef, ['name', 'age', 'location']);

// Automatically handles composite keys
querybaseRef.push({ 
  name: 'David',
  age: 27,
  location: 'SF'
});

// Find records by multiple fields
// returns a Firebase Database ref
const queriedDbRef = querybaseRef
 .where({
   name: 'David',
   age: 27
 });

// Listen for realtime updates
queriedDbRef.on('value', snap => console.log(snap));
Дэвид Восток
источник
Любые определения TypeScript доступны для вашей библиотеки?
Леви Фуллер
1
Это написано в TS! Так что просто импорт :)
Дэвид Ист
7
Вы знаете, почему они сделали базу данных такой строгой?
Сед
1
Любой вариант сделать IOS Swift / версию для Android
mding5692
1
Дэвид, какой-нибудь андроид / Java эквивалент?
Снейк
3
var ref = new Firebase('https://your.firebaseio.com/');

Query query = ref.orderByChild('genre').equalTo('comedy');
query.addValueEventListener(new ValueEventListener() {
    @Override
    public void onDataChange(DataSnapshot dataSnapshot) {
        for (DataSnapshot movieSnapshot : dataSnapshot.getChildren()) {
            Movie movie = dataSnapshot.getValue(Movie.class);
            if (movie.getLead().equals('Jack Nicholson')) {
                console.log(movieSnapshot.getKey());
            }
        }
    }

    @Override
    public void onCancelled(FirebaseError firebaseError) {

    }
});
Эндрю Лам
источник
3
В объекте DataSnapshot нет метода getLead ().
Параг Кадам
3
Он преобразовал его в объект Movie и предполагая, что есть метод getLead ().
Ким Дж Фам
1
Спасибо большое. Это работает, например, больше, где оговорки
Nuwan Withanage
1

Фрэнк ответил хорошо, но Firestore представил array-contains недавно который облегчает выполнение запросов AND.

Вы можете создать filtersполе для добавления ваших фильтров. Вы можете добавить столько значений, сколько вам нужно. Например, чтобы отфильтровать по комедии и Джеку Николсону, вы можете добавить значение, comedy_Jack Nicholsonно если вы хотите, чтобы по комедии и 2014 вы могли добавить значениеcomedy_2014 без создания дополнительных полей.

{
"movies": {
    "movie1": {
        "genre": "comedy",
        "name": "As good as it gets",
        "lead": "Jack Nicholson",
        "year": 2014,
        "filters": [
          "comedy_Jack Nicholson",
          "comedy_2014"
        ]
    }
  }  
}
Тюльдер Тюрьма
источник
1

Firebase не позволяет запрашивать несколько условий. Тем не менее, я нашел способ обойти это:

Нам нужно загрузить исходные отфильтрованные данные из базы данных и сохранить их в списке массивов.

                Query query = databaseReference.orderByChild("genre").equalTo("comedy");
                databaseReference.addValueEventListener(new ValueEventListener() {
                    @Override
                    public void onDataChange(@NonNull DataSnapshot dataSnapshot) {

                        ArrayList<Movie> movies = new ArrayList<>();
                        for (DataSnapshot dataSnapshot1 : dataSnapshot.getChildren()) {
                            String lead = dataSnapshot1.child("lead").getValue(String.class);
                            String genre = dataSnapshot1.child("genre").getValue(String.class);

                            movie = new Movie(lead, genre);

                            movies.add(movie);

                        }

                        filterResults(movies, "Jack Nicholson");

                        }

                    }

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

                    }
                });

Как только мы получим исходные отфильтрованные данные из базы данных, нам нужно выполнить дополнительную фильтрацию в нашем бэкэнде.

public void filterResults(final List<Movie> list,  final String genre) {
        List<Movie> movies = new ArrayList<>();
        movies = list.stream().filter(o -> o.getLead().equals(genre)).collect(Collectors.toList());
        System.out.println(movies);

        employees.forEach(movie -> System.out.println(movie.getFirstName()));
    }
Mayur
источник
-1
ref.orderByChild("lead").startAt("Jack Nicholson").endAt("Jack Nicholson").listner....

Это будет работать

Джалил
источник