Un'applicazione può utilizzare query per cercare nel Datastore entità che corrispondono a criteri di ricerca specifici chiamati filtri.
Panoramica
Un'applicazione può utilizzare query per cercare nel Datastore 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
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 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 da utilizzare 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 degli indici prima di caricare il codice che utilizza il nuovo indice.
Puoi utilizzare la Console di amministrazione per scoprire quando la compilazione degli indici è stata completata.
Datastore di App Engine supporta in modo nativo i filtri per
le corrispondenze esatte (operatore ==) e
i confronti (operatori <, <=, > e >=).
Supporta la combinazione di più filtri utilizzando un'operazione booleanaAND
, 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 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 relativamente insolite e lente.
Vengono implementati utilizzando l'unione in memoria degli stream di risultati. Tieni presente che p != v
è
implementato come "p < v OPPURE p > v".
Questo è importante per le proprietà ripetute.
Limitazioni: Datastore applica alcune limitazioni alle query. La violazione di queste regole comporterà l'invio di eccezioni. Ad esempio, al momento non sono consentiti l'unione di troppi filtri, l'utilizzo di ineguaglianze per più proprietà o l'unione di un'ingiunzione con un ordine di ordinamento in una proprietà diversa. 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 sensibilità alle maiuscole o la cosiddetta ricerca full-text. Esistono modi per implementare corrispondenze senza distinzione tra maiuscole e minuscole e persino la ricerca a testo intero utilizzando le proprietà calcolate.
Filtri per valori delle proprietà
Richiama la classe Account da Proprietà NDB:
In genere, non è necessario recuperare tutte le entità di un determinato tipo, ma solo quelle con un valore o un intervallo di valori specifico per una proprietà.
Gli oggetti Property 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 esiste 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:
Vengono trovate tutte le entità Account la cui proprietà userid
è superiore o uguale a 40.
Due di queste operazioni, != e IN, sono implementate come combinazioni delle altre e sono un po' insolite, come descritto in != e IN.
Puoi specificare più filtri:
Questo combina gli argomenti del filtro specificati, 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 il filtro per disuguaglianza su più proprietà.
Invece di specificare un intero filtro della query in una singola espressione, potresti trovare più pratico crearlo in più passaggi: ad esempio:
query3
è equivalente alla variabile query
dell'
esempio precedente. Tieni presente che gli oggetti query sono immutabili, quindi la compilazione di query2
non influisce su query1
e la compilazione di query3
non influisce su query1
o query2
.
Operazioni != e IN
Ricorda la classe Articolo da Proprietà NDB:
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 uno dei suoi tag è "perl":
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, è implementato come
(property == value1) OR (property == value2) OR ...
Ad esempio,
è equivalente a
Nota:
le query che utilizzano OR
deduplicano i risultati: lo stream di 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 è anche un 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 proprietà ripetute con oggetti list (Datastore non lo comprende) e un filtro come
esegue un'operazione completamente diversa dalla ricerca di entità Article
il cui valore tags è l'elenco ['python', 'ruby', 'php']
:
cerca le entità il cui valore tags
(considerato come elenco)
contiene almeno uno di questi valori.
La query per un valore None
in una proprietà ripetuta ha un comportamento non definito. Non eseguire questa operazione.
Combinare 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 con un'eccezione. È più sicuro normalizzare questi
filtri in modo che esista (al massimo) una singola operazione OR
nella parte superiore
dell'albero delle espressioni e un singolo livello di operazioni OR
al di sotto.AND
Per eseguire questa normalizzazione, devi ricordare sia le regole della logica booleana sia la modalità di implementazione effettiva dei filtri !=
e IN
:
- Espandi gli operatori
!=
eIN
nella loro forma primitiva, dove!=
diventa un controllo per verificare che la proprietà sia < o > del valore eIN
diventa un controllo per verificare che la proprietà sia == al primo valore o al secondo valore o… fino all'ultimo valore nell'elenco. - Un
AND
contenente unOR
è equivalente a unOR
di piùAND
applicati agli operandiAND
originali, con un singolo operandoOR
sostitutivo dell'OR
originale. Ad esempio,AND(a, b, OR(c, d))
è equivalente aOR(AND(a, b, c), AND(a, b, d))
- Un
AND
con un operando che è a sua volta un'operazioneAND
può incorporare gli operandi delAND
nidificato nell'AND
esterno. Ad esempio,AND(a, b, AND(c, d))
è equivalente aAND(a, b, c, d)
- Un
OR
con un operando che è a sua volta un'operazioneOR
può incorporare gli operandi dell'OR
nidificato nell'OR
contenente. Ad esempio,OR(a, b, OR(c, d))
è equivalente aOR(a, b, c, d)
Se applichiamo queste trasformazioni in più fasi al filtro di esempio, utilizzando una notazione più semplice rispetto a Python, otteniamo:
- Utilizzo della regola 1 per gli operatori
IN
e!=
:AND(tags == 'python', OR(tags == 'ruby', tags == 'jruby', AND(tags == 'php', OR(tags < 'perl', tags > 'perl'))))
- Utilizzo della regola 2 sull'
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 4 per
OR
nidificato in un altroOR
:AND(tags == 'python', OR(tags == 'ruby', tags == 'jruby', AND(tags == 'php', tags < 'perl'), AND(tags == 'php', tags > 'perl')))
- Utilizzo della regola 2 per il
OR
rimanente nidificato 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')))
- Utilizza la regola 3 per comprimere gli altri
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. Prendi in considerazione il AND
di 3 OR
clausole con 2 clausole di base ciascuna.
Se viene normalizzato, diventa un OR
di 8 clausole AND
con tre clausole di base ciascuna: ovvero, 6 termini diventano 24.
Specificare gli ordini di ordinamento
Puoi utilizzare il metodo order()
per specificare l'ordine in cui una query restituisce i risultati. Questo metodo accetta un elenco di argomenti, ciascuno 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 serie di entità consecutive con la stessa proprietà dei contenuti verranno messe in ordine decrescente in base al valore della proprietà date
.
Puoi utilizzare più chiamate order()
per 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 sugli antenati ti consentono di eseguire query fortemente coerenti nel datastore, ma le entità con lo stesso antenato sono limitate a una scrittura al secondo. Di seguito è riportato un semplice confronto dei compromessi e della struttura tra una query principale e una non principale che utilizza i clienti e gli acquisti associati nel data store.
Nel seguente esempio di entità non principale, nel datastore è presente un'entità per ogni Customer
e un'entità per ogni Purchase
, con un KeyProperty
che rimanda al cliente.
Per trovare tutti gli acquisti che appartengono al cliente, puoi utilizzare la seguente query:
In questo caso, il datastore offre un'elevata velocità in scrittura, ma solo la coerenza finale. Se è stato aggiunto un nuovo acquisto, potresti ricevere dati non aggiornati. Puoi eliminare questo comportamento utilizzando le query sugli antenati.
Per i clienti e gli acquisti con query sugli antenati, hai sempre la stessa struttura con due entità distinte. La parte relativa al cliente è la stessa. Tuttavia, quando crei gli acquisti,
non devi più specificare KeyProperty()
per gli acquisti. Questo accade perché, quando utilizzi le query sugli antenati, chiami la chiave dell'entità cliente quando crei un'entità di acquisto.
Ogni acquisto ha una chiave e anche il cliente ha la propria chiave. Tuttavia, ogni chiave di acquisto avrà incorporata la chiave di customer_entity. Ricorda che questo sarà limitato a una scrittura per antenato al secondo. Il seguente codice crea un'entità con un'antecedente:
Per eseguire una query sugli 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 tipo (di solito il nome della classe) |
predecessore | Key | None | Antenato specificato per la query |
filters | FilterNode | None | Espressione di filtro |
ordini | Order | None | Ordinamenti |
La stampa di un oggetto query (o la chiamata di str()
o
repr()
)
produce una rappresentazione di stringa ben formattata:
Filtri per valori delle proprietà strutturate
Una query può filtrare direttamente in base ai valori di campo delle proprietà strutturate.
Ad esempio, una query per tutti i contatti con un indirizzo la cui città è
'Amsterdam'
avrà il seguente aspetto
Se combini più filtri di questo tipo, i filtri potrebbero corrispondere a diverse entità secondarie Address
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 restituisce solo risultati con più valori in una singola entità secondaria:
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 riportato sopra restituirebbe solo i contatti con il paese uguale a
'us'
; per prendere in considerazione i contatti con altri valori di paese,
dovrai filtrare per
Address(city='San Francisco', street='Spear St',
country=None)
.
Se una entità secondaria ha valori di proprietà uguali a None
,
questi vengono ignorati. Pertanto, non ha senso applicare un filtro per un valore della proprietà della sottoentità None
.
Utilizzo di proprietà denominate tramite stringa
A volte potresti voler filtrare o ordinare una query in base a una proprietà
il cui nome è
specificato tramite stringa. Ad esempio, se consenti all'utente di inserire query di ricerca come tags:python
, sarebbe opportuno trasformarla in qualche modo in una query come
Article.query(Article."tags" == "python") # does NOT work
Se il tuo modello è un
Expando
, il
filtro può utilizzare GenericProperty
, la
classe utilizzata da Expando
per le proprietà dinamiche:
L'utilizzo di GenericProperty
funziona anche se il tuo modello non è un
Expando
, ma se vuoi assicurarti di utilizzare
solo i nomi delle proprietà definiti, puoi anche utilizzare
l'attributo della classe _properties
oppure utilizza getattr()
per scaricarlo dal corso:
La differenza è che getattr()
utilizza il "nome Python"
della proprietà, mentre _properties
è indicizzato dal
"nome del data store" della proprietà. Queste differiscono solo quando la proprietà è stata dichiarata con qualcosa di simile a
Qui il nome Python è title
, ma il nome del data store è
t
.
Questi approcci funzionano anche per ordinare i risultati delle query:
Iteratori di query
Mentre una query è in corso, il relativo 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 unQuery
oggetto - chiamando il metodo
iter()
dell'oggettoQuery
.
Il primo supporta l'utilizzo di un ciclo for
di Python
(che chiama implicitamente la funzione iter()
)
per eseguire un ciclo su una query.
Il secondo metodo, che utilizza il metodo iter()
dell'oggetto Query
, consente di passare opzioni all'iteratore per modificarne il comportamento. Ad esempio, per utilizzare una query solo per chiavi in un loop for
, puoi scrivere quanto segue:
Gli iteratori di query hanno altri metodi utili:
Metodo | Descrizione |
---|---|
__iter__()
| Componente del protocollo iterator di Python. |
next()
| Restituisce il risultato successivo
o solleva l'eccezione StopIteration se non ce ne sono. |
has_next()
| Restituisce True se una chiamata next()
successiva restituirà un risultato, False se verrà generato
StopIteration .Si blocca finché non viene fornita una risposta a questa domanda e memorizza nel buffer il risultato (se presente) finché non lo recuperi con next() .
|
probably_has_next()
| Come has_next() , ma utilizza una scorciatoia più rapida
(e a volte imprecisa).Potrebbe restituire un falso positivo ( True quando
next() in realtà genererebbe StopIteration ),
ma mai un falso negativo (False quando
next() in realtà restituirebbe 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 non è stata passata l'opzione di query produce_cursors ).
|
cursor_after()
| Restituisce un cursore di query che rappresenta un punto appena dopo l'ultimo
risultato restituito. Genera un'eccezione se non è disponibile alcun cursore (in particolare, se non è stata passata l'opzione di query produce_cursors ).
|
index_list()
| Restituisce un elenco di indici utilizzati da una query eseguita, inclusi gli indici principali, composti, di tipo e di proprietà singola. |
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)
.
L'indicatore more
restituito indica che probabilmente sono presenti 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 le eventuali convalide necessarie.
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:
...
Tieni presente l'utilizzo di urlsafe()
e
Cursor(urlsafe=s)
per eseguire la serializzazione e la deserializzazione del 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: typically il metodo fetch_page()
restituisce un cursore anche se non ci sono altri risultati, ma non è garantito: il valore del cursore restituito può essere None
. Tieni inoltre presente che, poiché il flag more
è 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)
genera un errore, impostalo su
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 è al posto giusto, chiama il suo cursor_after()
per ottenere un cursore che si trova subito dopo. In alternativa, chiama
cursor_before()
per un cursore appena prima.
Tieni presente che l'istruzione cursor_after()
o
cursor_before()
potrebbe eseguire una chiamata bloccante a Datastore, eseguendo nuovamente
parte della query per estrarre un cursore che punti al centro di
un batch.
Per utilizzare un cursore per scorrere i risultati della query a ritroso, crea una query inversa:
Chiamata di una funzione per ogni entità ("Mappatura")
Supponiamo che tu debba recuperare le entità Account
corrispondenti alle entità Message
restituite da una query.
Potresti scrivere qualcosa di simile al seguente:
Tuttavia, questo approccio è piuttosto inefficiente: attende di recuperare un'entità, poi la utilizza; attende l'entità successiva, la utilizza. I tempi di attesa sono molto lunghi. Un altro modo è scrivere una funzione di callback mappata ai risultati della query:
Questa versione verrà eseguita un po' più velocemente del semplice loop for
sopra perché è possibile una certa concorrenza.
Tuttavia, poiché la chiamata in get()
callback()
è ancora sincrona,
il guadagno non è enorme.
Questo è un buon punto per utilizzare
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. Questa operazione è simile alla creazione di una query con Model.query()
, ma utilizza la sintassi GQL per definire il filtro e l'ordinamento 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()
e così via.Model.gql(querystring)
è una rappresentazione abbreviata 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 le associazioni di parametri simili a 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 quella originale. - Se la classe del modello sostituisce il 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 proprio nome (ad es.
foo = StringProperty('bar')
) la tua query GQL deve utilizzare il nome della proprietà soprascritta (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.
È 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 fetch()
della query ha la precedenza sul limite o sull'offset impostato dalle clausole OFFSET
e LIMIT
di GQL. Non combinare OFFSET
e
LIMIT
di GQL con fetch_page()
. Tieni presente che il numero massimo di risultati
di 1000 imposto da App Engine alle query si applica sia a offset che a limit.
Se sei abituato a SQL, fai attenzione a non fare false supposizioni quando utilizzi GQL. GQL viene tradotto nell'API di query nativa di NDB. È diverso da un tipico mapper Object-Relational (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.