根据Firebase中的多个where子句进行查询


244
{
"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'lead = 'Jack Nicholson'

我有什么选择?

Answers:


238

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

如果这还不够好,则应考虑修改/扩展数据以允许用例。例如:您可以将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对字符串值进行的词典编排顺序将与时间顺序相同。

属性中值的这种组合可以使用两个以上的值,但是您只能对Composite属性中的最后一个值进行范围过滤。

这是一个非常特殊的变体,由FirebaseGeoFire库实现。该库将位置的纬度和经度组合到所谓的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


3
下周正式发布此命令时,多个orderBy()调用将引发错误,因为客户端当前给出了意外的结果。它有可能在您的测试中偶然起作用,但并不是为了一般情况而设计(尽管我们很乐意添加它!)。
Rob DiMarco

18
这在2016年与Firebase V3仍然有用吗?那里没有更好的方法吗?
码头

27
为什么我们不能使用.equalTo('comedy')代替.startAt('comedy').endAt('comedy')
JCarlosR '16

41
现在是2018年,对此没有简单的解决方案吗?这就像关系数据库中的查询101。考虑到David的视频中有关SQL#8的所有评论,我希望Google到现在就解决它。我错过了一些更新吗?

10
@Snake 2019年2月,问题仍然没有解决.... Google太慢了。
Supertecnoboff

48

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

认识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定义?
列维·富勒

1
它是用TS编写的!因此,只需导入即可:)
David East

7
您知道为什么他们如此限制数据库吗?
Ced

1
做IOS Swift / Android版本的任何选项
mding5692

1
大卫,有什么与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) {

    }
});

3
DataSnapshot对象上没有getLead()方法。
Parag Kadam

3
他将其转换为Movie对象,并假设存在一个getLead()方法。
金G Pham

1
非常感谢。它正在运行,例如更多where子句
Nuwan Withanage

1

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"
        ]
    }
  }  
}

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()));
    }

-1
ref.orderByChild("lead").startAt("Jack Nicholson").endAt("Jack Nicholson").listner....

这将起作用。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.