Kueri NDB

Aplikasi dapat menggunakan kueri untuk menelusuri Datastore guna menemukan entity yang cocok dengan kriteria penelusuran tertentu yang disebut filter.

Ringkasan

Aplikasi dapat menggunakan kueri untuk menelusuri Datastore guna menemukan entity yang cocok dengan kriteria penelusuran tertentu yang disebut filter. Misalnya, aplikasi yang melacak beberapa buku tamu dapat menggunakan kueri untuk mengambil pesan dari satu buku tamu, yang diurutkan berdasarkan tanggal:

from google.appengine.ext import ndb
...
class Greeting(ndb.Model):
    """Models an individual Guestbook entry with content and date."""
    content = ndb.StringProperty()
    date = ndb.DateTimeProperty(auto_now_add=True)

    @classmethod
    def query_book(cls, ancestor_key):
        return cls.query(ancestor=ancestor_key).order(-cls.date)
...
class MainPage(webapp2.RequestHandler):
    GREETINGS_PER_PAGE = 20

    def get(self):
        guestbook_name = self.request.get('guestbook_name')
        ancestor_key = ndb.Key('Book', guestbook_name or '*notitle*')
        greetings = Greeting.query_book(ancestor_key).fetch(
            self.GREETINGS_PER_PAGE)

        self.response.out.write('<html><body>')

        for greeting in greetings:
            self.response.out.write(
                '<blockquote>%s</blockquote>' % cgi.escape(greeting.content))

        self.response.out.write('</body></html>')

Beberapa kueri lebih kompleks daripada yang lain; datastore memerlukan indeks pre-built untuk ini. Indeks yang telah di-build ini ditetapkan dalam file konfigurasi, index.yaml. Di server pengembangan, jika Anda menjalankan kueri yang membutuhkan indeks yang belum Anda tentukan, server pengembangan akan otomatis menambahkannya ke index.yaml. Namun, di situs Anda, kueri yang membutuhkan indeks dan belum ditentukan akan gagal. Dengan demikian, siklus pengembangan umumnya adalah mencoba kueri baru di server pengembangan, lalu mengupdate situs agar menggunakan index.yaml yang berubah secara otomatis. Anda dapat mengupdate index.yaml secara terpisah dari mengupload aplikasi dengan menjalankan gcloud app deploy index.yaml. Jika datastore Anda memiliki banyak entity, perlu waktu lama untuk membuat indeks baru untuk entity tersebut. Dalam hal ini, sebaiknya perbarui definisi indeks sebelum mengupload kode yang menggunakan indeks baru. Anda dapat menggunakan Konsol Admin untuk mencari tahu kapan indeks selesai dibangun.

App Engine Datastore secara native mendukung filter untuk pencocokan persis (operator ==) dan perbandingan (operator <, <=, >, dan >=). Solusi ini mendukung penggabungan beberapa filter menggunakan operasi AND Boolean, dengan beberapa batasan (lihat di bawah).

Selain operator native, API juga mendukung operator !=, yang menggabungkan grup filter menggunakan operasi OR Boolean, dan operasi IN, yang menguji kesetaraan dengan salah satu daftar kemungkinan nilai (seperti operator 'in' Python). Operasi ini tidak memetakan 1:1 ke operasi native Datastore; jadi relatif sedikit unik dan lambat. Pengujian ini diimplementasikan menggunakan penggabungan aliran hasil dalam memori. Perhatikan bahwa p != v diimplementasikan sebagai "p < v OR p > v". (Hal ini penting untuk properti berulang.)

Keterbatasan: Datastore menerapkan beberapa batasan pada kueri. Melanggar hal ini akan menyebabkan pengecualian. Misalnya, menggabungkan terlalu banyak filter, menggunakan ketidaksetaraan untuk beberapa properti, atau menggabungkan ketidaksetaraan dengan tata urutan di properti yang berbeda saat ini tidak diizinkan. Selain itu, filter yang merujuk ke beberapa properti terkadang memerlukan indeks sekunder untuk dikonfigurasi.

Tidak didukung: Datastore tidak secara langsung mendukung pencocokan substring, kecocokan yang tidak peka huruf besar/kecil, atau yang disebut penelusuran teks lengkap. Ada cara untuk menerapkan pencocokan yang tidak peka huruf besar/kecil dan bahkan penelusuran teks lengkap menggunakan properti terkomputasi.

Memfilter berdasarkan Nilai Properti

Ingat kembali class Account dari NDB Properties:

class Account(ndb.Model):
    username = ndb.StringProperty()
    userid = ndb.IntegerProperty()
    email = ndb.StringProperty()

Biasanya, Anda tidak ingin mengambil semua entity dari jenis tertentu; Anda hanya menginginkan entity yang memiliki nilai atau rentang nilai tertentu untuk beberapa properti.

Objek properti membebani beberapa operator untuk menampilkan ekspresi filter yang dapat digunakan untuk mengontrol kueri: misalnya, untuk menemukan semua entity Akun yang properti userid-nya memiliki nilai persis 42, Anda dapat menggunakan ekspresi tersebut

query = Account.query(Account.userid == 42)

(Jika Anda yakin hanya ada satu Account dengan userid tersebut, sebaiknya gunakan userid sebagai kunci. Account.get_by_id(...) lebih cepat daripada Account.query(...).get().)

NDB mendukung operasi berikut:

property == value
property < value
property <= value
property > value
property >= value
property != value
property.IN([value1, value2])

Untuk memfilter ketidaksetaraan, Anda dapat menggunakan sintaksis seperti berikut:

query = Account.query(Account.userid >= 40)

Ini akan menemukan semua Entitas akun yang properti userid-nya lebih besar dari atau sama dengan 40.

Dua dari operasi ini, != dan IN, diterapkan sebagai kombinasi dari yang lain, dan sedikit unik seperti yang dijelaskan dalam != dan IN.

Anda dapat menentukan beberapa filter:

query = Account.query(Account.userid >= 40, Account.userid < 50)

Fungsi ini menggabungkan argumen filter yang ditentukan, yang menampilkan semua entitas Akun yang nilai userid-nya lebih besar dari atau sama dengan 40 dan kurang dari 50.

Catatan: Seperti yang disebutkan sebelumnya, Datastore menolak kueri menggunakan pemfilteran ketidaksetaraan di lebih dari satu properti.

Daripada menetapkan seluruh filter kueri dalam satu ekspresi, Anda akan lebih mudah membuatnya dalam beberapa langkah: misalnya:

query1 = Account.query()  # Retrieve all Account entitites
query2 = query1.filter(Account.userid >= 40)  # Filter on userid >= 40
query3 = query2.filter(Account.userid < 50)  # Filter on userid < 50 too

query3 setara dengan variabel query dari contoh sebelumnya. Perhatikan bahwa objek kueri tidak dapat diubah, sehingga konstruksiquery2 tidak memengaruhiquery1 dan konstruksiquery3 tidak memengaruhi query1 atau query2.

Operasi != dan IN

Ingat kembali class Article dari NDB Properties:

class Article(ndb.Model):
    title = ndb.StringProperty()
    stars = ndb.IntegerProperty()
    tags = ndb.StringProperty(repeated=True)

Operasi != (tidak sama dengan) dan IN (keanggotaan) diterapkan dengan menggabungkan filter lain menggunakan operasi OR. Yang pertama,

property != value

diterapkan sebagai

(property < value) OR (property > value)

Misalnya,

query = Article.query(Article.tags != 'perl')

setara dengan

query = Article.query(ndb.OR(Article.tags < 'perl',
                             Article.tags > 'perl'))

Catatan: Mungkin secara mengejutkan, kueri ini tidak menelusuri entity Article yang tidak menyertakan 'perl' sebagai tag. Namun, metode ini akan menemukan semua entitas dengan sedikitnya satu tag yang tidak sama dengan 'perl'. Misalnya, entity berikut akan disertakan dalam hasil, meskipun memiliki 'perl' sebagai salah satu tag-nya:

Article(title='Perl + Python = Parrot',
        stars=5,
        tags=['python', 'perl'])

Namun, pertanyaan ini tidak akan disertakan:

Article(title='Introduction to Perl',
        stars=3,
        tags=['perl'])

Tidak ada cara untuk membuat kueri entity yang tidak menyertakan tag yang sama dengan 'perl'.

Demikian pula, operasi IN

property IN [value1, value2, ...]

yang menguji keanggotaan dalam daftar kemungkinan nilai, diimplementasikan sebagai

(property == value1) OR (property == value2) OR ...

Misalnya,

query = Article.query(Article.tags.IN(['python', 'ruby', 'php']))

setara dengan

query = Article.query(ndb.OR(Article.tags == 'python',
                             Article.tags == 'ruby',
                             Article.tags == 'php'))

Catatan: Kueri yang menggunakan OR menghapus duplikat hasilnya: aliran hasil tidak menyertakan entity lebih dari sekali, meskipun entity cocok dengan dua subkueri atau lebih.

Membuat Kueri untuk Properti Berulang

Class Article yang ditentukan di bagian sebelumnya juga berfungsi sebagai contoh pembuatan kueri untuk properti berulang. Khususnya, filter seperti

Article.tags == 'python'

akan menggunakan nilai tunggal, meskipun Article.tags adalah properti berulang. Anda tidak dapat membandingkan properti berulang dengan objek daftar (Datastore tidak akan memahaminya), dan filter seperti

Article.tags.IN(['python', 'ruby', 'php'])

melakukan sesuatu yang benar-benar berbeda dari menelusuri entity Article yang nilai tag-nya adalah daftar ['python', 'ruby', 'php'] : menelusuri entity yang nilai tags (dianggap sebagai daftar) berisiminimal satu dari nilai-nilai tersebut.

Kueri untuk nilai None pada properti berulang memiliki perilaku yang tidak ditentukan; jangan lakukan itu.

Menggabungkan Operasi AND dan OR

Anda dapat membuat tingkat operasi AND dan OR secara acak. Contoh:

query = Article.query(ndb.AND(Article.tags == 'python',
                              ndb.OR(Article.tags.IN(['ruby', 'jruby']),
                                     ndb.AND(Article.tags == 'php',
                                             Article.tags != 'perl'))))

Namun, karena penerapan OR, kueri formulir ini yang terlalu kompleks mungkin gagal dengan pengecualian. Anda akan lebih aman jika menormalisasi filter ini, sehingga ada (maksimum) satu operasi OR di bagian atas hierarki ekspresi, dan satu level operasi AND di bawahnya.

Untuk melakukan normalisasi ini, Anda harus mengingat aturan logika Boolean Anda, serta bagaimana filter != dan IN benar-benar diterapkan:

  1. Luaskan operator != dan IN ke bentuk dasar, dengan != menjadi pemeriksaan untuk properti yang < atau > dari nilai tersebut, dan IN menjadi centang untuk properti yang menjadi == ke nilai pertama atau nilai kedua atau...sampai ke nilai terakhir dalam daftar.
  2. AND denganOR di dalamnya setara dengan OR dari beberapa AND yang diterapkan ke varian asli AND operand, dengan satu OR operand yang menggantikan OR asli. Misalnya, AND(a, b, OR(c, d)) setara dengan OR(AND(a, b, c), AND(a, b, d))
  3. AND yang memiliki operand yang juga merupakan operasi AND dapat menggabungkan operand AND yang disusun bertingkat ke dalam AND yang mencakupnya. Misalnya, AND(a, b, AND(c, d)) setara dengan AND(a, b, c, d)
  4. OR yang memiliki operand yang juga merupakan operasi OR dapat menggabungkan operand OR yang disusun bertingkat ke dalam OR yang mencakupnya. Misalnya, OR(a, b, OR(c, d)) setara dengan OR(a, b, c, d)

Jika kami menerapkan transformasi ini secara bertahap pada filter contoh, menggunakan notasi yang lebih sederhana daripada Python, Anda akan mendapatkan:

  1. Menggunakan aturan #1 pada operator IN dan !=:
    AND(tags == 'python',
      OR(tags == 'ruby',
         tags == 'jruby',
         AND(tags == 'php',
             OR(tags < 'perl', tags > 'perl'))))
  2. Menggunakan aturan #2 pada OR terdalam yang disusun bertingkat dalam AND:
    AND(tags == 'python',
      OR(tags == 'ruby',
         tags == 'jruby',
         OR(AND(tags == 'php', tags < 'perl'),
            AND(tags == 'php', tags > 'perl'))))
  3. Menggunakan aturan #4 di OR yang disusun bertingkat dalam OR lain:
    AND(tags == 'python',
      OR(tags == 'ruby',
         tags == 'jruby',
         AND(tags == 'php', tags < 'perl'),
         AND(tags == 'php', tags > 'perl')))
  4. Menggunakan aturan #2 pada OR lain yang disusun bertingkat dalam AND:
    OR(AND(tags == 'python', tags == 'ruby'),
       AND(tags == 'python', tags == 'jruby'),
       AND(tags == 'python', AND(tags == 'php', tags < 'perl')),
       AND(tags == 'python', AND(tags == 'php', tags > 'perl')))
  5. Menggunakan aturan #3 untuk menciutkan AND lain yang disusun bertingkat:
    OR(AND(tags == 'python', tags == 'ruby'),
       AND(tags == 'python', tags == 'jruby'),
       AND(tags == 'python', tags == 'php', tags < 'perl'),
       AND(tags == 'python', tags == 'php', tags > 'perl'))

Perhatian: Untuk beberapa filter, normalisasi ini dapat menyebabkan ledakan kombinatorial. Pertimbangkan AND dari 3 klausa OR dengan masing-masing 2 klausa dasar. Saat dinormalisasi, ini menjadi OR dari 8 klausa AND dengan masing-masing 3 klausa dasar: yaitu, 6 istilah menjadi 24.

Menentukan Tata Urutan

Anda dapat menggunakan metode order() untuk menentukan urutan kueri yang menampilkan hasilnya. Metode ini mengambil daftar argumen, yang masing-masing merupakan objek properti (akan diurutkan dalam urutan menaik) atau negasinya (yang menunjukkan urutan menurun). Contoh:

query = Greeting.query().order(Greeting.content, -Greeting.date)

Tindakan ini akan mengambil semua entity Greeting, yang diurutkan berdasarkan nilai properti content menaik. Runutan entity yang berurutan dengan properti konten yang sama akan diurutkan berdasarkan nilai menurun dari properti date-nya. Anda dapat menggunakan beberapa panggilan order() untuk efek yang sama:

query = Greeting.query().order(Greeting.content).order(-Greeting.date)

Catatan: Saat menggabungkan filter dengan order(), Datastore menolak kombinasi tertentu. Secara khusus, saat Anda menggunakan filter ketidaksetaraan, tata urutan pertama (jika ada) harus menentukan properti yang sama dengan filter. Selain itu, terkadang Anda perlu mengonfigurasi indeks sekunder.

Kueri Ancestor

Dengan kueri ancestor, Anda dapat membuat kueri yang sangat konsisten ke datastore, tetapi entity dengan ancestor yang sama dibatasi hingga 1 penulisan per detik. Berikut adalah perbandingan sederhana antara kueri ancestor dan non-ancestor yang dapat disesuaikan dengan pelanggan dan pembelian terkaitnya di datastore.

Pada contoh non-ancestor berikut, ada satu entity di datastore untuk setiap Customer, dan satu entity di datastore untuk setiap Purchase, dengan KeyProperty yang mengarah ke pelanggan.

class Customer(ndb.Model):
    name = ndb.StringProperty()

class Purchase(ndb.Model):
    customer = ndb.KeyProperty(kind=Customer)
    price = ndb.IntegerProperty()

Untuk menemukan semua pembelian milik pelanggan, Anda dapat menggunakan kueri berikut:

purchases = Purchase.query(
    Purchase.customer == customer_entity.key).fetch()

Dalam hal ini, datastore menawarkan throughput tulis yang tinggi, tetapi hanya konsistensi tertunda. Jika pembelian baru ditambahkan, Anda mungkin mendapatkan data yang sudah tidak berlaku. Anda dapat menghilangkan perilaku ini menggunakan kueri ancestor.

Untuk pelanggan dan pembelian dengan kueri ancestor, Anda masih memiliki struktur yang sama dengan dua entity terpisah. Bagian pelanggan sama. Namun, saat melakukan pembelian, Anda tidak perlu lagi menentukan KeyProperty() untuk pembelian. Ini dikarenakan ketika menggunakan kueri ancestor, Anda memanggil kunci entity pelanggan saat membuat entity pembelian.

class Customer(ndb.Model):
    name = ndb.StringProperty()

class Purchase(ndb.Model):
    price = ndb.IntegerProperty()

Setiap pembelian memiliki kunci, dan pelanggan juga memiliki kuncinya sendiri. Namun, setiap kunci pembelian akan memiliki kunci customer_entity yang disematkan di dalamnya. Ingat, ini akan dibatasi ke satu penulisan per ancestor per detik. Kode berikut akan membuat entity dengan ancestor:

purchase = Purchase(parent=customer_entity.key)

Untuk membuat kueri pembelian pelanggan tertentu, gunakan kueri berikut.

purchases = Purchase.query(ancestor=customer_entity.key).fetch()

Atribut Kueri

Objek kueri memiliki atribut data hanya baca berikut:

Atribut Jenis Default Deskripsi
jenis str None Nama jenis (biasanya nama class)
ancestor Key None Ancestor ditentukan ke kueri
filter FilterNode None Ekspresi filter
pesanan Order None Urutan penyortiran

Mencetak objek kueri (atau memanggil str() atau repr() di dalamnya) menghasilkan representasi string yang diformat dengan baik:

print(Employee.query())
# -> Query(kind='Employee')
print(Employee.query(ancestor=ndb.Key(Manager, 1)))
# -> Query(kind='Employee', ancestor=Key('Manager', 1))

Pemfilteran untuk Nilai Properti Terstruktur

Kueri dapat langsung memfilter nilai kolom properti terstruktur. Misalnya, kueri untuk semua kontak dengan alamat yang kotanya adalah 'Amsterdam' akan terlihat seperti ini

query = Contact.query(Contact.addresses.city == 'Amsterdam')

Jika Anda menggabungkan beberapa filter, filter tersebut mungkin cocok dengan sub-entity Address yang berbeda dalam entity Kontak yang sama. Contoh:

query = Contact.query(Contact.addresses.city == 'Amsterdam',  # Beware!
                      Contact.addresses.street == 'Spear St')

dapat menemukan kontak dengan alamat yang kotanya adalah 'Amsterdam' dan alamat lain (yang berbeda) yang jalannya adalah 'Spear St'. Namun, setidaknya untuk filter kesetaraan, Anda dapat membuat kueri yang hanya menampilkan hasil dengan beberapa nilai dalam satu sub-entity:

query = Contact.query(Contact.addresses == Address(city='San Francisco',
                                                   street='Spear St'))

Jika Anda menggunakan teknik ini, properti sub-entity yang sama dengan None akan diabaikan dalam kueri. Jika properti memiliki nilai default, Anda harus secara eksplisit menetapkannya ke None untuk mengabaikannya dalam kueri. Jika tidak, kueri akan menyertakan filter yang mengharuskan nilai properti tersebut sama dengan default. Misalnya, jika model Address memiliki properti country dengan default='us', contoh di atas hanya akan menampilkan kontak dengan negara yang sama dengan 'us'; untuk mempertimbangkan kontak dengan nilai negara lain, Anda harus memfilter Address(city='San Francisco', street='Spear St', country=None).

Jika sub-entity memiliki nilai properti yang sama dengan None, nilai tersebut akan diabaikan. Oleh karena itu, tidak masuk akal untuk memfilter nilai properti sub-entity None.

Menggunakan Properti yang Dinamai berdasarkan String

Terkadang, Anda ingin memfilter atau mengurutkan kueri berdasarkan properti yang namanya ditentukan string. Misalnya, jika Anda membiarkan pengguna memasukkan kueri penelusuran seperti tags:python, akan lebih mudah untuk mengubahnya menjadi kueri seperti

Article.query(Article."tags" == "python") # does NOT work

Jika model Anda adalah Expando, maka filter dapat menggunakan GenericProperty, class Expando yang digunakan untuk properti dinamis:

property_to_query = 'location'
query = FlexEmployee.query(ndb.GenericProperty(property_to_query) == 'SF')

MenggunakanGenericProperty juga berfungsi jika model Anda bukan Expando, tetapi jika ingin memastikan bahwa Anda hanya menggunakan nama properti yang ditentukan, Anda juga dapat menggunakan atribut class _properties

query = Article.query(Article._properties[keyword] == value)

atau gunakan getattr() untuk mendapatkannya dari class:

query = Article.query(getattr(Article, keyword) == value)

Perbedaannya adalah getattr() menggunakan "nama Python" properti, sementara _properties diindeks oleh "nama datastore" properti. Ini hanya berbeda ketika properti dideklarasikan dengan sesuatu seperti

class ArticleWithDifferentDatastoreName(ndb.Model):
    title = ndb.StringProperty('t')

Di sini, nama Python adalah title, tetapi nama datastore adalah t.

Pendekatan ini juga berfungsi untuk mengurutkan hasil kueri:

expando_query = FlexEmployee.query().order(ndb.GenericProperty('location'))

property_query = Article.query().order(Article._properties[keyword])

Iterator Kueri

Saat kueri sedang berlangsung, statusnya disimpan di objek iterator. (Sebagian besar aplikasi tidak akan menggunakannya secara langsung; biasanya lebih mudah memanggil fetch(20) daripada memanipulasi objek iterator.) Ada dua cara dasar untuk mendapatkan objek tersebut:

  • menggunakan fungsi iter() bawaan Python pada objek Query
  • memanggil metode iter() objek Query

Yang pertama mendukung penggunaan loop for Python (yang secara implisit memanggil fungsi iter()) untuk melakukan loop atas suatu kueri.

for greeting in greetings:
    self.response.out.write(
        '<blockquote>%s</blockquote>' % cgi.escape(greeting.content))

Cara kedua, yang menggunakan metode iter() objek Query, memungkinkan Anda meneruskan opsi ke iterator untuk memengaruhi perilakunya. Misalnya, untuk menggunakan kueri khusus kunci dalam loop for, Anda dapat menulis ini:

for key in query.iter(keys_only=True):
    print(key)

Iterator kueri memiliki metode lain yang berguna:

Metode Deskripsi
__iter__() Bagian dari protokol iterator Python.
next() Menampilkan hasil berikutnya atau menaikkan pengecualian StopIteration jika tidak ada.

has_next() Menampilkan True jika panggilan next() berikutnya akan menampilkan hasil, False jika akan memunculkan StopIteration.

Memblokir hingga jawaban atas pertanyaan ini diketahui dan mem-buffer hasilnya (jika ada) hingga Anda mengambilnya dengan next().
probably_has_next() Seperti has_next(), tetapi menggunakan pintasan yang lebih cepat (dan terkadang tidak akurat).

Dapat menampilkan positif palsu (PP) (True kapan next() akan meningkatkan StopIteration ), tetapi tidak akan pernah negatif palsu (NP) (False kapan next() benar-benar akan memberikan hasil).
cursor_before() Menampilkan query cursor yang merepresentasikan titik tepat sebelum hasil terakhir yang ditampilkan.

Menampilkan pengecualian jika tidak ada kursor yang tersedia (khususnya, jika opsi kueri produce_cursors tidak diteruskan).
cursor_after() Menampilkan query cursor yang merepresentasikan titik tepat setelah hasil terakhir yang ditampilkan.

Menampilkan pengecualian jika tidak ada kursor yang tersedia (khususnya, jika opsi kueri produce_cursors tidak diteruskan).
index_list() Menampilkan daftar indeks yang digunakan oleh kueri yang dieksekusi, termasuk indeks properti utama, komposit, jenis, dan properti tunggal.

Query Cursor

Query cursor adalah struktur data buram kecil yang mewakili titik kelanjutan dalam kueri. Ini berguna untuk menampilkan halaman hasil satu per satu kepada pengguna; juga berguna untuk menangani tugas panjang yang mungkin perlu dihentikan dan dilanjutkan. Cara umum untuk menggunakannya adalah dengan metode fetch_page() kueri. Fungsi ini mirip dengan fetch(), tetapi menampilkan (results, cursor, more) tiga kali. Flag more yang ditampilkan menunjukkan bahwa mungkin ada lebih banyak hasil; UI dapat menggunakan ini, misalnya, untuk menyembunyikan tombol atau link "Halaman Berikutnya". Untuk meminta halaman berikutnya, teruskan kursor yang ditampilkan oleh satu panggilan fetch_page() ke panggilan berikutnya. BadArgumentError ditampilkan jika Anda meneruskan kursor yang tidak valid. Perhatikan bahwa validasi hanya memeriksa apakah nilainya dienkode base64. Anda harus melakukan validasi lebih lanjut yang diperlukan.

Dengan demikian, agar pengguna dapat melihat semua entity yang cocok dengan kueri, mengambilnya satu per satu halaman, kode Anda mungkin terlihat seperti ini:

from google.appengine.datastore.datastore_query import Cursor
...
class List(webapp2.RequestHandler):
    GREETINGS_PER_PAGE = 10

    def get(self):
        """Handles requests like /list?cursor=1234567."""
        cursor = Cursor(urlsafe=self.request.get('cursor'))
        greets, next_cursor, more = Greeting.query().fetch_page(
            self.GREETINGS_PER_PAGE, start_cursor=cursor)

        self.response.out.write('<html><body>')

        for greeting in greets:
            self.response.out.write(
                '<blockquote>%s</blockquote>' % cgi.escape(greeting.content))

        if more and next_cursor:
            self.response.out.write('<a href="/list?cursor=%s">More...</a>' %
                                    next_cursor.urlsafe())

        self.response.out.write('</body></html>')

Perhatikan penggunaan urlsafe() dan Cursor(urlsafe=s) untuk melakukan serialisasi dan deserialisasi kursor. Dengan cara ini, Anda dapat meneruskan kursor ke klien di web sebagai respons terhadap satu permintaan, dan menerimanya kembali dari klien dalam permintaan selanjutnya.

Catatan: Metode fetch_page() biasanya menampilkan kursor meskipun tidak ada hasil lagi, tetapi hal ini tidak dijamin: nilai kursor yang ditampilkan mungkin adalah None. Perhatikan juga bahwa karena flag more diimplementasikan menggunakan metode probably_has_next() iterator, dalam situasi yang jarang terjadi, flag ini mungkin akan menampilkan True meskipun halaman berikutnya kosong.

Beberapa kueri NDB tidak mendukung query cursor, tetapi Anda dapat memperbaikinya. Jika kueri menggunakan IN, OR, atau !=, maka hasil kueri tidak akan berfungsi dengan kursor kecuali diurutkan berdasarkan kunci. Jika tidak mengurutkan hasil berdasarkan kunci dan memanggil fetch_page(), aplikasi akan mendapatkan BadArgumentError. Jika User.query(User.name.IN(['Joe', 'Jane'])).order(User.name).fetch_page(N) mengalami error, ubah menjadi User.query(User.name.IN(['Joe', 'Jane'])).order(User.name, User.key).fetch_page(N)

Alih-alih "paging" pada hasil kueri, Anda dapat menggunakan metode iter() kueri untuk mendapatkan kursor pada titik yang tepat. Untuk melakukannya, teruskan produce_cursors=True ke iter(); saat iterator berada di tempat yang tepat, panggil cursor_after() untuk mendapatkan kursor yang muncul tepat setelahnya. (Atau, sama seperti, panggil cursor_before() untuk kursor tepat sebelumnya.) Perhatikan bahwa memanggil cursor_after() atau cursor_before() dapat membuat panggilan Datastore pemblokiran, yang menjalankan kembali bagian kueri untuk mengekstrak kursor yang menunjuk ke tengah batch.

Untuk menggunakan kursor ke halaman mundur melalui hasil kueri, buat kueri terbalik:

# Set up.
q = Bar.query()
q_forward = q.order(Bar.key)
q_reverse = q.order(-Bar.key)

# Fetch a page going forward.
bars, cursor, more = q_forward.fetch_page(10)

# Fetch the same page going backward.
r_bars, r_cursor, r_more = q_reverse.fetch_page(10, start_cursor=cursor)

Memanggil Fungsi untuk setiap Entity ("Mapping")

Misalkan, Anda perlu mendapatkan entity Account yang sesuai dengan entity Message yang ditampilkan kueri. Anda bisa menulis sesuatu seperti ini:

message_account_pairs = []
for message in message_query:
    key = ndb.Key('Account', message.userid)
    account = key.get()
    message_account_pairs.append((message, account))

