286
{
  "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 新手。如何从AND上面genre = 'comedy'lead = 'Jack Nicholson'数据中检索结果?

我有什么选择?

4

8 回答 8

277

使用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. 添加一个结合了您要过滤的值的属性

如果这还不够好,您应该考虑修改/扩展您的数据以允许您的用例。例如:您可以将流派+线索填充到您仅用于此过滤器的单个属性中。

"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 关于复合查询的文档。

于 2014-11-02T16:00:52.953 回答
56

我编写了一个个人库,允许您按多个值排序,所有排序都在服务器上完成。

认识查询库!

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));
于 2016-06-06T13:38:41.443 回答
6
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) {

    }
});
于 2016-01-20T14:22:01.547 回答
3

弗兰克的回答很好,但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"
      ]
    }
  }
}
于 2020-04-05T14:37:35.880 回答
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()));
    }
于 2020-04-14T16:28:04.760 回答
0

firebase 实时数据库中的数据为 _InternalLinkedHashMap<dynamic, dynamic>。您也可以将其转换为您的地图并非常轻松地进行查询。

例如,我有一个聊天应用程序,我使用实时数据库来存储用户的 uid 和用户是否在线的布尔值。如下图。

现在,我有一个 RealtimeDatabase 类和一个静态方法 getAllUsersOnineStatus()。

static getOnilineUsersUID() {
var dbRef = FirebaseDatabase.instance;
DatabaseReference reference = dbRef.reference().child("Online");
reference.once().then((value) {
  Map<String, bool> map = Map<String, bool>.from(value.value);
  List users = [];
  map.forEach((key, value) {
    if (value) {
      users.add(key);
    }
  });
  print(users);
});

}

它将打印 [NOraDTGaQSZbIEszidCujw1AEym2]

我是新来的,如果你知道更多,请更新答案。

于 2021-04-14T12:11:13.000 回答
0

对于 Cloud Firestore

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

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

cityRef.where('state', '==', 'CO').where('name', '==', 'Denver'); cityRef.where('state', '==', 'CA').where('population', '<', 1000000); 您只能对单个字段执行范围(<、<=、>、>=)或不等于 (!=) 比较,并且在复合查询中最多可以包含一个 array-contains 或 array-contains-any 子句:

于 2021-10-06T14:34:46.460 回答
-2
ref.orderByChild("lead").startAt("Jack Nicholson").endAt("Jack Nicholson").listner....

这将起作用。

于 2018-10-23T09:33:58.327 回答