การค้นหาขึ้นอยู่กับหลาย ๆ ที่ส่วนคำสั่งใน 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"
    }
  }  
 }

ฉันเป็นมือใหม่ Firebase ฉันจะดึงผลลัพธ์จากข้อมูลข้างต้นที่genre = 'comedy'AND ได้lead = 'Jack Nicholson'อย่างไร

ฉันมีตัวเลือกอะไรบ้าง

คำตอบ:


238

เมื่อใช้Query API ของ Firebaseคุณอาจถูกลองทำสิ่งนี้:

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

แต่ในฐานะ @RobDiMarco จาก Firebase กล่าวว่าในความคิดเห็น:

การ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. เพิ่มคุณสมบัติที่รวมค่าที่คุณต้องการกรอง

หากยังไม่ดีพอคุณควรพิจารณาแก้ไข / ขยายข้อมูลเพื่อให้สามารถใช้งานได้ ตัวอย่างเช่นคุณสามารถใส่ประเภท + นำไปสู่พร็อพเพอร์ตี้เดียวที่คุณใช้สำหรับตัวกรองนี้

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

เดวิดตะวันออกได้เขียนห้องสมุดเรียก 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 ทำกับค่าสตริงจะเหมือนกับการเรียงตามลำดับเวลา

การรวมค่าในพร็อพเพอร์ตี้นี้สามารถใช้งานได้มากกว่าสองค่า แต่คุณสามารถทำฟิลเตอร์พิสัยระหว่างค่าสุดท้ายในคุณสมบัติคอมโพสิต

ความแตกต่างที่พิเศษมากของการนี้จะดำเนินการโดยห้องสมุด GeoFire สำหรับ Firebase ห้องสมุดนี้รวมละติจูดและลองจิจูดของตำแหน่งไว้ในGeohash ที่เรียกว่าซึ่งสามารถใช้ในการทำแบบสอบถามแบบเรียลไทม์บน Firebase

3. สร้างดัชนีที่กำหนดเองโดยทางโปรแกรม

อีกทางเลือกหนึ่งคือทำสิ่งที่เราทำเสร็จก่อนที่จะเพิ่ม Query 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
สิ่งนี้ยังคงเกี่ยวข้องในปี 2559 กับ Firebase V3 หรือไม่ ไม่มีวิธีที่ดีกว่าในการทำสิ่งนี้ใช่ไหม
ท่าเรือ

27
ทำไมเราไม่สามารถใช้.equalTo('comedy')แทน.startAt('comedy').endAt('comedy')?
JCarlosR

41
มันคือปี 2018 ไม่มีวิธีแก้ปัญหาง่าย ๆ สำหรับสิ่งนี้หรือไม่? นี่เป็นเหมือนแบบสอบถาม 101 ในฐานข้อมูลเชิงสัมพันธ์ และเมื่อได้รับความคิดเห็นทั้งหมดเกี่ยวกับวิดีโอของเดวิดที่พูดถึง SQL # 8 ฉันคาดว่า Google จะแก้ไขได้ในตอนนี้ ฉันพลาดการอัพเดทบ้างไหม?
งู

10
@Snake ก.พ. 2019 และปัญหายังไม่ได้รับการแก้ไข .... 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 ใด ๆ ที่มีอยู่สำหรับห้องสมุดของคุณ?
Levi Fuller

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
ไม่มีเมธอด getLead () บนวัตถุ DataSnapshot
Parag Kadam

3
เขาแปลงให้เป็นวัตถุภาพยนตร์และสมมติว่ามีเมธอด getLead ()
Kim G Pham

1
ขอบคุณมาก มันใช้งานได้มากกว่าที่ที่อนุประโยค
Nuwan Withanage

1

คำตอบของ Frank นั้นดี แต่ Firestore array-containsเพิ่งเปิดตัวเมื่อเร็ว ๆ นี้ซึ่งทำให้ง่ายต่อการค้นหาและใช้แบบสอบถาม

คุณสามารถสร้างfiltersฟิลด์เพื่อเพิ่มตัวกรองของคุณ คุณสามารถเพิ่มค่าได้มากเท่าที่คุณต้องการ ตัวอย่างเช่นการกรองตามภาพยนตร์ตลกและJack Nicholsonคุณสามารถเพิ่มมูลค่าได้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()));
    }

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.