Firebase 中基于多 where 子句的查询

{
"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 的新手。我如何从以上的 在哪里 ABC0和 lead = 'Jack Nicholson'数据检索结果?

我还有什么选择?

244355 次浏览

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

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


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

这是可行的。

Frank的回答很好,但是Firestore最近引入了array-contains,使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"
]
}
}
}
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实时数据库的数据is _InternalLinkedHashMap<dynamic, dynamic> 你也可以把它转换成你的地图和查询非常容易

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

.

现在,我有一个类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]

我是扑球新手,如果你知道更多请更新答案。

对于云壁炉

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

< p >复合查询 您可以将多个相等操作符(==或数组包含)方法链接起来,以创建更具体的查询(逻辑与)。但是,必须创建一个复合索引,将相等操作符与不等式操作符<, <=, >, and !=结合起来
citiesRef.where('state', '==', 'CO').where('name', '==', 'Denver');
citiesRef.where('state', '==', 'CA').where('population', '<', 1000000);

你只能在单个字段上执行范围(<, <=, >, >=)或不等于(!=)的比较,并且你最多可以在复合查询中包含一个array-contains或array-contains-any子句: