Query NDB

Un'applicazione può utilizzare le query per cercare in Datastore le entità che corrispondono a criteri di ricerca specifici chiamati filtri.

Panoramica

Un'applicazione può utilizzare le query per cercare in Datastore le entità che corrispondono a criteri di ricerca specifici chiamati filtri. Ad esempio, un'applicazione che tiene traccia di diversi guestbook potrebbe utilizzare una query per recuperare i messaggi da un guestbook, ordinati per data:

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)

Alcune query sono più complesse di altre; per queste, il datastore richiede indici predefiniti. Questi indici predefiniti sono specificati in un file di configurazione, index.yaml. Sul server di sviluppo, se esegui una query che richiede un indice che non hai specificato, il server di sviluppo lo aggiunge automaticamente al suo index.yaml. Tuttavia, nel tuo sito web, una query che richiede un indice non ancora specificato non va a buon fine. Pertanto, il ciclo di sviluppo tipico consiste nel provare una nuova query sul server di sviluppo e quindi aggiornare il sito web per utilizzare il file index.yaml modificato automaticamente. Puoi aggiornare index.yaml separatamente dal caricamento dell'applicazione eseguendo gcloud app deploy index.yaml. Se il datastore contiene molte entità, la creazione di un nuovo indice richiede molto tempo. In questo caso, è consigliabile aggiornare le definizioni degli indici prima di caricare il codice che utilizza il nuovo indice. Puoi utilizzare la Console di amministrazione per scoprire quando la creazione degli indici è stata completata.

Il datastore di App Engine supporta in modo nativo i filtri per le corrispondenze esatte (l'operatore ==) e i confronti (gli operatori <, <=, > e >=). Supporta la combinazione di più filtri utilizzando un'operazione booleana AND, con alcune limitazioni (vedi di seguito).

Oltre agli operatori nativi, l'API supporta l'operatore !=, che combina gruppi di filtri utilizzando l'operazione booleana OR, e l'operazione IN, che verifica l'uguaglianza con uno dei valori di un elenco di valori possibili (come l'operatore "in" di Python). Queste operazioni non vengono mappate 1:1 alle operazioni native di Datastore, pertanto sono un po' particolari e lente, relativamente. Vengono implementate utilizzando l'unione in memoria degli stream dei risultati. Tieni presente che p != v è implementato come "p < v OR p > v". (Questo è importante per le proprietà ripetute).

Limitazioni: Datastore applica alcune limitazioni alle query. La violazione di queste regole causerà la generazione di eccezioni. Ad esempio, la combinazione di troppi filtri, l'utilizzo di disuguaglianze per più proprietà o la combinazione di una disuguaglianza con un ordinamento su una proprietà diversa non sono attualmente consentiti. Inoltre, i filtri che fanno riferimento a più proprietà a volte richiedono la configurazione di indici secondari.

Non supportato: Datastore non supporta direttamente le corrispondenze di sottostringhe, le corrispondenze senza distinzione tra maiuscole e minuscole o la cosiddetta ricerca a testo intero. Esistono modi per implementare corrispondenze senza distinzione tra maiuscole e minuscole e persino la ricerca a testo intero utilizzando le proprietà calcolate.

Filtro per valori delle proprietà

Ricorda la classe Account di Proprietà NDB:

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

In genere, non vuoi recuperare tutte le entità di un determinato tipo, ma solo quelle con un valore o un intervallo di valori specifico per una determinata proprietà.

Gli oggetti proprietà eseguono l'overload di alcuni operatori per restituire espressioni di filtro che possono essere utilizzate per controllare una query: ad esempio, per trovare tutte le entità Account la cui proprietà userid ha il valore esatto 42, puoi utilizzare l'espressione

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

(Se hai la certezza che esisteva un solo Account con quel userid, potresti preferire utilizzare userid come chiave. Account.get_by_id(...) è più veloce di Account.query(...).get().)

NDB supporta le seguenti operazioni:

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

Per filtrare in base a una disuguaglianza, puoi utilizzare una sintassi simile alla seguente:

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

In questo modo vengono trovate tutte le entità Account la cui proprietà userid è maggiore o uguale a 40.

