Un'applicazione può utilizzare query per cercare nel Datastore entità che corrispondono a criteri di ricerca specifici denominati filtri.
Panoramica
Un'applicazione può utilizzare query per cercare nel Datastore entità che corrispondono a criteri di ricerca specifici denominati 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:
...
...
Alcune query sono più complesse di altre; per queste query sono necessari 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 non 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 ha esito negativo.
Di conseguenza, il ciclo di sviluppo tipico consiste nel provare una nuova query sul server di sviluppo e poi aggiornare il sito web in modo che utilizzi il valore index.yaml
modificato automaticamente.
Puoi aggiornare index.yaml
separatamente dal caricamento
dell'applicazione eseguendo
gcloud app deploy index.yaml
.
Se il tuo datastore ha molte entità, la creazione di un nuovo indice richiede molto tempo; in questo caso, è consigliabile aggiornare le definizioni dell'indice prima di caricare il codice che utilizza il nuovo indice.
Puoi utilizzare la console di amministrazione per sapere quando è stata completata la creazione degli indici.
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 mediante un'operazione booleana AND
, con alcune limitazioni (vedi di seguito).
Oltre agli operatori nativi,
l'API supporta l'operatore !=
,
combinando gruppi di filtri utilizzando l'operazione booleana OR
e l'operazione IN
,
che verificano l'uguaglianza rispetto a uno di un elenco di valori possibili
(come l'operatore "in" di Python).
Queste operazioni non mappano 1:1 alle operazioni native di Datastore; sono quindi un po' stravaganti e lente, relativamente.
Vengono implementati utilizzando l'unione in memoria dei
flussi di 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 tali disposizioni comporterà l'istituzione di un'eccezione. Ad esempio, al momento non sono consentiti combinare troppi filtri, utilizzare disuguaglianze per più proprietà o combinare una disuguaglianza con un ordinamento su una proprietà diversa. Anche 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 proprietà
Ricorda la classe Account di NDB Properties:
In genere non vuoi recuperare tutte le entità di un determinato tipo; vuoi solo quelle con un valore o un intervallo di valori specifico per alcune proprietà.
Gli oggetti proprietà sovraccaricano 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
Se hai la certezza che ci sia solo un 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 la disuguaglianza, puoi utilizzare una sintassi simile alla seguente:
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' stravaganti, come descritto in != e IN.
Puoi specificare più filtri:
Combina gli argomenti di filtro specificati e restituisce tutte le entità Account con un valore id utente maggiore o uguale a 40 e inferiore a 50.
Nota: come accennato in precedenza, Datastore rifiuta le query che utilizzano i filtri di disuguaglianza in più di una proprietà.
Anziché specificare un intero filtro di query in una singola espressione, potrebbe essere più pratico crearlo in passaggi, ad esempio:
query3
è equivalente alla variabile query
dell'esempio precedente. Tieni presente che gli oggetti query sono immutabili, pertanto la
creazione di query2
non influisce su query1
e la creazione di query3
non influisce su
query1
o query2
.
Le operazioni != e IN
Ricorda la classe Article di NDB Properties:
Le operazioni !=
(diverso da) e IN
(abbonamento)
vengono implementate combinando altri filtri che usano
l'operazione OR
. Il primo,
property != value
viene implementato come
(property < value) OR (property > value)
Ad esempio,
equivale a
Nota:
probabilmente, questa query non cerca
entità Article
che non includono "perl" come tag.
Piuttosto, trova tutte le entità con almeno un tag diverso da "perl".
Ad esempio, la seguente entità verrebbe inclusa nei risultati,
anche se ha "perl" come tag:
Tuttavia, questo non verrebbe incluso:
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 implementata come
(property == value1) OR (property == value2) OR ...
Ad esempio,
equivale a
Nota: le query che utilizzano OR
ne deduplicano i risultati: il flusso di risultati non include l'entità più di una volta, anche se un'entità corrisponde a due o più sottoquery.
Esecuzione di query su proprietà ripetute
Anche la classe Article
definita nella sezione precedente
rappresenta un esempio di query per proprietà ripetute. In particolare, un filtro
come il filtro
utilizza un singolo valore, anche se Article.tags
è una proprietà ripetuta. Non puoi confrontare le proprietà ripetute per
elencare gli oggetti (non sarà in grado di comprenderlo)
fa qualcosa di completamente diverso dalla ricerca di
entità Article
il cui valore tag è l'elenco
['python', 'ruby', 'php']
:
cerca entità il cui valore tags
(considerato come elenco)
contiene almeno uno di questi valori.
Eseguire query su un valore None
su una proprietà ripetuta ha un comportamento indefinito; non farlo.
Combinazione delle operazioni AND e OR
Puoi nidificare le operazioni AND
e OR
in modo arbitrario.
Ad esempio:
Tuttavia, a causa dell'implementazione di OR
, una query di questo modulo troppo complessa potrebbe non riuscire, con un'eccezione. Hai una maggiore protezione se normalizzi questi filtri in modo che sia presente (al massimo) una singola operazione OR
nella parte superiore dell'albero delle espressioni e un singolo livello di operazioni AND
sotto la stessa.
Per eseguire questa normalizzazione, devi ricordare sia le regole della logica booleana sia il modo in cui i filtri !=
e IN
vengono effettivamente implementati:
- Espandi gli operatori
!=
eIN
nella loro forma originaria, dove!=
diventa un controllo per verificare che la proprietà sia < o > rispetto al valore eIN
diventa un controllo per verificare se la proprietà è == fino al primo valore o al secondo valore o...fino all'ultimo valore dell'elenco. - Un elemento
AND
conOR
all'interno equivale aOR
di più operandiAND
applicati agli operandiAND
originali. Un singolo operandoOR
è stato sostituito con l'operandoOR
originale. Ad esempio,AND(a, b, OR(c, d))
equivale aOR(AND(a, b, c), AND(a, b, d))
- Un elemento
AND
con un operando che è a sua volta un'operazioneAND
può incorporare gli operandi dell'elementoAND
nidificato nell'oggettoAND
che contiene. Ad esempioAND(a, b, AND(c, d))
equivale aAND(a, b, c, d)
- Un elemento
OR
con un operando che è a sua volta un'operazioneOR
può incorporare gli operandi dell'elementoOR
nidificato nel relativoOR
. Ad esempioOR(a, b, OR(c, d))
equivale aOR(a, b, c, d)
Se applichiamo queste trasformazioni in fasi al filtro di esempio, utilizzando una notazione più semplice rispetto a Python, si ottiene:
- Utilizzo della regola n. 1 sugli operatori
IN
e!=
:AND(tags == 'python', OR(tags == 'ruby', tags == 'jruby', AND(tags == 'php', OR(tags < 'perl', tags > 'perl'))))
- Utilizzo della regola n. 2 sul componente
OR
più interno nidificato in unAND
:AND(tags == 'python', OR(tags == 'ruby', tags == 'jruby', OR(AND(tags == 'php', tags < 'perl'), AND(tags == 'php', tags > 'perl'))))
- Utilizzo della regola n. 4 su
OR
nidificata all'interno di un altroOR
:AND(tags == 'python', OR(tags == 'ruby', tags == 'jruby', AND(tags == 'php', tags < 'perl'), AND(tags == 'php', tags > 'perl')))
- Utilizzo della regola 2 sui restanti
OR
nidificati all'interno di unAND
: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')))
- Utilizzo della regola 3 per comprimere i
AND
nidificati rimanenti: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 normalizzato, diventa un OR
di 8 clausole AND
con 3 clausole di base ciascuna: ovvero 6 termini diventano 24.
Specificare gli ordini
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 di proprietà (da ordinare in ordine crescente) o la sua negazione (che indica l'ordine decrescente). Ad esempio:
Questo consente di recuperare tutte le entità Greeting
, ordinate in base al valore crescente della relativa proprietà content
.
Le esecuzioni di entità consecutive con la stessa proprietà di contenuto verranno ordinate in base al valore decrescente della relativa proprietà date
.
Puoi utilizzare più chiamate order()
per ottenere lo stesso effetto:
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 predecessore
Le query predecessore consentono di eseguire query a elevata coerenza sul datastore, tuttavia 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 predecessore e non predecessore utilizzando i clienti e i relativi acquisti associati nel datastore.
Nel seguente esempio non predecessore, esiste un'entità nel datastore per ogni Customer
e un'entità nel datastore per ogni Purchase
, con un KeyProperty
che
rimanda al cliente.
Per trovare tutti gli acquisti appartenenti al cliente, puoi utilizzare la seguente query:
In questo caso, il datastore offre una velocità effettiva di scrittura elevata, ma solo coerenza finale. Se è stato aggiunto un nuovo acquisto, potresti ricevere dati inattivi. Puoi eliminare questo comportamento utilizzando le query dei predecessori.
Per i clienti e gli acquisti con query predecessore, hai comunque la stessa struttura con due entità separate. La parte dedicata al cliente è la stessa. Tuttavia, quando crei acquisti, non devi più specificare il KeyProperty()
per gli acquisti. Questo perché, quando utilizzi le query predecessore, chiami la chiave dell'entità cliente quando crei un'entità di acquisto.
Ogni acquisto ha una chiave e anche il cliente ha la sua chiave. Tuttavia, ogni chiave di acquisto avrà la chiave customer_entity incorporata al suo interno. Ricorda che sarà limitata a una scrittura per predecessore al secondo. Di seguito viene creata un'entità con un predecessore:
Per eseguire una query relativa agli acquisti di un determinato cliente, utilizza la seguente query.
Attributi query
Gli oggetti query hanno i seguenti attributi dei dati di sola lettura:
Attributo | Tipo | Predefinita | Descrizione |
---|---|---|---|
kind | str | None | Nome del tipo (di solito il nome della classe) |
ancestor | Key | None | Il predecessore specificato per la query |
filters | FilterNode | None | Espressione di filtro |
ordini | Order | None | Ordina |
La stampa di un oggetto di query (o la chiamata a str()
o repr()
) produce una rappresentazione di stringa formattata correttamente:
Filtrare per i valori delle proprietà strutturate
Una query può filtrare direttamente in base ai valori dei campi delle proprietà strutturate.
Ad esempio, una query per tutti i contatti con un indirizzo la cui città è
'Amsterdam'
potrebbe essere simile a
Se combini più filtri di questo tipo, i filtri potrebbero corrispondere a entità secondarie Address
diverse all'interno della stessa entità Contact.
Ad esempio:
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:
Se utilizzi questa tecnica, le proprietà dell'entità secondaria uguale 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 al valore predefinito.
Ad esempio, se il modello Address
avesse una proprietà
country
con default='us'
, l'esempio
riportato sopra restituirà solo i contatti con paese uguale a
'us'
; per considerare i contatti con valori di altri paesi,
devi filtrare in base a
Address(city='San Francisco', street='Spear St',
country=None)
.
Se un'entità secondaria ha valori di proprietà uguali a None
,
questi valori vengono ignorati. Pertanto, non ha senso filtrare in base al valore di una proprietà di sottoentità di None
.
Utilizzo di proprietà con nome stringa
A volte potresti voler filtrare o ordinare una query in base a una proprietà il cui nome è specificato dalla stringa. Ad esempio, se consenti all'utente di inserire query di ricerca
come tags:python
, sarebbe conveniente in qualche modo trasformarle
in una query come
Article.query(Article."tags" == "python") # does NOT work
Se il modello è un
Expando
, il filtro può utilizzare GenericProperty
, la classe che Expando
utilizza per le proprietà dinamiche:
L'utilizzo di GenericProperty
funziona anche se il modello non è Expando
, ma se vuoi assicurarti di utilizzare solo nomi di proprietà definiti, puoi anche utilizzare l'attributo di classe _properties
oppure usa getattr()
per riceverlo dal corso:
La differenza è che getattr()
utilizza il "nome Python"
della proprietà, mentre _properties
è indicizzato dal
"nome datastore" della proprietà. Si differenziano solo quando la proprietà
è stata dichiarata con un valore simile
Qui il nome Python è title
ma il nome del datastore è
t
.
Questi approcci funzionano anche per ordinare i risultati delle query:
Iteratori query
Mentre una query è in corso, il suo stato viene mantenuto in un oggetto iteratore. La maggior parte delle applicazioni non le utilizzerà direttamente; di solito è più semplice chiamare fetch(20)
che manipolare l'oggetto Iterator.
Esistono due modi di base per ottenere un oggetto di questo tipo:
- usando la funzione
iter()
integrata di Python su un oggettoQuery
- chiamando il metodo
iter()
dell'oggettoQuery
Il primo supporta l'uso di un loop for
Python
(che chiama implicitamente la funzione iter()
)
per il loop su una query.
Il secondo modo, utilizzando il metodo iter()
dell'oggetto Query
, ti consente di passare opzioni all'iteratore per modificarne il comportamento. Ad esempio, per utilizzare una
query basata solo su chiavi in un loop for
, puoi scrivere questo:
Gli iteratori delle query hanno altri metodi utili:
Metodo | Descrizione |
---|---|
__iter__()
| Parte del protocollo Iterator di Python. |
next()
| Restituisce il risultato successivo o genera l'eccezione StopIteration se non è presente. |
has_next()
| Restituisce True se una chiamata next()
successiva restituirà un risultato, False se genererà
StopIteration .Blocca fino a quando la risposta a questa domanda non è nota e memorizza il risultato (se presente) nel buffer finché 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() in realtà genera StopIteration ),
ma mai un falso negativo (False quando
next() restituisce effettivamente un risultato).
|
cursor_before()
| Restituisce un cursore di query che rappresenta un punto subito prima dell'ultimo risultato restituito. Genera un'eccezione se non è disponibile alcun cursore (in particolare se non è stata superata l'opzione di query produce_cursors ).
|
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 non è stata superata l'opzione di query produce_cursors ).
|
index_list()
| Restituisce un elenco di indici utilizzati da una query eseguita, inclusi indici primari, composti, di tipo e a proprietà singola. |
Cursori di query
Un cursore di query è una piccola struttura di dati opaca
che rappresenta un punto di ripresa in una query. È utile per mostrare a un utente una pagina di risultati alla volta; è utile anche per gestire job lunghi che potrebbero dover essere arrestati e ripresi.
Un modo tipico per utilizzarle è il metodo fetch_page()
di una query.
Funziona in un certo modo come fetch()
, ma restituisce un triplo
(results, cursor, more)
.
Il flag more
restituito indica che probabilmente ci sono più risultati; una UI può usarlo, 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. Se passi un cursore non valido, viene generato un valore BadArgumentError
. Tieni presente che la convalida controlla solo se il valore è codificato in base64. Dovrai eseguire eventuali altre verifiche necessarie.
Di conseguenza, per consentire all'utente di visualizzare tutte le entità corrispondenti a una query, recuperandole una pagina alla volta, il codice potrebbe avere il seguente aspetto:
...
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 inoltre presente che, poiché il flag more
viene implementato utilizzando il metodo probably_has_next()
dell'iteratore, in rari casi potrebbe restituire True
anche se la pagina successiva è vuota.
Alcune query NDB non supportano i cursori di query, ma puoi correggerli.
Se una query utilizza
IN
, OR
o !=
,
i risultati della query
non funzioneranno con i cursori a meno che non vengano 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, modificalo in
User.query(User.name.IN(['Joe', 'Jane'])).order(User.name, User.key).fetch_page(N)
Invece di "impaginare" i risultati della query, puoi utilizzare il metodo iter()
di una query per far arrivare il cursore in un punto preciso.
Per farlo, passa produce_cursors=True
a iter()
;
quando l'iteratore si trova nella posizione giusta, chiama il relativo cursor_after()
per ottenere un cursore che si trova subito dopo. (oppure, allo stesso modo, richiama
cursor_before()
per un cursore subito prima).
Tieni presente che chiamando cursor_after()
o
cursor_before()
potrebbe essere effettuata una chiamata Datastore di blocco, eseguendo nuovamente
parte della query per estrarre un cursore che punta al centro di
un batch.
Per utilizzare il cursore per scorrere i risultati della query a ritroso, crea una query inversa:
Chiamata a una funzione per ogni entità ("Mappatura")
Supponi di dover ottenere le entità Account
corrispondenti alle entità Message
restituite da una query.
Potresti scrivere qualcosa del genere:
Tuttavia, questa procedura è piuttosto inefficiente: attende il recupero di un'entità, quindi utilizza l'entità, attende l'entità successiva, quindi utilizza l'entità. Il tempo di attesa è molto lungo. Un altro modo è scrivere una funzione di callback mappata sui risultati della query:
Questa versione verrà eseguita un po' più velocemente del semplice for
loop di cui sopra perché è possibile una certa contemporaneità.
Tuttavia, poiché la chiamata a get()
in
callback()
è ancora sincrona,
il guadagno non è enorme.
Questo è un buon posto per utilizzare i recuperi asincroni.
GQL
GQL è un linguaggio di tipo SQL per recuperare 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 GQL è simile a quella di SQL. La sintassi di GQL è descritta nella documentazione di riferimento GQL.
Puoi utilizzare GQL per creare query. È simile alla creazione di una query con Model.query()
, ma utilizza la sintassi GQL per definire il filtro e l'ordine delle query. Per utilizzarla:
ndb.gql(querystring)
restituisce un oggettoQuery
(lo stesso tipo restituito daModel.query()
). Tutti i metodi usuali sono disponibili su questi oggettiQuery
:fetch()
,map_async()
,filter()
e così via.Model.gql(querystring)
è un'abbreviazione dindb.gql("SELECT * FROM Model " + querystring)
. In genere, querystring è simile a"WHERE prop1 > 0 AND prop2 = TRUE"
.- Per eseguire query su modelli che contengono proprietà strutturate, puoi usare
foo.bar
nella sintassi GQL per fare riferimento alle proprietà secondarie. - GQL supporta le associazioni di parametri di tipo SQL. Un'applicazione può definire una query e quindi associare i valori al suo interno:
La chiamata alla funzione
bind()
di una query restituisce una nuova query, che non modifica l'originale. - Se la classe del modello esegue l'override del metodo della classe
_get_kind()
, la query GQL deve utilizzare il tipo restituito da quella funzione, non il nome della classe. - Se una proprietà nel modello sostituisce il suo 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 di parametri se alcuni valori della query sono variabili fornite dall'utente. In questo modo si evitano gli attacchi basati su compromissioni sintattiche.
È un errore eseguire una query per un modello che non è stato importato (o, più in generale, definito).
È un errore utilizzare il nome di una proprietà non definito dalla classe del modello, a meno che il modello non sia un tipo di espansione.
Se specifichi un limite o un offset per fetch()
della query, verrà eseguito l'override
del limite o dell'offset impostato dalle clausole OFFSET
e
LIMIT
di GQL. Non combinare i valori OFFSET
e LIMIT
di GQL con fetch_page()
. Tieni presente che il numero massimo di 1000 risultati imposto da App Engine sulle query si applica sia all'offset che al limite.
Se sei abituato a SQL, fai attenzione alle false ipotesi quando utilizzi GQL. GQL viene tradotto nell'API di query nativa di NDB. Questo è diverso da un tipico mapping di oggetti relazionali (come il supporto dei database di SQLAlchemy o Django), in cui le chiamate API vengono tradotte in SQL prima di essere trasmesse al server di database. GQL non supporta le modifiche di Datastore (inserimenti, eliminazioni o aggiornamenti); supporta solo le query.