Truy vấn dựa trên nhiều mệnh đề trong Firebase


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

Tôi là một người mới chơi Firebase. Làm cách nào tôi có thể truy xuất kết quả từ dữ liệu ở trên đâugenre = 'comedy'lead = 'Jack Nicholson' ?

Tôi có những lựa chọn nào?

Câu trả lời:


238

Sử dụng API truy vấn của Firebase , bạn có thể muốn thử điều này:

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

Nhưng như @RobDiMarco từ Firebase nói trong các bình luận:

nhiều orderBy()cuộc gọi sẽ gây ra lỗi

Vì vậy, mã của tôi ở trên sẽ không hoạt động .

Tôi biết ba cách tiếp cận sẽ làm việc.

1. lọc hầu hết trên máy chủ, làm phần còn lại trên máy khách

Những gì bạn có thể làm là thực thi một cái orderBy().startAt()./endAt()trên máy chủ, kéo xuống dữ liệu còn lại và lọc theo mã JavaScript trên máy khách của bạn.

ref
  .orderBy('genre')
  .equalTo('comedy')
  .on('child_added', function(snapshot) { 
      var movie = snapshot.val();
      if (movie.lead == 'Jack Nicholson') {
          console.log(movie);
      }
  });

2. thêm thuộc tính kết hợp các giá trị mà bạn muốn lọc

Nếu điều đó không đủ tốt, bạn nên xem xét sửa đổi / mở rộng dữ liệu của mình để cho phép trường hợp sử dụng của bạn. Ví dụ: bạn có thể nhồi thể loại + dẫn vào một thuộc tính mà bạn chỉ sử dụng cho bộ lọc này.

"movie1": {
    "genre": "comedy",
    "name": "As good as it gets",
    "lead": "Jack Nicholson",
    "genre_lead": "comedy_Jack Nicholson"
},...

Về cơ bản, bạn đang xây dựng chỉ mục nhiều cột của riêng mình và có thể truy vấn nó bằng:

ref
  .orderBy('genre_lead')
  .equalTo('comedy_Jack Nicholson')
  .on('child_added', function(snapshot) { 
      var movie = snapshot.val();
      console.log(movie);
  });

David East đã viết một thư viện có tên QueryBase giúp tạo các thuộc tính như vậy .

Bạn thậm chí có thể thực hiện các truy vấn tương đối / phạm vi, giả sử rằng bạn muốn cho phép truy vấn phim theo thể loại và năm. Bạn sẽ sử dụng cấu trúc dữ liệu này:

"movie1": {
    "genre": "comedy",
    "name": "As good as it gets",
    "lead": "Jack Nicholson",
    "genre_year": "comedy_1997"
},...

Và sau đó truy vấn các phim hài của thập niên 90 với:

ref
  .orderBy('genre_year')
  .startAt('comedy_1990')
  .endAt('comedy_2000')
  .on('child_added', function(snapshot) { 
      var movie = snapshot.val();
      console.log(movie);
  });

Nếu bạn cần lọc nhiều hơn chỉ trong năm, hãy đảm bảo thêm các phần ngày khác theo thứ tự giảm dần, ví dụ "comedy_1997-12-25". Bằng cách này, thứ tự từ điển mà Firebase thực hiện trên các giá trị chuỗi sẽ giống như thứ tự thời gian.

Sự kết hợp các giá trị này trong một thuộc tính có thể hoạt động với nhiều hơn hai giá trị, nhưng bạn chỉ có thể thực hiện một bộ lọc phạm vi trên giá trị cuối cùng trong thuộc tính tổng hợp.

Một biến thể rất đặc biệt của điều này được thư viện GeoFire thực hiện cho Firebase . Thư viện này kết hợp vĩ độ và kinh độ của một vị trí vào cái gọi là Geohash , sau đó có thể được sử dụng để thực hiện các truy vấn phạm vi thời gian thực trên Firebase.

3. tạo một chỉ mục tùy chỉnh theo chương trình

Tuy nhiên, một cách khác là làm tất cả những gì chúng ta đã làm trước khi API truy vấn mới này được thêm vào: tạo một chỉ mục trong một nút khác:

  "movies"
      // the same structure you have today
  "by_genre"
      "comedy"
          "by_lead"
              "Jack Nicholson"
                  "movie1"
              "Jim Carrey"
                  "movie3"
      "Horror"
          "by_lead"
              "Jack Nicholson"
                  "movie2"