Due di queste operazioni, != e IN, sono implementate come combinazioni delle altre e sono un po' particolari, come descritto in != e IN.

Puoi specificare più filtri:

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

In questo modo, gli argomenti di filtro specificati vengono combinati, restituendo tutte le entità Account il cui valore userid è maggiore o uguale a 40 e minore di 50.

Nota: come accennato in precedenza, Datastore rifiuta le query che utilizzano filtri di disuguaglianza su più proprietà.

Anziché specificare un intero filtro di query in un'unica espressione, potresti trovare più comodo crearlo in più passaggi, ad esempio:

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 equivale alla variabile query dell'esempio precedente. Tieni presente che gli oggetti query sono immutabili, quindi la costruzione di query2 non influisce su query1 e la costruzione di query3 non influisce su query1 o query2.

Le operazioni != e IN

Ricorda la classe Article di Proprietà NDB:

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

Le operazioni != (non uguale) e IN (appartenenza) vengono implementate combinando altri filtri utilizzando l'operazione OR. Il primo,

property != value

è implementato come

(property < value) OR (property > value)

Ad esempio,

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

è equivalente a

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

Nota: Forse sorprendentemente, questa query non cerca Article entità che non includono 'perl' come tag! Trova invece tutte le entità con almeno un tag diverso da "perl". Ad esempio, la seguente entità verrebbe inclusa nei risultati, anche se ha "perl" come uno dei suoi tag:

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

Tuttavia, questo non verrebbe incluso:

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

Non è possibile eseguire query per entità che non includono un tag uguale a "perl".

Analogamente, l'operazione IN

property IN [value1, value2, ...]

che verifica l'appartenenza a un elenco di valori possibili, viene implementato come segue:

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

Ad esempio,

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

è equivalente a

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

Nota: le query che utilizzano OR deduplicano i risultati: lo stream dei risultati non include l'entità più di una volta, anche se un'entità corrisponde a due o più sottoquery.

Esecuzione di query per proprietà ripetute

La classe Article definita nella sezione precedente funge anche da esempio di query per le proprietà ripetute. In particolare, un filtro come

Article.tags == 'python'

utilizza un singolo valore, anche se Article.tags è una proprietà ripetuta. Non puoi confrontare le proprietà ripetute con gli oggetti elenco (Datastore non lo capirà) e un filtro come

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

esegue un'operazione completamente diversa dalla ricerca di entità Article il cui valore dei tag è l'elenco ['python', 'ruby', 'php']: cerca le entità il cui valore tags (considerato come un elenco) contiene almeno uno di questi valori.

La query per un valore di None su una proprietà ripetuta ha un comportamento non definito; non farlo.

Combinazione delle operazioni AND e OR

Puoi nidificare le operazioni AND e OR in modo arbitrario. Ad esempio:

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

Tuttavia, a causa dell'implementazione di OR, una query di questo modulo troppo complessa potrebbe non riuscire con un'eccezione. È più sicuro normalizzare questi filtri in modo che ci sia (al massimo) una singola operazione OR nella parte superiore dell'albero delle espressioni e un singolo livello di operazioni AND sotto.

Per eseguire questa normalizzazione, devi ricordare sia le regole della logica booleana sia il modo in cui vengono effettivamente implementati i filtri != e IN:

  1. Espandi gli operatori != e IN nella loro forma primitiva, dove != diventa un controllo per la proprietà < o > del valore e IN diventa un controllo per la proprietà == al primo valore o al secondo valore o… fino all'ultimo valore nell'elenco.
  2. Un AND con un OR al suo interno equivale a un OR di più AND applicati agli operandi AND originali, con un singolo operando OR sostituito all'OR originale. Ad esempio, AND(a, b, OR(c, d)) equivale a OR(AND(a, b, c), AND(a, b, d))
  3. Un'operazione AND che ha un operando che è a sua volta un'operazione AND può incorporare gli operandi dell'operazione AND nidificata nell'operazione AND di inclusione. Ad esempio, AND(a, b, AND(c, d)) equivale a AND(a, b, c, d)
  4. Un OR che ha un operando che è a sua volta un'operazione OR può incorporare gli operandi del OR nidificato nel OR di inclusione. Ad esempio, OR(a, b, OR(c, d)) equivale a OR(a, b, c, d)