Namun, hal ini cukup tidak efisien: sistem menunggu untuk mengambil entity, lalu menggunakan entity tersebut; menunggu entity berikutnya, menggunakan entity tersebut. Ada banyak waktu tunggu. Cara lainnya adalah dengan menulis fungsi callback yang dipetakan di atas hasil kueri:

def callback(message):
    key = ndb.Key('Account', message.userid)
    account = key.get()
    return message, account

message_account_pairs = message_query.map(callback)
# Now message_account_pairs is a list of (message, account) tuples.

Versi ini akan berjalan agak lebih cepat daripada loop for sederhana di atas karena beberapa konkurensi dapat terjadi. Namun, karena panggilan get() di callback() masih sinkron, peningkatannya tidak begitu besar. Ini adalah tempat yang baik untuk menggunakan pengambilan asinkron.

GQL

GQL adalah bahasa yang mirip SQL untuk mengambil entity atau kunci dari App Engine Datastore. Meskipun fitur GQL berbeda dengan bahasa kueri untuk database relasional tradisional, sintaksis GQL mirip dengan SQL. Sintaksis GQL dijelaskan dalam Referensi GQL.

Anda dapat menggunakan GQL untuk membuat kueri. Cara ini mirip dengan membuat kueri dengan Model.query(), tetapi menggunakan sintaksis GQL untuk menentukan filter dan urutan kueri. Untuk menggunakannya:

  • ndb.gql(querystring) menampilkan objek Query (jenis sama dengan yang ditampilkan oleh Model.query()). Semua metode biasa tersedia pada objek Query tersebut: fetch(), map_async(), filter(), dll.
  • Model.gql(querystring) adalah singkatan untuk ndb.gql("SELECT * FROM Model " + querystring). Biasanya, querystring terlihat seperti "WHERE prop1 > 0 AND prop2 = TRUE".
  • Untuk membuat kueri model yang berisi properti terstruktur, Anda dapat menggunakan foo.bar dalam sintaksis GQL untuk mereferensikan sub-properti.
  • GQL mendukung binding parameter seperti SQL. Aplikasi dapat menentukan kueri, kemudian mengikat nilai ke dalamnya:
    query = ndb.gql("SELECT * FROM Article WHERE stars > :1")
    query2 = query.bind(3)
    
    atau
    query = ndb.gql("SELECT * FROM Article WHERE stars > :1", 3)

    Memanggil fungsi bind() kueri akan menampilkan kueri baru; setelan ini tidak mengubah aslinya.

  • Jika class model Anda mengganti metode class _get_kind(), kueri GQL Anda harus menggunakan jenis yang ditampilkan fungsi tersebut, bukan nama class.
  • Jika properti dalam model Anda mengganti namanya (misalnya, foo = StringProperty('bar')) kueri GQL Anda harus menggunakan nama properti yang telah diganti (dalam contoh, bar).

Selalu gunakan fitur parameter-binding jika beberapa nilai dalam kueri Anda adalah variabel yang disediakan pengguna. Hal ini menghindari serangan berdasarkan peretasan sintaksis.

Error terjadi saat membuat kueri untuk model yang belum diimpor (atau, secara lebih umum, ditentukan).

Error terjadi saat menggunakan nama properti yang tidak ditentukan oleh class model kecuali jika model tersebut adalah Luaskano.

Menentukan batas atau offset untuk fetch() kueri akan menggantikan batas atau offset yang ditetapkan oleh klausa OFFSET dan LIMIT GQL. Jangan gabungkan OFFSET dan LIMIT GQL dengan fetch_page() Perhatikan bahwa nilai maksimum 1.000 hasil yang diberlakukan oleh App Engine pada kueri berlaku untuk offset dan batas.

Jika Anda terbiasa dengan SQL, berhati-hatilah terhadap asumsi palsu saat menggunakan GQL. GQL diterjemahkan ke API kueri native NDB. Hal ini berbeda dengan mapper Object-Relational standar (seperti SQLAlchemy atau dukungan database Django), yang panggilan API-nya diterjemahkan ke dalam SQL sebelum dikirim ke server database. GQL tidak mendukung modifikasi Datastore (penyisipan, penghapusan, atau pembaruan); GQL hanya mendukung kueri.