Запрос на основе нескольких предложений 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' ?

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

Ответы:


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()));
    }

-1
ref.orderByChild("lead").startAt("Jack Nicholson").endAt("Jack Nicholson").listner....

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

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.