Se applichiamo queste trasformazioni in più fasi al filtro di esempio, utilizzando una notazione più semplice di Python, otteniamo:

  1. Utilizzando la regola n. 1 sugli operatori IN e !=:
    AND(tags == 'python',
      OR(tags == 'ruby',
         tags == 'jruby',
         AND(tags == 'php',
             OR(tags < 'perl', tags > 'perl'))))
  2. Utilizzando la regola n. 2 sull'elemento OR più interno nidificato all'interno di un elemento AND:
    AND(tags == 'python',
      OR(tags == 'ruby',
         tags == 'jruby',
         OR(AND(tags == 'php', tags < 'perl'),
            AND(tags == 'php', tags > 'perl'))))
  3. Utilizzando la regola n. 4 su OR nidificato all'interno di un altro OR:
    AND(tags == 'python',
      OR(tags == 'ruby',
         tags == 'jruby',
         AND(tags == 'php', tags < 'perl'),
         AND(tags == 'php', tags > 'perl')))
  4. Utilizzando la regola n. 2 sul restante OR nidificato all'interno di un 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. Utilizzando la regola n. 3 per comprimere i restanti AND nidificati:
    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'))

Attenzione: per alcuni filtri, questa normalizzazione può causare un'esplosione combinatoria. Considera la AND di 3 clausole OR con 2 clausole di base ciascuna. Se normalizzata, questa diventa una OR di 8 clausole AND con 3 clausole di base ciascuna: ovvero 6 termini diventano 24.

Specifica degli ordinamenti

Puoi utilizzare il metodo order() per specificare l'ordine in cui una query restituisce i risultati. Questo metodo accetta un elenco di argomenti, ognuno dei quali è un oggetto proprietà (da ordinare in ordine crescente) o la sua negazione (che indica l'ordine decrescente). Ad esempio:

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

In questo modo vengono recuperate tutte le entità Greeting, ordinate in base al valore crescente della proprietà content. Le sequenze di entità consecutive con la stessa proprietà dei contenuti verranno ordinate in base al valore decrescente della proprietà date. Puoi utilizzare più chiamate order() per ottenere lo stesso effetto:

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

Nota: quando combini i filtri con order(), Datastore rifiuta determinate combinazioni. In particolare, quando utilizzi un filtro di disuguaglianza, il primo ordinamento (se presente) deve specificare la stessa proprietà del filtro. Inoltre, a volte è necessario configurare un indice secondario.

Query da predecessore

Le query da predecessore consentono di eseguire query con elevata coerenza sul datastore, ma le entità con lo stesso predecessore sono limitate a 1 scrittura al secondo. Ecco un semplice confronto dei compromessi e della struttura tra una query da predecessore e una query non da predecessore che utilizza i clienti e i relativi acquisti associati nel datastore.

Nell'esempio seguente, che non riguarda i predecessori, esiste un'entità nel datastore per ogni Customer e un'entità nel datastore per ogni Purchase, con un KeyProperty che punta al cliente.

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

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

Per trovare tutti gli acquisti che appartengono al cliente, puoi utilizzare la seguente query:

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

In questo caso, il datastore offre un throughput di scrittura elevato, ma solo una coerenza finale. Se è stato aggiunto un nuovo acquisto, potresti ricevere dati obsoleti. Puoi eliminare questo comportamento utilizzando le query da predecessore.

Per i clienti e gli acquisti con query da predecessore, hai ancora la stessa struttura con due entità separate. La parte relativa al cliente è la stessa. Tuttavia, quando crei gli acquisti, non è più necessario specificare KeyProperty() per gli acquisti. Questo perché quando utilizzi le query da predecessore, chiami la chiave dell'entità cliente quando crei un'entità acquisto.

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

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

Ogni acquisto ha una chiave e anche il cliente ha la propria chiave. Tuttavia, ogni chiave di acquisto avrà la chiave di customer_entity incorporata. Ricorda che questo sarà limitato a una scrittura per predecessore al secondo. Di seguito viene creata un'entità con un predecessore:

