Firebaseの複数のwhere句に基づくクエリ


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'ANDでlead = 'Jack Nicholson'結果を取得できますか?

どのようなオプションがありますか?

回答:


238

FirebaseのQuery APIを使用すると、これを試してみたくなるかもしれません。

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

しかし、Firebaseの@RobDiMarcoがコメントで言っているように:

複数のorderBy()呼び出しはエラーをスローします

したがって、上記の私のコードは機能しません

うまくいく3つのアプローチを知っています。

1.サーバーで大部分をフィルタリングし、残りをクライアントで行います

あなたがすることができません、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);
  });

David Eastは、このようなプロパティの生成に役立つ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が文字列値に対して行う辞書式順序は、年代順と同じになります。

プロパティの値のこの組み合わせは、3つ以上の値で機能しますが、複合プロパティの最後の値に対してのみ範囲フィルターを実行できます。

これの非常に特殊なバリアントは、FirebaseのGeoFireライブラリによって実装されます。このライブラリは、場所の緯度と経度を組み合わせて、いわゆるGeohashを作成します。これを使用して、Firebaseでリアルタイムの範囲クエリを実行できます。

3.プログラムでカスタムインデックスを作成する

さらに別の方法は、この新しいQuery 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()れると、クライアントが現在予期しない結果を返すため、複数の呼び出しはエラーをスローします。テストで偶然機能した可能性がありますが、これを一般的に機能するように構築されていません(追加したいのですが!)。
Rob DiMarco、2014年

18
これは2016年もFirebase V3に関連していますか?これを行うためのより良い方法はありませんか?
桟橋

27
.equalTo('comedy')代わりに使用できないのはなぜ.startAt('comedy').endAt('comedy')ですか?
JCarlosR 2016年

41
それは2018年ですが、これに対する簡単な解決策はありませんか?これは、リレーショナルデータベースのクエリ101に似ています。そして、SQL#8について話しているDavidのビデオへのすべてのコメントを考えると、私はGoogleが今までにそれを修正することを期待していました。更新を見逃しましたか?
ヘビ

10
@Snake 2019年2月、問題はまだ修正されていません... 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定義はありますか?
Levi Fuller

1
TSで書かれています!したがって、インポートするだけです:)
David East

7
彼らがデータベースをそんなに制限した理由を知っていますか?
Ced

1
IOS Swift / Androidバージョンを実行するためのオプション
mding5692

1
David、それに相当するAndroid / 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()メソッドはありません。
Parag Kadam

3
彼はそれをMovieオブジェクトに変換し、getLead()メソッドがあると仮定しました。
Kim G Pham 2017

1
どうもありがとう。それはより多くのwhere句のように機能しています
Nuwan Withanage

1

フランクの答えは良いですが、array-contains最近Firestoreが導入され、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....

これは動作します。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.