Un'applicazione può utilizzare le query per cercare nel datastore entità che corrispondono a criteri di ricerca specifici chiamati filtri.
Panoramica
Un'applicazione può utilizzare le query per cercare nel datastore entità che corrispondono a criteri di ricerca specifici chiamati filtri. Ad esempio, un'applicazione che tiene traccia di diversi libri degli ospiti potrebbe utilizzare una query per recuperare i messaggi da un libro degli ospiti, ordinati per data:
...
...
Alcune query sono più complesse di altre, poiché 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 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 prevede di provare una nuova query sul server di sviluppo e quindi di 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.
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 tramite un'operazione booleana AND
, con alcune limitazioni (vedi di seguito).
Oltre agli operatori nativi, l'API supporta l'operatore !=
, combinando gruppi di filtri tramite l'operazione booleana OR
e l'operazione IN
, che verifica l'uguaglianza con uno di un elenco di possibili valori (come l'operatore "in" di Python).
Queste operazioni non vengono mappate 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 aspetto è importante per le proprietà ripetute).
Limitazioni: Datastore applica alcune restrizioni alle query. In caso di violazione, verranno generate delle eccezioni. Ad esempio, al momento non è consentita la combinazione di troppi filtri, l'utilizzo di disuguaglianze per più proprietà o la combinazione di una disuguaglianza con un ordinamento in 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 corrispondenze di sottostringhe, 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 Proprietà NDB:
In genere, non vuoi recuperare tutte le entità di un determinato tipo, bensì solo quelle con un valore o un intervallo di valori specifico per una proprietà.
Gli oggetti delle 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 elemento Account
con 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à dell'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, 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 utilizzando un filtro di disuguaglianza su più di una proprietà.
Anziché specificare un intero filtro di query in una singola espressione, potresti trovare più pratico crearlo in passaggi: ad esempio:
query3
è equivalente alla variabile query
dell'esempio precedente. Tieni presente che gli oggetti di 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 Articolo delle proprietà NDB:
Le operazioni !=
(diverso da) e IN
(abbonamento)
vengono implementate combinando altri filtri mediante l'operazione
OR
. Il primo di questi
property != value
viene implementato come
(property < value) OR (property > value)
Ad esempio:
è equivalente a
Nota: è sorprendente che questa query non cerchi entità Article
che non includono "perl" come tag.
Vengono invece trovate tutte le entità con almeno un tag disuguale a "perl".
Ad esempio, la seguente entità verrebbe inclusa nei risultati, anche se ha "perl" come uno dei suoi tag:
Tuttavia, questo non viene incluso:
Non è possibile eseguire query su entità che non includono un tag uguale a "perl".
Analogamente, l'operazione IN
property IN [value1, value2, ...]
per i test di appartenenza a un elenco di possibili valori, viene implementato
(property == value1) OR (property == value2) OR ...
Ad esempio:
è equivalente a
Nota: le query che utilizzano OR
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 per proprietà ripetute
La classe Article
definita nella sezione precedente funge anche
da esempio di esecuzione di query per le 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 elencati (il datastore non le capisce).
fa qualcosa di completamente diverso dalla ricerca delle
entità Article
il cui valore dei tag è l'elenco
['python', 'ruby', 'php']
:
cerca entità il cui valore tags
(considerato come elenco)
contiene almeno uno di questi valori.
L'esecuzione di query su un valore di None
su una proprietà ripetuta ha
un comportamento indefinito; non farlo.
Combinazione di operazioni AND e OR
Puoi nidificare in modo arbitrario le operazioni AND
e OR
.
Ad esempio:
Tuttavia, a causa dell'implementazione di OR
, una query di questo modulo troppo complessa potrebbe non riuscire, con un'eccezione. Per maggiore sicurezza, normalizzi questi filtri in modo che sia presente (al massimo) una singola operazione OR
in cima all'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 i filtri !=
e IN
vengono effettivamente implementati:
- Espandi gli operatori
!=
eIN
alla loro forma primitiva, dove!=
diventa un controllo che la proprietà sia < o > rispetto al valore, mentreIN
diventa un controllo che la proprietà sia == al primo valore o al secondo valore o...fino all'ultimo valore dell'elenco. - Un elemento
AND
con unOR
all'interno equivale a unOR
di piùAND
applicati agli operandiAND
originali, con un singolo operandoOR
sostituito dall'operandoOR
originale. Ad esempioAND(a, b, OR(c, d))
è equivalente aOR(AND(a, b, c), AND(a, b, d))
- Un elemento
AND
che contiene un operando stesso di un'operazioneAND
può incorporare gli operandi dell'elementoAND
nidificato nell'elementoAND
che lo contiene. Ad esempio,AND(a, b, AND(c, d))
è equivalente aAND(a, b, c, d)
- Un elemento
OR
che contiene un operando che è a sua volta un'operazioneOR
può incorporare gli operandi dell'elementoOR
nidificato nell'elementoOR
che lo contiene. Ad esempio,OR(a, b, OR(c, d))
è equivalente aOR(a, b, c, d)
Se applichiamo queste trasformazioni in fasi al filtro di esempio, utilizzando una notazione più semplice di Python, ottieni:
- Utilizzo della regola n. 1 con gli operatori
IN
e!=
:AND(tags == 'python', OR(tags == 'ruby', tags == 'jruby', AND(tags == 'php', OR(tags < 'perl', tags > 'perl'))))
- Utilizzo della regola n. 2 per l'elemento
OR
più interno nidificato all'interno di un elementoAND
: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 n. 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 n. 3 per comprimere gli elementi
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 AND
di 3 clausole OR
con due clausole di base ciascuna.
Una volta normalizzata, diventa un OR
di 8 clausole AND
con 3 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, ognuno dei quali è un oggetto della proprietà (da ordinare in ordine crescente) o la relativa negazione (in ordine decrescente). Ad esempio:
Questa operazione recupera tutte le entità Greeting
, ordinate in base
al valore crescente della relativa proprietà content
.
Le esecuzioni di entità consecutive con la stessa proprietà di 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 su Ancestor
Le query sui dati predecessori consentono di eseguire query a elevata coerenza nel 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 una query non predecessore utilizzando i clienti e i relativi acquisti associati nel datastore.
Nel seguente esempio di non predecessore, è presente 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 di proprietà del cliente, puoi utilizzare la seguente query:
In questo caso, il datastore offre una velocità effettiva di scrittura elevata, ma solo la coerenza finale. Se è stato aggiunto un nuovo acquisto, potresti ricevere dati inattivi. Puoi eliminare questo comportamento utilizzando query sui predecessori.
Per i clienti e gli acquisti con query sui predecessori, hai comunque la stessa struttura con due entità separate. La parte del cliente è la stessa. Tuttavia, quando crei acquisti,
non devi più specificare il KeyProperty()
per gli acquisti. Questo perché
quando utilizzi query dei predecessori, chiami la chiave dell'entità cliente quando
crei un'entità purchase.
Ogni acquisto ha una chiave e anche il cliente ha la sua. Tuttavia, in ogni chiave di acquisto sarà incorporata la chiave di customer_entity. Ricorda che questa operazione sarà limitata a una scrittura per predecessore al secondo. Quanto segue crea un'entità con un predecessore:
Per eseguire una query sugli 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 | Predefinito | Descrizione |
---|---|---|---|
kind | str | None | Nome del tipo (di solito il nome del corso) |
ancestor | Key | None | Antenato 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 a str()
o
repr()
su di esso) produce una rappresentazione di stringa ben formattata:
Filtro dei 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
di tipo diverse all'interno
della stessa entità Contact.
Ad esempio:
potrebbero 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à
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
ha una proprietà
country
con default='us'
, l'esempio
precedente restituirà solo i contatti con paese uguale a
'us'
; per considerare i contatti con valori di altri paesi,
devi applicare un filtro in base a
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à di sottoentità di None
.
Utilizzo delle proprietà denominate da una stringa
A volte, vuoi filtrare o ordinare una query in base a una proprietà il cui nome è specificato da una stringa. Ad esempio, se permetti all'utente di inserire query di ricerca come tags:python
, sarebbe pratico convertirle in una query come
Article.query(Article."tags" == "python") # does NOT work
Se il modello è
Expando
, il
filtro può utilizzare GenericProperty
, la
classe Expando
per le proprietà dinamiche:
L'uso di GenericProperty
funziona anche se il modello non è
Expando
, ma per assicurarti di utilizzare
solo nomi di proprietà definiti, puoi utilizzare anche 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 con il "nome datastore" della proprietà. Questi valori variano solo quando la proprietà
è stata dichiarata con informazioni del tipo
Qui il nome Python è title
, ma il nome del datastore è t
.
Questi approcci funzionano anche per ordinare i risultati delle query:
Iteratori di query
Mentre una query è in corso, il suo stato è mantenuto in un oggetto iteratore. La maggior parte delle applicazioni non li utilizzerà direttamente; di solito è più semplice chiamare fetch(20)
che manipolare l'oggetto iteratore.
Esistono due modi di base per ottenere un oggetto di questo tipo:
- usando la funzione
iter()
integrata di Python su un oggettoQuery
- chiamata al metodo
iter()
dell'oggettoQuery
Il primo supporta l'utilizzo di un loop for
Python (che chiama implicitamente la funzione iter()
) per eseguire un loop su una query.
Il secondo modo, utilizzando il metodo iter()
dell'oggetto Query
, ti consente di passare opzioni all'iteratore per influenzarne il comportamento. Ad esempio, per utilizzare una query basata solo sulle chiavi in un loop for
, puoi scrivere questo:
Gli iteratori delle query hanno altri metodi utili:
Metodo | Descrizione |
---|---|
__iter__()
| Parte del protocollo iteratore di Python. |
next()
| Restituisce il risultato successivo o solleva l'eccezione StopIteration se non c'è nessuno. |
has_next()
| Restituisce True se una chiamata next() successiva restituirà un risultato, False se aumenterà StopIteration .Si blocca finché la risposta a questa domanda non viene nota e il risultato viene memorizzato nel buffer (se presente) finché non la 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() in realtà aumenterà StopIteration ),
ma mai un falso negativo (False quando
next() restituirebbe effettivamente un risultato).
|
cursor_before()
| Restituisce un cursore di query che rappresenta un punto immediatamente prima dell'ultimo risultato restituito. Genera un'eccezione se non è disponibile alcun cursore (in particolare, se l'opzione di query produce_cursors 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 l'opzione di query produce_cursors non è stata passata).
|
index_list()
| Restituisce un elenco di indici utilizzati da una query eseguita, tra cui indici primari, composti, 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. Ciò è 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 di utilizzarli è il metodo fetch_page()
di una query.
Funziona in modo simile a fetch()
, ma restituisce un valore (results, cursor, more)
triplo.
Il flag more
restituito indica che probabilmente ci sono più risultati. Questa funzionalità può essere utilizzata da un'interfaccia utente, ad esempio per eliminare un link o un pulsante "Pagina successiva".
Per richiedere le pagine successive, fai passare il cursore restituito da una chiamata fetch_page()
alla successiva. Se passi un cursore non valido, viene visualizzato un BadArgumentError
. Tieni presente che la convalida verifica solo se il valore è codificato in base64. Dovrai eseguire le eventuali altre convalide necessarie.
Pertanto, 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.
Questo ti consente di passare un cursore a un client sul web nella risposta a una richiesta e di 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 restituito del cursore 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 delle query, ma puoi correggerli.
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, modifica il valore in
User.query(User.name.IN(['Joe', 'Jane'])).order(User.name, User.key).fetch_page(N)
Anziché "pagare" i risultati delle 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 nel posto giusto, chiama cursor_after()
per ottenere un cursore che si trova subito dopo. In alternativa, chiama cursor_before()
per un cursore immediatamente prima.
Tieni presente che la chiamata a cursor_after()
o
cursor_before()
potrebbe effettuare una chiamata Datastore che blocca, rieseguendo
parte della query per estrarre un cursore che punta 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")
Supponi di dover recuperare 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à, quindi utilizza l'entità; attende l'entità successiva e utilizza l'entità. I tempi di attesa sono molti. Un altro modo è scrivere una funzione di callback mappata sui risultati della query:
Questa versione verrà eseguita un po' più velocemente del semplice loop for
riportato sopra perché è possibile una certa contemporaneità.
Tuttavia, poiché la chiamata get()
in
callback()
è ancora sincrona,
il guadagno non è enorme.
Questo è un buon posto per utilizzare i
risultati 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 GQL è simile a quella di SQL. La sintassi GQL è descritta nella documentazione di 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 delle query. Per utilizzarla:
ndb.gql(querystring)
restituisce un oggettoQuery
(lo stesso tipo restituito daModel.query()
). Per questi oggettiQuery
sono disponibili tutti i metodi abituali:fetch()
,map_async()
,filter()
e così via.Model.gql(querystring)
è una forma abbreviata dindb.gql("SELECT * FROM Model " + querystring)
. In genere, querystring corrisponde a"WHERE prop1 > 0 AND prop2 = TRUE"
.- Per eseguire query su modelli contenenti proprietà strutturate, puoi usare
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 valori:
La chiamata della funzione
bind()
di una query restituisce una nuova query; la query originale non viene modificata. - 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 nome (ad es.
foo = StringProperty('bar')
) la query GQL deve utilizzare il nome della proprietà sostituita (nell'esempio,bar
).
Utilizza sempre la funzionalità di associazione di parametri se alcuni valori nella query sono variabili fornite dall'utente. In questo modo si evitano gli attacchi basati su attacchi sintattici.
È un errore eseguire una query per un modello che non è stato importato (o, più in generale, definito).
È un errore utilizzare un nome 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, il limite o l'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 sei abituato a SQL, fai attenzione ai falsi presupposti quando utilizzi GQL. GQL viene tradotto nell'API di query nativa di NDB. Questo è diverso da un tipico mappatore relazionale degli 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 di database. GQL non supporta le modifiche di Datastore (inserzioni, eliminazioni o aggiornamenti); supporta solo le query.