purchase = Purchase(parent=customer_entity.key)

Per eseguire una query sugli acquisti di un determinato cliente, utilizza la seguente query.

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

Attributi della query

Gli oggetti Query hanno i seguenti attributi di dati di sola lettura:

Attributo Tipo Predefinito Descrizione
kind str None Nome del tipo (di solito il nome della classe)
predecessore Key None Antenato specificato per la query
filters FilterNode None Espressione di filtro
ordini Order None Ordini di smistamento

La stampa di un oggetto query (o la chiamata di str() o repr() su di esso) produce una rappresentazione di stringa ben formattata:

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

Filtro per valori delle proprietà strutturate

Una query può filtrare direttamente i valori dei campi delle proprietà strutturate. Ad esempio, una query per tutti i contatti con un indirizzo la cui città è 'Amsterdam' sarebbe simile a

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

Se combini più filtri di questo tipo, i filtri potrebbero corrispondere a entità secondarie Address diverse all'interno della stessa entità Contatto. Ad esempio:

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

potrebbe trovare contatti con un indirizzo la cui città è 'Amsterdam' e un altro indirizzo (diverso) la cui via è 'Spear St'. Tuttavia, almeno per i filtri di uguaglianza, puoi creare una query che restituisca solo risultati con più valori in una singola entità secondaria:

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

Se utilizzi questa tecnica, le proprietà della sottoentità uguali a None vengono ignorate nella query. Se una proprietà ha un valore predefinito, devi impostarlo esplicitamente su None per ignorarlo nella query, altrimenti la query include un filtro che richiede che il valore della proprietà sia uguale a quello predefinito. Ad esempio, se il modello Address aveva una proprietà country con default='us', l'esempio precedente restituirebbe solo i contatti con paese uguale a 'us'; per considerare i contatti con altri valori di paese, dovresti filtrare per Address(city='San Francisco', street='Spear St', country=None).

Se un'entità secondaria ha valori di proprietà uguali a None, questi vengono ignorati. Pertanto, non ha senso filtrare per un valore della proprietà dell'entità secondaria di None.

Utilizzo di proprietà denominate tramite stringa

A volte vuoi filtrare o ordinare una query in base a una proprietà il cui nome è specificato da una stringa. Ad esempio, se consenti all'utente di inserire query di ricerca come tags:python, sarebbe utile trasformarle in qualche modo in una query come

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

Se il modello è un Expando, il filtro può utilizzare GenericProperty, la classe utilizzata da Expando per le proprietà dinamiche:

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

L'utilizzo di GenericProperty funziona anche se il modello non è un Expando, ma se vuoi assicurarti di utilizzare solo nomi di proprietà definiti, puoi anche utilizzare l'attributo della classe _properties

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

oppure utilizza getattr() per recuperarlo dalla classe:

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

La differenza è che getattr() utilizza il "nome Python" della proprietà, mentre _properties è indicizzato in base al "nome datastore" della proprietà. Questi differiscono solo quando la proprietà è stata dichiarata con un valore simile a

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

In questo caso, il nome Python è title, ma il nome del datastore è t.

Questi approcci funzionano anche per l'ordinamento dei risultati delle query:

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

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

Iteratori di query

