基于Firebase中多个where子句的查询

firebase firebase-realtime-database


{
"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'

我有哪些选择?





Answer 1 Frank van Puffelen


使用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() 调用将引发错误

所以我上面的代码将无法工作

据我所知,有三种方法会有效果。

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.添加一个属性,结合你要过滤的值,在

如果这还不够好,你应该考虑修改你的数据,以允许你的用例。例如:你可以把genre+lead塞进一个单独的属性中,而这个属性只是用于这个过滤器。

"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对字符串值进行的词典编排顺序将与时间顺序相同。

这种复合属性中的值的组合可以使用两个以上的值,但你只能对复合属性中的最后一个值进行范围过滤。

这是一个非常特殊的变体,由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




Answer 2 David East


我写了一个个人库,允许你用多个值来排序,所有的排序都在服务器上完成。

认识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));



Answer 3 Andrew Lam Yat Weng


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) {

    }
});



Answer 4 Tidder Jail


Frank的回答很好,但是Firestore 最近引入了 array-contains ,这使得执行AND查询变得更加容易。

您可以创建一个 filters 字段以添加过滤器。您可以根据需要添加任意多个值。例如,按喜剧Jack Nicholson进行过滤,您可以添加值 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"
        ]
    }
  }  
}



Answer 5 mayur


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