Un'applicazione può utilizzare le query per cercare nell'Datastore le entità che corrispondono a criteri di ricerca specifici chiamati filtri.
Panoramica
Un'applicazione può utilizzare le query per cercare nell'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:
...
...
Alcune query sono più complesse di altre; per queste, il datastore ha bisogno di 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
relativo 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 poi aggiornare il sito web in modo che utilizzi index.yaml
modificato automaticamente.
Puoi aggiornare index.yaml
separatamente dal caricamento
dell'applicazione eseguendo
gcloud app deploy index.yaml
.
Se il tuo datastore contiene 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 scoprire quando gli indici
sono stati creati.
App Engine Datastore supporta in modo nativo i filtri per
corrispondenze esatte (operatore ==) e
confronti (operatori <, <=, > e >=).
Supporta la combinazione di più filtri utilizzando un'operazione
AND
booleana, con alcune limitazioni (vedi di seguito).
Oltre agli operatori nativi,
l'API supporta l'operatore !=
,
la combinazione di gruppi di filtri utilizzando l'operazione booleana OR
e l'operazione IN
,
che verifica l'uguaglianza con uno degli elenchi di valori possibili
(come l'operatore "in" di Python).
Queste operazioni non corrispondono 1:1 alle operazioni native di Datastore, pertanto sono un po' strane 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 impone alcune restrizioni alle query. La violazione di queste norme comporterà l'innalzamento 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 in base a 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 full-text. Esistono modi per implementare corrispondenze non sensibili alle maiuscole e minuscole e persino la ricerca a testo intero utilizzando le proprietà calcolate.
Filtro per valori della proprietà
Richiama la classe Account da 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 una determinata 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 esista 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 come la seguente:
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' strane, come descritto in != e IN.
Puoi specificare più filtri:
Combina gli argomenti del filtro specificati, restituendo tutte le entità Account il cui valore userid è maggiore o uguale a 40 e inferiore a 50.
Nota: come accennato in precedenza, Datastore rifiuta le query che utilizzano il filtro di disuguaglianza su più di una proprietà.
Invece di specificare un intero filtro di query in una singola espressione, potresti trovare più comodo crearlo per passaggi, ad esempio:
query3
è equivalente 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
.
Operazioni != e IN
Ricorda la classe Article di NDB Properties:
Le operazioni !=
(diverso da) e IN
(appartenenza)
vengono implementate combinando altri filtri utilizzando l'operazione
OR
. Il primo di questi,
property != value
è implementato come
(property < value) OR (property > value)
Ad esempio,
è equivalente a
Nota:
Forse sorprendentemente, questa query non cerca
entità Article
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:
Tuttavia, questo non sarebbe 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,
è equivalente a
Nota:
le query che utilizzano OR
duplicano i risultati: il flusso di risultati non
include un'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 proprietà ripetute. In particolare, un filtro
come
utilizza un singolo valore, anche se Article.tags
è una proprietà ripetuta. Non puoi confrontare le proprietà ripetute con gli oggetti elenco (Datastore non lo comprenderà) e un filtro come
fa qualcosa di completamente diverso dalla ricerca di entità Article
il cui valore dei tag è l'elenco ['python', 'ruby', 'php']
: cerca entità il cui valore tags
(considerato un elenco) contiene almeno uno di questi valori.
L'esecuzione di query per un valore di None
su una proprietà ripetuta ha
un comportamento indefinito, quindi 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 tipo troppo complessa potrebbe non riuscire e generare 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
al di sotto.
Per eseguire questa normalizzazione, devi ricordare sia le regole della logica booleana,
sia come vengono effettivamente implementati i filtri !=
e IN
:
- Espandi gli operatori
!=
eIN
alla loro forma primitiva, dove!=
diventa un controllo per verificare se la proprietà è < o > del valore eIN
diventa un controllo per verificare se la proprietà è == al primo valore o al secondo valore o...fino all'ultimo valore dell'elenco. - Un
AND
con unOR
al suo interno equivale a unOR
di piùAND
applicati agli operandiAND
originali, con un singolo operandoOR
sostituito all'operandoOR
originale. Ad esempioAND(a, b, OR(c, d))
equivale aOR(AND(a, b, c), AND(a, b, d))
- Un
AND
con un operando che è a sua volta un'operazioneAND
può incorporare gli operandi dell'AND
nidificato nell'AND
contenitore. Ad esempio,AND(a, b, AND(c, d))
equivale aAND(a, b, c, d)
- Un'
OR
che ha un operando che a sua volta è un'operazioneOR
può incorporare gli operandi dell'OR
nidificata nell'OR
che la contiene. Ad esempio,OR(a, b, OR(c, d))
equivale aOR(a, b, c, d)
Se applichiamo queste trasformazioni in più fasi al filtro di esempio, utilizzando una notazione più semplice di Python, otteniamo:
- 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'))))
- Utilizzando la regola n. 2 sul
OR
più interno nidificato all'interno di 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
nidificato all'interno di un altroOR
:AND(tags == 'python', OR(tags == 'ruby', tags == 'jruby', AND(tags == 'php', tags < 'perl'), AND(tags == 'php', tags > 'perl')))
- Utilizzando la regola n. 2 sui
OR
rimanenti 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')))
- Utilizzando la regola n. 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 l'AND
di 3 clausole OR
con 2 clausole di base ciascuna.
Una volta normalizzata, questa diventa una OR
di 8 clausole AND
con 3 clausole di base ciascuna, ovvero 6 termini diventano 24.
Specificare gli 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:
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:
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 di antenati consentono di eseguire query fortemente coerenti sul datastore, ma le entità con lo stesso antenato sono limitate a una scrittura al secondo. Ecco un semplice confronto tra i compromessi e la struttura di una query principale e non principale che utilizza i clienti e i loro acquisti associati nel datastore.
Nel seguente esempio non predecessore, nel datastore è presente un'entità per ogni Customer
e un'entità per ogni Purchase
, con un KeyProperty
che punta al cliente.
Per trovare tutti gli acquisti appartenenti al cliente, puoi utilizzare la seguente query:
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 degli antenati.
Per clienti e acquisti con query di antenati, la struttura rimane la stessa con
due entità separate. La parte relativa al cliente è la stessa. Tuttavia, quando crei acquisti,
non devi più specificare KeyProperty()
per gli acquisti. Questo perché quando utilizzi le query di antenati, chiami la chiave dell'entità cliente quando crei un'entità acquisto.
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 sarà limitato a una scrittura per antenato al secondo. Il seguente codice crea un'entità con un predecessore:
Per eseguire una query per gli acquisti di un determinato cliente, utilizza la seguente query.
Attributi 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 | Elemento principale specificato per la query |
filters | FilterNode | None | Espressione di filtro |
ordini | Order | None | Ordinare gli ordini |
La stampa di un oggetto query (o la chiamata di str()
o
repr()
) produce una rappresentazione di stringa ben formattata:
Filtro per 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'
avrebbe il seguente aspetto
Se combini più filtri di questo tipo, questi potrebbero corrispondere
a entità secondarie Address
diverse all'interno
della stessa entità Contatto.
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 sottoentità:
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
avesse una proprietà
country
con default='us'
, l'esempio
precedente restituirebbe solo i contatti con paese uguale a
'us'
; per prendere in considerazione i contatti con altri valori di paese,
dovresti filtrare per
Address(city='San Francisco', street='Spear St',
country=None)
.
Se una sottoentità ha valori di proprietà uguali a None
,
vengono ignorati. Pertanto, non ha senso filtrare in base a un valore della proprietà
dell'entità secondaria pari a None
.
Utilizzo di proprietà denominate per 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 comodo trasformarla
in una query come
Article.query(Article."tags" == "python") # does NOT work
Se il tuo modello è un
Expando
, il tuo
filtro può utilizzare GenericProperty
, la
classe Expando
per le proprietà dinamiche:
L'utilizzo di GenericProperty
funziona anche se il modello non è un
Expando
, ma se vuoi assicurarti di utilizzare solo i nomi delle proprietà definiti, puoi anche utilizzare l'attributo di classe
_properties
.
o utilizza getattr()
per ottenerlo dal corso:
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 qualcosa del tipo
Qui il nome Python è title
, ma il nome del datastore è
t
.
Questi approcci funzionano anche per ordinare i risultati delle query:
QueryIterator
Mentre una query è in corso, il suo stato viene mantenuto in un
oggetto iteratore. La maggior parte delle applicazioni non li utilizza 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 oggettoQuery
- chiamando il metodo
iter()
dell'oggettoQuery
La prima supporta l'uso di un ciclo for
Python
(che chiama implicitamente la funzione iter()
)
per scorrere una query.
Il secondo modo, utilizzando il metodo iter()
dell'oggetto Query
, ti consente di passare opzioni all'iteratore per influire sul suo comportamento. Ad esempio, per utilizzare una
query solo chiavi in un ciclo for
, puoi scrivere:
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 ce ne sono. |
has_next()
| Restituisce True se una chiamata next() successiva
restituirà un risultato, False se genererà
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 a volte 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 l'opzione di query produce_cursors non è stata trasmessa).
|
cursor_after()
| Restituisce un cursore di query che rappresenta un punto immediatamente successivo all'ultimo
risultato restituito. Genera un'eccezione se non è disponibile alcun cursore (in particolare, se l'opzione di query produce_cursors non è stata trasmessa).
|
index_list()
| Restituisce un elenco degli indici utilizzati da una query eseguita, inclusi gli indici primari, composti, di tipo e a singola proprietà. |
Cursori di query
Un cursore di query è una piccola struttura di dati opaca
che rappresenta un punto di ripresa in una query. Questa funzionalità è utile
per mostrare a un utente una pagina di risultati alla volta. È utile anche
per gestire lavori 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 ci sono altri
risultati; un'interfaccia utente 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 BadArgumentError
se passi un cursore non valido. Tieni presente che la convalida controlla 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:
...
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 di nuovo dal client in una richiesta
successiva.
Nota:Il metodo fetch_page()
in genere restituisce un cursore anche
se non ci sono altri risultati, ma 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 rari casi 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)
restituisce un errore, modificalo in
User.query(User.name.IN(['Joe', 'Jane'])).order(User.name, User.key).fetch_page(N)
Invece di scorrere 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 che si trova subito dopo. (o, in modo simile, chiama
cursor_before()
per un cursore appena prima).
Tieni presente che la chiamata di cursor_after()
o
cursor_before()
potrebbe effettuare una chiamata di blocco di Datastore, eseguendo nuovamente
parte della query per estrarre un cursore che punta al centro di
un batch.
Per utilizzare un cursore per scorrere all'indietro i risultati della query, crea una query inversa:
Chiamare una funzione per ogni entità ("mapping")
Supponiamo di dover ottenere le entità Account
corrispondenti alle entità Message
restituite da una query.
Potresti scrivere qualcosa del genere:
Tuttavia, questo approccio è piuttosto inefficiente: attende di recuperare un'entità, poi la utilizza, attende l'entità successiva e la utilizza. C'è molto tempo di attesa. Un altro modo è scrivere una funzione di callback mappata sui risultati della query:
Questa versione verrà eseguita un po' più velocemente rispetto al semplice ciclo for
precedente perché è possibile una certa concorrenza.
Tuttavia, poiché la chiamata get()
in
callback()
è ancora sincrona,
il guadagno non è enorme.
Questo è un buon punto per utilizzare
recuperi asincroni.
GQL
GQL è un linguaggio simile a 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 di GQL è simile a quella di SQL. La sintassi GQL è descritta nel riferimento GQL.
Puoi utilizzare GQL per creare query. Questa operazione è simile alla creazione di una query
con Model.query()
, ma utilizza la sintassi GQL per definire
il filtro e l'ordine della query. Per utilizzarla:
ndb.gql(querystring)
restituisce un oggettoQuery
(lo stesso tipo restituito daModel.query()
). Tutti i metodi consueti sono disponibili su questi oggettiQuery
:fetch()
,map_async()
,filter()
, ecc.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 sui modelli contenenti proprietà strutturate, puoi utilizzare
foo.bar
nella sintassi GQL per fare riferimento alle proprietà secondarie. - GQL supporta i binding dei parametri in stile SQL. Un'applicazione può definire
una query e poi associarvi i valori:
La chiamata della funzione
bind()
di una query restituisce una nuova query; 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 questa funzione, non il nome della classe. - Se una proprietà nel modello sostituisce il proprio nome (ad es.
foo = StringProperty('bar')
), la query GQL deve utilizzare il nome della proprietà sottoposto a override (nell'esempio,bar
).
Utilizza sempre la funzionalità di binding dei parametri se alcuni valori nella query sono variabili fornite dall'utente. In questo modo si evitano attacchi basati su hack sintattici.
È un errore eseguire una query per un modello che non è stato importato (o, più in generale, definito).
È un errore utilizzare un nome di proprietà non definito dalla classe del modello, a meno che il modello non sia un Expando.
La specifica di un limite o di un offset per la clausola fetch()
della query esegue 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 familiarità con SQL, fai attenzione a non fare ipotesi errate quando utilizzi GQL. GQL viene tradotto nell'API di query nativa di NDB. Questo approccio è diverso da un tipico mapper ORM (Object-Relational Mapper), come SQLAlchemy o il supporto del database di Django, in cui le chiamate API vengono tradotte in SQL prima di essere trasmesse al server di database. GQL non supporta le modifiche a Datastore (inserimenti, eliminazioni o aggiornamenti); supporta solo le query.