Mentre una query è in corso, il suo stato viene mantenuto in un oggetto iteratore. (La maggior parte delle applicazioni non li utilizzerà direttamente; in genere è più semplice chiamare fetch(20) che manipolare l'oggetto iteratore). Esistono due modi di base per ottenere un oggetto di questo tipo:

  • utilizzando la funzione iter() integrata di Python su un oggetto Query
  • chiamando il metodo iter() dell'oggetto Query

Il primo supporta l'utilizzo di un ciclo for Python (che chiama implicitamente la funzione iter()) per eseguire il ciclo su una query.

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

Il secondo modo, che utilizza il metodo iter() dell'oggetto Query, consente di passare opzioni all'iteratore per influenzarne il comportamento. Ad esempio, per utilizzare una query solo chiavi in un ciclo for, puoi scrivere quanto segue:

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

Gli iteratori di query hanno altri metodi utili:

Metodo Descrizione
__iter__() Parte del protocollo iteratore di Python.
next() Restituisce il risultato successivo o genera l'eccezione StopIteration se non esiste.

has_next() Restituisce True se una chiamata next() successiva restituisce un risultato, False se genera StopIteration.

Blocca fino a quando non è nota la risposta a questa domanda e memorizza nel buffer il risultato (se presente) fino a quando non lo recuperi con next().
probably_has_next() Come has_next(), ma utilizza una scorciatoia più veloce (e talvolta imprecisa).

Potrebbe restituire un falso positivo (True quando next() genererebbe effettivamente StopIteration), ma mai un falso negativo (False quando next() restituirebbe effettivamente un risultato).
cursor_before() Restituisce un cursore di query che rappresenta un punto appena prima dell'ultimo risultato restituito.

Genera un'eccezione se non è disponibile alcun cursore (in particolare, se ilproduce_cursors L'opzione di query non è stata passata).
cursor_after() Restituisce un cursore di query che rappresenta un punto subito dopo l'ultimo risultato restituito.

Genera un'eccezione se non è disponibile alcun cursore (in particolare, se ilproduce_cursors L'opzione di query non è stata passata).
index_list() Restituisce un elenco di indici utilizzati da una query eseguita, inclusi indici principali, composti, di tipo e di singola proprietà.

Cursori di query

Un cursore di query è una piccola struttura di dati opaca che rappresenta un punto di ripresa in una query. Questo è utile per mostrare a un utente una pagina di risultati alla volta ed è utile anche per gestire job lunghi che potrebbero dover essere interrotti e ripresi. Un modo tipico per utilizzarli è con il metodo fetch_page() di una query. Funziona in modo simile a fetch(), ma restituisce una tripla (results, cursor, more). Il flag more restituito indica che probabilmente sono presenti altri risultati; una UI può utilizzarlo, ad esempio, per eliminare un pulsante o un link "Pagina successiva". Per richiedere le pagine successive, passa il cursore restituito da una chiamata fetch_page() alla successiva. Viene generato un errore BadArgumentError se passi un cursore non valido. Tieni presente che la convalida verifica solo se il valore è codificato in Base64. Dovrai eseguire qualsiasi ulteriore convalida necessaria.

Pertanto, per consentire all'utente di visualizzare tutte le entità corrispondenti a una query, recuperandole una pagina alla volta, il codice potrebbe essere simile al seguente:

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

Nota l'utilizzo di urlsafe() e Cursor(urlsafe=s) per serializzare e deserializzare il cursore. In questo modo puoi passare un cursore a un client sul web nella risposta a una richiesta e riceverlo dal client in una richiesta successiva.

Nota: il metodo fetch_page() in genere restituisce un cursore anche se non ci sono altri risultati, ma questo non è garantito: il valore del cursore restituito potrebbe essere None. Tieni presente inoltre che, poiché il flag more viene implementato utilizzando il metodo probably_has_next() dell'iteratore, in rare circostanze potrebbe restituire True anche se la pagina successiva è vuota.

Alcune query NDB non supportano i cursori di query, ma puoi correggerle. Se una query utilizza IN, OR o !=, i risultati della query non funzioneranno con i cursori, a meno che non siano ordinati per chiave. Se un'applicazione non ordina i risultati per chiave e chiama fetch_page(), riceve un BadArgumentError. Se User.query(User.name.IN(['Joe', 'Jane'])).order(User.name).fetch_page(N) riceve un errore, cambialo in User.query(User.name.IN(['Joe', 'Jane'])).order(User.name, User.key).fetch_page(N)

Invece di "sfogliare" i risultati della query, puoi utilizzare il metodo iter() di una query per ottenere un cursore in un punto preciso. Per farlo, passa produce_cursors=True a iter(); quando l'iteratore si trova nella posizione corretta, chiama il relativo cursor_after() per ottenere un cursore subito dopo. (In alternativa, chiama cursor_before() per un cursore immediatamente precedente). Tieni presente che la chiamata a cursor_after() o cursor_before() potrebbe effettuare una chiamata Datastore di blocco, eseguendo nuovamente parte della query per estrarre un cursore che punta al centro di un batch.

