Запит, що базується на множині де клаузи у 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'ANDlead = 'Jack Nicholson' ?

Які у мене варіанти?

Відповіді:


238

Використовуючи 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 . Ця бібліотека поєднує широту і довготу місця в так званому Geohash , який потім може бути використаний для запитів в режимі реального часу на 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
@Snake Feb 2019, і проблема все ще не виправлена ​​.... Google занадто повільний.
Supertecnoboff

48

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

Знайомтесь із запитом!

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
чи знаєте ви, чому вони зробили базу настільки обмежувальною?
Ced

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нещодавно, що полегшує виконання І запитів.

Ви можете створити 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()));
    }

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.