Buka konsol

Bekerja dengan Array, List, dan Set

Banyak aplikasi perlu menyimpan struktur data seperti array dalam dokumen. Misalnya, dokumen pengguna mungkin berisi sebuah daftar 5 teman teratas pengguna, atau dokumen entri blog mungkin berisi serangkaian kategori terkait.

Meskipun dapat menyimpan array, Cloud Firestore tidak mendukung pembuatan kueri anggota array atau mengupdate elemen array tunggal. Namun, Anda tetap dapat mencontoh data semacam ini dengan memanfaatkan kemampuan Cloud Firestore lainnya.

Sebelum melanjutkan, pastikan Anda telah membaca artikel mengenai model data Cloud Firestore.

Solusi: Peta nilai

Salah satu cara untuk mencontoh data seperti array di Cloud Firestore adalah dengan menunjukkannya sebagai peta dari kunci ke nilai boolean. Misalnya, anggaplah bahwa dalam aplikasi blogging Anda, setiap entri ditandai dengan sejumlah kategori. Jika Anda menggunakan array, dokumen entri blog akan terlihat seperti contoh berikut:

Web

// Sample document in the 'posts' collection.
{
    title: "My great post",
    categories: [
        "technology",
        "opinion",
        "cats"
    ]
}

Swift

struct PostArray {

    let title: String
    let categories: [String]

    init(title: String, categories: [String]) {
        self.title = title
        self.categories = categories
    }

}

let myArrayPost = PostArray(title: "My great post",
                            categories: ["technology", "opinion", "cats"])

Android

public class ArrayPost {
    String title;
    String[] categories;

    public ArrayPost(String title, String[] categories) {
        this.title = title;
        this.categories = categories;
    }
}
ArrayPost myArrayPost = new ArrayPost("My great post", new String[]{
        "technology", "opinion", "cats"
});

Bagaimana jika Anda ingin membuat kueri untuk semua postingan yang merupakan bagian dari kategori "cats"? Dengan struktur data di atas, tidak ada cara untuk membuat kueri ini.

Pertimbangkan struktur data alternatif ini dengan setiap kategori berupa kunci dalam sebuah peta dan semua nilainya adalah true:

Web

// Sample document in the 'posts' collection
{
    title: "My great post",
    categories: {
        "technology": true,
        "opinion": true,
        "cats": true
    }
}

Swift

struct PostDict {

    let title: String
    let categories: [String: Bool]

    init(title: String, categories: [String: Bool]) {
        self.title = title
        self.categories = categories
    }

}

let post = PostDict(title: "My great post", categories: [
    "technology": true,
    "opinion": true,
    "cats": true
])

Android

public class MapPost {
    String title;
    Map<String,Boolean> categories;

    public MapPost(String title, Map<String,Boolean> categories) {
        this.title = title;
        this.categories = categories;
    }
}
Map<String, Boolean> categories = new HashMap<>();
categories.put("technology", true);
categories.put("opinion", true);
categories.put("cats", true);
MapPost myMapPost = new MapPost("My great post", categories);

Pembuatan kueri untuk semua entri blog dalam 1 kategori kini dapat dilakukan dengan mudah:

Web

 // Find all documents in the 'posts' collection that are
// in the 'cats' category.
db.collection('posts')
    .where('categories.cats', '==', true)
    .get()
    .then(() => {
        // ...
    });)

Swift

db.collection("posts")
    .whereField("categories.cats", isEqualTo: true)
    .getDocuments() { (querySnapshot, err) in

        // ...

}

Android

db.collection("posts")
        .whereEqualTo("categories.cats", true)
        .get()
        .addOnCompleteListener(new OnCompleteListener<QuerySnapshot>() {
            // ...
        });

Teknik ini bergantung pada fakta bahwa Cloud Firestore membuat indeks bawaan untuk semua kolom dokumen, bahkan kolom dalam peta bertingkat.

Bagaimana jika Anda ingin melakukan kueri yang lebih kompleks, seperti semua entri blog dalam 1 kategori yang diurutkan berdasarkan tanggal yang diposting? Anda bisa mencoba kueri seperti ini:

Kueri tidak valid

Web

 db.collection('posts')
    .where('categories.cats', '==', true)
    .orderBy('timestamp');)

Swift

db.collection("posts")
    .whereField("categories.cats", isEqualTo: true)
    .order(by: "timestamp")

Android

db.collection("posts")
        .whereEqualTo("categories.cats", true)
        .orderBy("timestamp");

Kueri di beberapa kolom dengan filter rentang memerlukan indeks komposit. Sayangnya, ini tidak memungkinkan dengan pendekatan yang ditunjukkan di atas. Indeks harus ditentukan pada lokasi kolom tertentu. Anda harus membuat indeks di setiap lokasi kolom yang memungkinkan (seperti categories.cats atau categories.opinion), yang tidak mungkin dilakukan sebelumnya jika kategori Anda dibuat oleh pengguna.

Anda dapat mengatasi keterbatasan ini dengan mengenkode semua informasi untuk kueri ke dalam nilai peta:

Web

// The value of each entry in 'categories' is a unix timestamp
{
  title: "My great post",
  categories: {
    technology: 1502144665,
    opinion: 1502144665,
    cats: 1502144665
  }
}

Swift

struct PostDictAdvanced {

    let title: String
    let categories: [String: UInt64]

    init(title: String, categories: [String: UInt64]) {
        self.title = title
        self.categories = categories
    }

}

let dictPost = PostDictAdvanced(title: "My great post", categories: [
    "technology": 1502144665,
    "opinion": 1502144665,
    "cats": 1502144665
])

Android

public class MapPostAdvanced {
    String title;
    Map<String,Long> categories;

    public MapPostAdvanced(String title, Map<String,Long> categories) {
        this.title = title;
        this.categories = categories;
    }
}
Map<String, Long> categories = new HashMap<>();
categories.put("technology", 1502144665L);
categories.put("opinion", 1502144665L);
categories.put("cats", 1502144665L);
MapPostAdvanced myMapPostAdvanced = new MapPostAdvanced("My great post", categories);

Sekarang, Anda dapat mengekspresikan kueri yang diinginkan sebagai serangkaian condition pada sebuah bidang untuk menghindari kebutuhan akan indeks gabungan:

Kueri yang valid

Web

 db.collection('posts')
    .where('categories.cats', '>', 0)
    .orderBy('categories.cats');)

Swift

db.collection("posts")
    .whereField("categories.cats", isGreaterThan: 0)
    .order(by: "categories.cats")

Android

db.collection("posts")
        .whereGreaterThan("categories.cats", 0)
        .orderBy("categories.cats");

Dibandingkan dengan penggunaan peta boolean, teknik ini semakin menyulitkan update data karena nilai peta harus tetap sinkron satu sama lainnya.

Batasan

Solusi yang ditunjukkan di atas adalah cara yang bagus untuk menyimulasikan struktur seperti array di Cloud Firestore. Namun, Anda harus mengetahui sejumlah batasan berikut:

  • Batasan pengindeksan - Dokumen tunggal hanya dapat memiliki 20.000 properti untuk menggunakan indeks bawaan Cloud Firestore. Jika struktur data seperti array berkembang menjadi puluhan ribu anggota, Anda mungkin akan mencapai batasan ini.
  • Ukuran dokumen - Cloud Firestore dioptimalkan untuk dokumen berukuran kecil dan memberlakukan batas ukuran 1 MB pada dokumen. Jika array Anda dapat berkembang secara sembarang, sebaiknya gunakan subkoleksi mengingat performa penskalaannya yang lebih baik.