Per utilizzare un cursore per scorrere indietro i risultati della query, crea una query inversa:

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

Chiamata di una funzione per ogni entità ("mappatura")

Supponiamo che tu debba ottenere le entità Account corrispondenti alle entità Message restituite da una query. Potresti scrivere qualcosa del genere:

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

Tuttavia, questo è un metodo piuttosto inefficiente: attende il recupero di un'entità, quindi la utilizza; attende l'entità successiva, la utilizza. C'è molto tempo di attesa. Un altro modo è scrivere una funzione di callback mappata sui risultati della query:

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.

Questa versione verrà eseguita un po' più velocemente rispetto al semplice ciclo for riportato sopra, perché è possibile un certo livello di contemporaneità. Tuttavia, poiché la chiamata get() in callback() è ancora sincrona, il guadagno non è enorme. Questo è un buon punto per utilizzare i get asincroni.

GQL

GQL è un linguaggio simile a SQL per il recupero di entità o chiavi da App Engine Datastore. Sebbene le funzionalità di GQL siano diverse da quelle di un linguaggio di query per un database relazionale tradizionale, la sintassi di GQL è simile a quella di SQL. La sintassi GQL è descritta nel Riferimento GQL.

Puoi utilizzare GQL per creare query. Questo è simile alla creazione di una query con Model.query(), ma utilizza la sintassi GQL per definire il filtro e l'ordine della query. Per utilizzarlo:

  • ndb.gql(querystring) restituisce un oggetto Query (lo stesso tipo restituito da Model.query()). Tutti i metodi usuali sono disponibili su questi oggetti Query: fetch(), map_async(), filter(), e così via.
  • Model.gql(querystring) è un'abbreviazione di ndb.gql("SELECT * FROM Model " + querystring). In genere, querystring è simile a "WHERE prop1 > 0 AND prop2 = TRUE".
  • Per eseguire query su modelli contenenti proprietà strutturate, puoi utilizzare foo.bar nella sintassi GQL per fare riferimento alle proprietà secondarie.
  • GQL supporta associazioni di parametri simili a SQL. Un'applicazione può definire una query e quindi associarvi dei valori:
    query = ndb.gql("SELECT * FROM Article WHERE stars > :1")
    query2 = query.bind(3)
    
    o
    query = ndb.gql("SELECT * FROM Article WHERE stars > :1", 3)

    La chiamata alla funzione bind() di una query restituisce una nuova query, ma non modifica quella originale.

  • Se la classe del modello esegue l'override del metodo della classe _get_kind(), la query GQL deve utilizzare il tipo restituito da questa funzione, non il nome della classe.
  • Se una proprietà nel modello esegue l'override del nome (ad es. foo = StringProperty('bar')), la query GQL deve utilizzare il nome della proprietà sottoposta a override (nell'esempio, bar).

Utilizza sempre la funzionalità di associazione dei parametri se alcuni valori della query sono variabili fornite dall'utente. In questo modo si evitano attacchi basati su hack sintattici.

Se esegui una query per un modello che non è stato importato (o, più in generale, definito), si verifica un errore.

È un errore utilizzare un nome di proprietà non definito dalla classe del modello, a meno che il modello non sia un Expando.

Se specifichi un limite o un offset per fetch() della query, esegui l'override del limite o dell'offset impostato dalle clausole OFFSET e LIMIT di GQL. Non combinare OFFSET e LIMIT di GQL con fetch_page(). Tieni presente che il limite massimo di 1000 risultati imposto da App Engine alle query si applica sia all'offset che al limite.

Se hai dimestichezza con SQL, fai attenzione alle false ipotesi quando utilizzi GQL. GQL viene tradotto nell'API di query nativa di NDB. Questo è diverso da un tipico mapper relazionale a oggetti (come SQLAlchemy o il supporto del database di Django), in cui le chiamate API vengono tradotte in SQL prima di essere trasmesse al server del database. GQL non supporta le modifiche di Datastore (inserimenti, eliminazioni o aggiornamenti), ma solo le query.