【问题标题】:Query based on multiple where clauses in Firebase基于 Firebase 中多个 where 子句的查询
【发布时间】: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'

我有什么选择?

【问题讨论】:

    标签: firebase firebase-realtime-database


    【解决方案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://stackoverflow.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....
            

            这会起作用。

            【讨论】:

              猜你喜欢
              相关资源
              最近更新 更多