基于 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 字段来添加过滤器。您可以根据需要添加任意数量的值。例如,要按 comedyJack 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 子句的查询的主要内容,如果未能解决你的问题,请参考以下文章

查询中的Firebase多个WHERE子句[重复]

Firestore:多个条件 where 子句

如何在 mysqli 准备语句中使用多个内部连接和多个 WHERE 子句? [复制]

如何在 Firebase-DB 中查询“WHERE”子句之类的数据?

使用文档 auto-id firebase 查询“where”子句

Firebase查询:“where”子句不起作用,继续返回所有文档