基于 Firebase 中多个 where 子句的查询
Posted
技术标签:
【中文标题】基于 Firebase 中多个 where 子句的查询【英文标题】:Query based on multiple where clauses in Firebase 【发布时间】:2021-08-06 00:33:24 【问题描述】:
"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'
?
我有什么选择?
【问题讨论】:
【参考方案1】:使用Firebase's 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 在 cmets 中所说:
多次
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 写了一封library called QueryBase that helps with generating such properties。
您甚至可以进行相对/范围查询,假设您希望允许按类别和年份查询电影。你会使用这个数据结构:
"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 对字符串值的字典顺序将与时间顺序相同。
属性中值的这种组合可以处理两个以上的值,但您只能对复合属性中的最后一个值进行范围过滤。
GeoFire library for Firebase 实现了一个非常特殊的变体。该库将位置的纬度和经度组合成所谓的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://***.com/a/34105063
如果这些选项都不适合您,但您仍想将数据存储在 Firebase 中,您也可以考虑使用其Cloud Firestore database。
Cloud Firestore 可以在单个查询中处理多个相等过滤器,但只能处理一个范围过滤器。在引擎盖下,它本质上使用相同的查询模型,但就像它为您自动生成复合属性一样。请参阅 compound queries 上的 Firestore 文档。
【讨论】:
这在 2016 年仍然与 Firebase V3 相关吗?没有更好的方法吗? 我编写了一个个人库来帮助将#2 解决方案包装成一个易于使用的 API。该库名为 Querybase,可供 JS 用户使用:github.com/davideast/Querybasehttps://github.com/davideast/… 为什么我们不能使用.equalTo('comedy')
而不是.startAt('comedy').endAt('comedy')
?
现在是2018年,有没有简单的解决办法?这就像关系数据库中的查询 101。鉴于 David 的视频中的所有 cmets 都在谈论 SQL #8,我希望 Google 现在能够修复它。我错过了一些更新吗?
@Snake 2019 年 2 月,问题仍未解决......谷歌太慢了。【参考方案2】:
我编写了一个个人库,允许您按多个值排序,所有排序都在服务器上完成。
Meet 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));
【讨论】:
您的库有任何可用的 TypeScript 定义吗? 用TS写的!所以只需导入:) 你知道他们为什么让数据库如此严格吗? 任意选择 ios Swift/android 版本 大卫,有任何 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)
);
【讨论】:
DataSnapshot 对象上没有 getLead() 方法。 他将其转换为 Movie 对象并假设有一个 getLead() 方法。 非常感谢。它的工作原理是更多的 where 子句 我正在使用 Firestore。在这种情况下,我们如何使用 where 条件 - 获取“(senderid == loggedinuserid and receiverid == 10)或(senderid == 10 and receiverid == loggedinuserid)”的所有聊天对话?请建议。谢谢。【参考方案4】:Frank 的回答很好,但 Firestore 最近推出了array-contains
,这使得 AND 查询更容易。
您可以创建一个filters
字段来添加过滤器。您可以根据需要添加任意数量的值。例如,要按 comedy 和 Jack Nicholson 过滤,您可以添加值 comedy_Jack Nicholson
,但如果您还想按 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"
]
【讨论】:
【参考方案5】: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()));
【讨论】:
【参考方案6】:firebase 实时数据库中的数据为 _InternalLinkedHashMap
例如,我有一个聊天应用程序,我使用实时数据库来存储用户的 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]
我是新来的,如果您知道更多,请更新答案。
【讨论】:
【参考方案7】:对于 Cloud Firestore
https://firebase.google.com/docs/firestore/query-data/queries#compound_queries
复合查询 您可以链接多个相等运算符(== 或数组包含)方法来创建更具体的查询(逻辑 AND)。但是,您必须创建一个复合索引来将等式运算符与不等式运算符 和 != 结合起来。
citiesRef.where('state', '==', 'CO').where('name', '==', 'Denver'); cityRef.where('state', '==', 'CA').where('population', '、>=)或不等于 (!=) 比较,并且在复合查询中最多可以包含一个 array-contains 或 array-contains-any 子句:
【讨论】:
【参考方案8】:ref.orderByChild("lead").startAt("Jack Nicholson").endAt("Jack Nicholson").listner....
这会起作用。
【讨论】:
以上是关于基于 Firebase 中多个 where 子句的查询的主要内容,如果未能解决你的问题,请参考以下文章
如何在 mysqli 准备语句中使用多个内部连接和多个 WHERE 子句? [复制]
如何在 Firebase-DB 中查询“WHERE”子句之类的数据?