{
  "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的新手。我如何从上面的数据中检索一个结果类型= '喜剧'和领导= '杰克尼克尔森'?

我有什么选择?


当前回答

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

其他回答

使用Firebase的查询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.以编程方式创建自定义索引

还有一种替代方法是做我们在添加这个新的查询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


如果这些选项都不适合你,但你仍然想将数据存储在Firebase中,你也可以考虑使用它的Cloud Firestore数据库。

Cloud Firestore可以在一个查询中处理多个相等过滤器,但只能处理一个范围过滤器。本质上,它使用相同的查询模型,但它为您自动生成复合属性。请参阅Firestore关于复合查询的文档。

对于云壁炉

https://firebase.google.com/docs/firestore/query-data/queries#compound_queries

复合查询 您可以将多个相等操作符(==或数组包含)方法链接起来,以创建更具体的查询(逻辑与)。但是,您必须创建一个复合索引来将相等操作符与不等操作符、<、<=、>和!=组合在一起。

citiesRef.where('state', '==', 'CO').where('name', '==', 'Denver');
citiesRef.where('state', '==', 'CA').where('population', '<', 1000000);

你可以只在一个字段上执行range(<, <=, >, >=)或not equals(!=)比较,并且在复合查询中你最多可以包含一个array-contains或array-contains-any子句:

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

这是可行的。

Frank的回答很好,但是Firestore最近引入了数组包含,使得进行AND查询更容易。

您可以创建一个filters字段来添加筛选器。您可以根据需要添加任意多的值。例如,通过喜剧和杰克·尼克尔森过滤,你可以添加值comedy_杰克·尼克尔森,但如果你也想通过喜剧和2014,你可以添加值comedy_2014,而不需要创建更多字段。

{
  "movies": {
    "movie1": {
      "genre": "comedy",
      "name": "As good as it gets",
      "lead": "Jack Nicholson",
      "year": 2014,
      "filters": [
        "comedy_Jack Nicholson",
        "comedy_2014"
      ]
    }
  }
}

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