Có lẽ có nhiều cách tiếp cận hơn. Ví dụ: câu trả lời này làm nổi bật một chỉ mục tùy chỉnh hình cây thay thế: https://stackoverflow.com/a 432105063


3
Khi điều này được phát hành chính thức vào tuần tới, nhiều orderBy()cuộc gọi sẽ xuất hiện lỗi, vì các máy khách hiện đang cho kết quả không mong muốn. Có thể nó tình cờ hoạt động trong thử nghiệm của bạn, nhưng không được xây dựng để hoạt động chung chung (mặc dù chúng tôi muốn thêm nó!).
Rob DiMarco

18
Điều này có còn phù hợp trong năm 2016 với Firebase V3 không? Không có cách nào tốt hơn để làm điều này?
Cầu tàu

27
Tại sao chúng ta không thể sử dụng .equalTo('comedy')thay vì .startAt('comedy').endAt('comedy')?
JCarlosR

41
Đó là năm 2018, không có giải pháp đơn giản cho việc này? Điều này giống như truy vấn 101 trong cơ sở dữ liệu quan hệ. Và đưa ra tất cả các nhận xét về video của David nói về SQL # 8, tôi đã mong đợi Google sẽ sửa nó ngay bây giờ. Tôi đã bỏ lỡ một số cập nhật?
Rắn

10
@Snake tháng 2 năm 2019 và sự cố vẫn chưa được khắc phục .... Google quá chậm.
Supertecnoboff

48

Tôi đã viết một thư viện cá nhân cho phép bạn đặt hàng theo nhiều giá trị, với tất cả các thứ tự được thực hiện trên máy chủ.

Gặp gỡ Querybase!

Querybase lấy tham chiếu cơ sở dữ liệu Firebase và một loạt các trường bạn muốn lập chỉ mục. Khi bạn tạo bản ghi mới, nó sẽ tự động xử lý việc tạo các khóa cho phép nhiều truy vấn. Thông báo trước là nó chỉ hỗ trợ tương đương thẳng (không nhỏ hơn hoặc lớn hơn).

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

Bất kỳ định nghĩa TypeScript có sẵn cho thư viện của bạn?
Levi Fuller

1
Nó được viết bằng TS! Vì vậy, chỉ cần nhập :)
David East

7
Bạn có biết tại sao họ làm cho cơ sở dữ liệu hạn chế như vậy?
Ced

1
Bất kỳ tùy chọn nào để thực hiện phiên bản iOS Swift / Android
mding5692

1
David, bất kỳ tương đương Android / Java với nó?
Rắn

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
Không có phương thức getLead () trên đối tượng DataSnapshot.
Parag Kadam

3
Anh ta đã chuyển đổi nó thành đối tượng Movie và giả sử có metho getLead ().
Kim G Phạm

1
Cảm ơn rất nhiều. Nó hoạt động như nhiều điều khoản hơn
Nuwan Withanage 18/07/18

1

Câu trả lời của Frank là tốt nhưng Firestore đã giới thiệu array-containsgần đây giúp thực hiện các truy vấn AND dễ dàng hơn.

Bạn có thể tạo một filterstrường để thêm bạn các bộ lọc. Bạn có thể thêm bao nhiêu giá trị mà bạn cần. Ví dụ: để lọc theo hàiJack Nicholson, bạn có thể thêm giá trị comedy_Jack Nicholsonnhưng nếu bạn cũng muốn theo hài2014 bạn có thể thêm giá trị comedy_2014mà không cần tạo thêm trường.

{
"movies": {
    "movie1": {
        "genre": "comedy",
        "name": "As good as it gets",
        "lead": "Jack Nicholson",
        "year": 2014,
        "filters": [
          "comedy_Jack Nicholson",
          "comedy_2014"
        ]
    }
  }  
}

1

Firebase không cho phép truy vấn với nhiều điều kiện. Tuy nhiên, tôi đã tìm ra cách để làm điều này:

Chúng ta cần tải xuống dữ liệu được lọc ban đầu từ cơ sở dữ liệu và lưu trữ nó trong một danh sách mảng.

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

                    }
                });

Khi chúng tôi có được dữ liệu được lọc ban đầu từ cơ sở dữ liệu, chúng tôi cần thực hiện thêm bộ lọc trong phần phụ trợ của chúng tôi.

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....

Điều này sẽ làm việc.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.