Uma aplicação pode usar consultas para pesquisar no Datastore entidades que correspondam a critérios de pesquisa específicos denominados filtros.
Vista geral
Uma aplicação pode usar consultas para pesquisar no Datastore entidades que correspondam a critérios de pesquisa específicos denominados filtros. Por exemplo, uma aplicação que monitoriza vários livros de visitas pode usar uma consulta para obter mensagens de um livro de visitas, ordenadas por data:
...
...
Algumas consultas são mais complexas do que outras. O arquivo de dados precisa de índices pré-criados para estas.
Estes índices pré-criados são especificados num ficheiro de configuração,
index.yaml
.
No servidor de programação, se executar uma consulta que precise de um índice que não especificou, o servidor de programação adiciona-o automaticamente ao respetivo index.yaml
.
No entanto, no seu Website, uma consulta que precisa de um índice ainda não especificado falha.
Assim, o ciclo de desenvolvimento típico consiste em experimentar uma nova consulta no servidor de desenvolvimento e, em seguida, atualizar o Website para usar o index.yaml
alterado automaticamente.
Pode atualizar o index.yaml
separadamente do carregamento da aplicação executando
gcloud app deploy index.yaml
.
Se o seu arquivo de dados tiver muitas entidades, demora muito tempo a criar um novo índice para as mesmas. Neste caso, é aconselhável atualizar as definições de índice antes de carregar código que use o novo índice.
Pode usar a consola de administração para saber quando os índices
terminaram de ser criados.
O App Engine Datastore suporta nativamente filtros para:
Correspondências exatas (o operador ==) e
Comparações (os operadores <, <=, > e >=).
Suporta a combinação de vários filtros através de uma operação booleana
AND
, com algumas limitações (veja abaixo).
Além dos operadores nativos,
a API suporta o operador !=
,
a combinação de grupos de filtros através da operação booleana OR
,
e a operação IN
,
que testa a igualdade a um de uma lista de valores possíveis
(como o operador "in" do Python).
Estas operações não são mapeadas 1:1 para as operações nativas do Datastore. Por isso, são um pouco estranhas e lentas, relativamente.
São implementadas através da união na memória de streams de resultados. Tenha em atenção que p != v
é
implementado como "p < v OR p > v".
(Isto é importante para
propriedades repetidas.)
Limitações: o Datastore aplica algumas restrições às consultas. A violação destas regras faz com que sejam geradas exceções. Por exemplo, a combinação de demasiados filtros, a utilização de desigualdades para várias propriedades ou a combinação de uma desigualdade com uma ordem de classificação numa propriedade diferente estão atualmente proibidas. Além disso, os filtros que fazem referência a várias propriedades requerem, por vezes, a configuração de índices secundários.
Não suportado: o Datastore não suporta diretamente correspondências de subcadeias de carateres, correspondências não sensíveis a maiúsculas e minúsculas nem a chamada pesquisa de texto integral. Existem formas de implementar correspondências não sensíveis a maiúsculas e minúsculas e até mesmo uma pesquisa de texto completo usando propriedades calculadas.
Filtrar por valores de propriedades
Recorde a classe Account de NDB Properties:
Normalmente, não quer obter todas as entidades de um determinado tipo; quer apenas as que têm um valor específico ou um intervalo de valores para alguma propriedade.
Os objetos de propriedade sobrecarregam alguns operadores para devolver expressões de filtro que podem ser usadas para controlar uma consulta: por exemplo, para encontrar todas as entidades Account cuja propriedade userid tem o valor exato 42, pode usar a expressão
(Se tiver a certeza de que existiu apenas um Account
com esse userid
, pode preferir usar userid
como chave.
Account.get_by_id(...)
é mais rápido do que Account.query(...).get()
.)
O NDB suporta as seguintes operações:
property == value
property < value
property <= value
property > value
property >= value
property != value
property.IN([value1, value2])
Para filtrar uma desigualdade, pode usar uma sintaxe como a seguinte:
Esta consulta encontra todas as entidades Account cuja propriedade userid
é
superior ou igual a 40.
Duas destas operações, != e IN, são implementadas como combinações das outras e são um pouco peculiares, conforme descrito em != e IN.
Pode especificar vários filtros:
Esta função combina os argumentos de filtro especificados, devolvendo todas as entidades Account cujo valor de userid é superior ou igual a 40 e inferior a 50.
Nota: Conforme mencionado anteriormente, o Datastore rejeita consultas que usam a filtragem de desigualdade em mais de uma propriedade.
Em vez de especificar um filtro de consulta completo numa única expressão, pode ser mais conveniente criá-lo passo a passo. Por exemplo:
query3
é equivalente à variável query
do exemplo anterior. Tenha em atenção que os objetos de consulta são imutáveis, pelo que a construção de query2
não afeta query1
e a construção de query3
não afeta query1
nem query2
.
As operações != e IN
Recupere a classe Article de NDB Properties:
As operações !=
(diferente de) e IN
(pertence)
são implementadas combinando outros filtros através da operação OR
. O primeiro destes,
property != value
é implementado como
(property < value) OR (property > value)
Por exemplo,
é equivalente a
Nota:
Surpreendentemente, esta consulta não pesquisa entidades
Article
que não incluem "perl" como etiqueta!
Em vez disso, encontra todas as entidades com, pelo menos, uma etiqueta diferente de "perl".
Por exemplo, a seguinte entidade seria incluída nos resultados,
apesar de ter "perl" como uma das respetivas etiquetas:
No entanto, este não seria incluído:
Não existe forma de consultar entidades que não incluam uma etiqueta igual a "perl".
Da mesma forma, a operação IN
property IN [value1, value2, ...]
que testa a associação numa lista de possíveis valores, é implementado como
(property == value1) OR (property == value2) OR ...
Por exemplo,
é equivalente a
Nota:
As consultas que usam OR
removem duplicados dos respetivos resultados: o fluxo de resultados não
inclui a entidade mais do que uma vez, mesmo que uma entidade corresponda a duas ou mais subconsultas.
Consultar propriedades repetidas
A classe Article
definida na secção anterior também serve como exemplo de consulta de propriedades repetidas. Em particular, um filtro
como
usa um único valor, embora Article.tags
seja uma propriedade repetida. Não pode comparar propriedades repetidas com objetos de lista (o Datastore não o compreende) e um filtro como
faz algo completamente diferente de pesquisar entidades Article
cujo valor das etiquetas é a lista ['python', 'ruby', 'php']
: pesquisa entidades cujo valor tags
(considerado uma lista) contém pelo menos um desses valores.
A consulta de um valor de None
numa propriedade repetida tem um comportamento indefinido. Não o faça.
Combinar operações AND e OR
Pode aninhar operações AND
e OR
arbitrariamente.
Por exemplo:
No entanto, devido à implementação do OR
, uma consulta deste formulário que seja
demasiado complexa pode falhar com uma exceção. É mais seguro normalizar estes filtros para que exista (no máximo) uma única operação OR
na parte superior da árvore de expressões e um único nível de operações AND
abaixo.
Para realizar esta normalização, tem de se lembrar das regras de lógica booleana e de como os filtros !=
e IN
são efetivamente implementados:
- Expanda os operadores
!=
eIN
para a respetiva forma primitiva, em que!=
torna-se uma verificação da propriedade que é < ou > do que o valor eIN
torna-se uma verificação da propriedade que é == ao primeiro valor ou ao segundo valor ou...até ao último valor na lista. - Um
AND
com umOR
no interior é equivalente a umOR
de váriosAND
s aplicados aos operandosAND
originais, com um único operandoOR
substituído peloOR
original. Por exemplo,AND(a, b, OR(c, d))
é equivalente aOR(AND(a, b, c), AND(a, b, d))
- Um
AND
que tenha um operando que seja, em si mesmo, uma operaçãoAND
pode incorporar os operandos doAND
aninhado noAND
envolvente. Por exemplo,AND(a, b, AND(c, d))
é equivalente aAND(a, b, c, d)
- Um
OR
que tenha um operando que seja, em si mesmo, uma operaçãoOR
pode incorporar os operandos doOR
aninhado noOR
de inclusão. Por exemplo,OR(a, b, OR(c, d))
é equivalente aOR(a, b, c, d)
Se aplicarmos estas transformações em fases ao filtro de exemplo, usando uma notação mais simples do que a linguagem Python, obtemos:
- Usando a regra n.º 1 nos operadores
IN
e!=
:AND(tags == 'python', OR(tags == 'ruby', tags == 'jruby', AND(tags == 'php', OR(tags < 'perl', tags > 'perl'))))
- Usar a regra n.º 2 no elemento
OR
mais interior aninhado num elementoAND
:AND(tags == 'python', OR(tags == 'ruby', tags == 'jruby', OR(AND(tags == 'php', tags < 'perl'), AND(tags == 'php', tags > 'perl'))))
- Usar a regra n.º 4 no elemento
OR
aninhado noutro elementoOR
:AND(tags == 'python', OR(tags == 'ruby', tags == 'jruby', AND(tags == 'php', tags < 'perl'), AND(tags == 'php', tags > 'perl')))
- Usar a regra n.º 2 nos restantes
OR
aninhados numAND
: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')))
- Usar a regra n.º 3 para reduzir os restantes
AND
s aninhados: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'))
Atenção:
para alguns filtros, esta normalização pode causar uma explosão combinatória. Considere o AND
de 3 OR
cláusulas com 2 cláusulas básicas cada.
Quando normalizado, isto torna-se um OR
de 8 AND
cláusulas com 3 cláusulas básicas cada: ou seja, 6 termos tornam-se 24.
Especificar ordens de ordenação
Pode usar o método order()
para especificar a ordem em que uma consulta devolve os respetivos resultados. Este método recebe uma lista de argumentos, cada um dos quais é um objeto de propriedade (a ser ordenado por ordem ascendente) ou a sua negação (que indica a ordem descendente). Por exemplo:
Isto obtém todas as entidades Greeting
, ordenadas pelo valor ascendente da respetiva propriedade content
.
As execuções de entidades consecutivas com a mesma propriedade de conteúdo são ordenadas pelo valor descendente da respetiva propriedade date
.
Pode usar várias chamadas order()
para o mesmo efeito:
Nota: quando combina filtros com order()
, o Datastore rejeita determinadas combinações.
Em particular, quando usa um filtro de desigualdade, a primeira ordem de ordenação (se existir) tem de especificar a mesma propriedade que o filtro.
Além disso, por vezes, tem de configurar um índice secundário.
Consultas predecessoras
As consultas de antecessores permitem-lhe fazer consultas fortemente consistentes ao arquivo de dados. No entanto, as entidades com o mesmo antecessor estão limitadas a 1 gravação por segundo. Segue-se uma comparação simples das concessões e da estrutura entre uma consulta de antepassados e não antepassados que usa clientes e as respetivas compras associadas no arquivo de dados.
No exemplo não ancestral seguinte, existe uma entidade no arquivo de dados para cada Customer
e uma entidade no arquivo de dados para cada Purchase
, com um KeyProperty
que aponta para o cliente.
Para encontrar todas as compras que pertencem ao cliente, pode usar a seguinte consulta:
Neste caso, o arquivo de dados oferece um elevado débito de gravação, mas apenas consistência eventual. Se foi adicionada uma nova compra, pode receber dados desatualizados. Pode eliminar este comportamento através de consultas de antecessores.
Para clientes e compras com consultas de antepassados, continua a ter a mesma estrutura com duas entidades separadas. A parte do cliente é a mesma. No entanto, quando cria compras, já não precisa de especificar o KeyProperty()
para compras. Isto acontece porque, quando usa consultas de hierarquia, chama a chave da entidade de cliente quando cria uma entidade de compra.
Cada compra tem uma chave, e o cliente também tem a sua própria chave. No entanto, cada chave de compra tem a chave da entidade de cliente incorporada. Lembre-se de que isto está limitado a uma gravação por antepassado por segundo. O seguinte cria uma entidade com um antecessor:
Para consultar as compras de um determinado cliente, use a seguinte consulta.
Atributos de consulta
Os objetos de consulta têm os seguintes atributos de dados só de leitura:
Atributo | Tipo | Predefinição | Descrição |
---|---|---|---|
gentil | str | None | Nome do tipo (normalmente, o nome da classe) |
predecessor | Key | None | Predecessor especificado para consulta |
filtros | FilterNode | None | Expressão de filtro |
encomendas | Order | None | Ordene encomendas |
A impressão de um objeto de consulta (ou a chamada de str()
ou repr()
no mesmo) produz uma representação de string com formatação adequada:
Filtragem de valores de propriedades estruturadas
Uma consulta pode filtrar diretamente os valores dos campos de propriedades estruturadas.
Por exemplo, uma consulta para todos os contactos com uma morada cuja cidade seja
'Amsterdam'
teria o seguinte aspeto
Se combinar vários filtros deste tipo, os filtros podem corresponder a subentidades diferentes Address
na mesma entidade de contacto.
Por exemplo:
pode encontrar contactos com uma morada cuja cidade seja 'Amsterdam'
e outra morada (diferente) cuja rua seja 'Spear St'
. No entanto, pelo menos para filtros de igualdade, pode
criar uma consulta que devolva apenas resultados com vários valores numa
única subentidade:
Se usar esta técnica, as propriedades da subentidade
iguais a None
são ignoradas na consulta.
Se uma propriedade tiver um valor predefinido, tem de a definir explicitamente como
None
para a ignorar na consulta. Caso contrário, a consulta inclui
um filtro que exige que o valor da propriedade seja igual ao predefinido.
Por exemplo, se o modelo Address
tiver uma propriedade
country
com default='us'
, o exemplo
acima só devolve contactos com o país igual a
'us'
. Para considerar contactos com outros valores de país,
tem de filtrar por
Address(city='San Francisco', street='Spear St',
country=None)
.
Se uma subentidade tiver valores de propriedade iguais a None
,
são ignorados. Assim, não faz sentido filtrar por um valor da propriedade da subentidade de None
.
Usar propriedades com nome por string
Por vezes, quer filtrar ou ordenar uma consulta com base numa propriedade cujo nome é especificado por uma string. Por exemplo, se permitir que o utilizador introduza consultas de pesquisa
como tags:python
, seria conveniente transformar
isso numa consulta como
Article.query(Article."tags" == "python") # does NOT work
Se o seu modelo for um
Expando
, o seu filtro pode usar GenericProperty
, a classe Expando
usa para propriedades dinâmicas:
A utilização de GenericProperty
também funciona se o seu modelo não for um Expando
, mas se quiser garantir que está a usar apenas nomes de propriedades definidos, também pode usar o atributo de classe _properties
ou use getattr()
para o obter da turma:
A diferença é que getattr()
usa o "nome Python" da propriedade, enquanto _properties
é indexado pelo "nome da base de dados" da propriedade. Estas diferem apenas quando a propriedade foi declarada com algo como
Aqui, o nome do Python é title
, mas o nome do arquivo de dados é
t
.
Estas abordagens também funcionam para ordenar os resultados da consulta:
Iteradores de consultas
Enquanto uma consulta está em curso, o respetivo estado é mantido num objeto iterador. (A maioria das aplicações não as usa diretamente; normalmente, é mais simples chamar fetch(20)
do que manipular o objeto iterador.)
Existem duas formas básicas de obter um objeto deste tipo:
- usando a função
iter()
integrada do Python num objetoQuery
- chamar o método
iter()
do objetoQuery
O primeiro suporta a utilização de um ciclo for
Python
(que chama implicitamente a função iter()
)
para percorrer uma consulta.
A segunda forma, através do método Query
do objeto iter()
, permite-lhe transmitir opções ao iterador para afetar o respetivo comportamento. Por exemplo, para usar uma consulta apenas de chaves num ciclo for
, pode escrever o seguinte:
Os iteradores de consultas têm outros métodos úteis:
Método | Descrição |
---|---|
__iter__()
| Parte do protocolo de iteradores do Python. |
next()
| Devolve o resultado seguinte
ou gera a exceção StopIteration se não existir nenhum. |
has_next()
| Devolve True se uma chamada next()
subsequente devolver um resultado, False se gerar
StopIteration .Bloqueia até que a resposta a esta pergunta seja conhecida e armazena o resultado em buffer (se existir) até o obter com next() .
|
probably_has_next()
| Semelhante ao has_next() , mas usa um atalho mais rápido
(e, por vezes, impreciso).Pode devolver um falso positivo ( True quando
next() geraria realmente StopIteration ),
mas nunca um falso negativo (False quando
next() devolveria realmente um resultado).
|
cursor_before()
| Devolve um cursor de consulta que representa um ponto imediatamente antes do
último resultado devolvido. Gera uma exceção se não estiver disponível nenhum cursor (em particular, se a opção de consulta produce_cursors não tiver sido transmitida).
|
cursor_after()
| Devolve um cursor de consulta que representa um ponto imediatamente após o último resultado devolvido. Gera uma exceção se não estiver disponível nenhum cursor (em particular, se a opção de consulta produce_cursors não tiver sido transmitida).
|
index_list()
| Devolve uma lista de índices usados por uma consulta executada, incluindo índices principais, compostos, de tipo e de propriedade única. |
Cursores de consulta
Um cursor de consulta é uma pequena estrutura de dados opaca que representa um ponto de retoma numa consulta. Isto é útil
para mostrar ao utilizador uma página de resultados de cada vez. Também é
útil para processar tarefas longas que podem ter de ser interrompidas e retomadas.
Uma forma típica de os usar é com o método fetch_page()
de uma consulta.
Funciona de forma semelhante a fetch()
, mas devolve um triplo
(results, cursor, more)
.
O indicador more
devolvido indica que existem provavelmente mais resultados. Uma IU pode usar isto, por exemplo, para suprimir um botão ou um link "Página seguinte".
Para pedir páginas subsequentes, transmita o cursor devolvido por uma chamada fetch_page()
para a seguinte. É gerado um BadArgumentError
se transmitir um cursor inválido. Tenha em atenção que a validação apenas verifica se o valor está codificado em base64. Tem de fazer qualquer validação adicional necessária.
Assim, para permitir que o utilizador veja todas as entidades que correspondem a uma consulta, obtendo-as uma página de cada vez, o seu código pode ter o seguinte aspeto:
...
Tenha em atenção a utilização de urlsafe()
e
Cursor(urlsafe=s)
para serializar e
desserializar o cursor.
Isto permite-lhe transmitir um cursor a um cliente na Web na resposta a um pedido e recebê-lo de volta do cliente num pedido posterior.
Nota:
Normalmente, o método fetch_page()
devolve um cursor mesmo
que não existam mais resultados, mas isto não é garantido: o valor
do cursor devolvido pode ser None
. Tenha também em atenção que, como a flag
more
é implementada através do método
probably_has_next()
do iterador, em circunstâncias raras, pode
devolver True
, mesmo que a página seguinte esteja vazia.
Algumas consultas NDB não suportam cursores de consulta, mas pode corrigi-las.
Se uma consulta usar
IN
, OR
ou !=
,
os resultados da consulta
não funcionam com cursores a menos que sejam ordenados por chave.
Se uma aplicação não ordenar os resultados por chave e chamar
fetch_page()
, recebe um BadArgumentError
.
Se
User.query(User.name.IN(['Joe', 'Jane'])).order(User.name).fetch_page(N)
receber um erro, altere-o para
User.query(User.name.IN(['Joe', 'Jane'])).order(User.name, User.key).fetch_page(N)
Em vez de percorrer os resultados da consulta, pode usar o método iter()
de uma consulta para obter um cursor num ponto preciso.
Para o fazer, passe produce_cursors=True
para iter()
;
quando o iterador estiver no local certo, chame o respetivo cursor_after()
para obter um cursor que esteja imediatamente a seguir. (Em alternativa, ligue
cursor_before()
para um cursor imediatamente antes.)
Tenha em atenção que chamar cursor_after()
ou cursor_before()
pode fazer uma chamada de bloqueio do Datastore, executando novamente parte da consulta para extrair um cursor que aponta para o meio de um lote.
Para usar um cursor para retroceder nas páginas dos resultados da consulta, crie uma consulta inversa:
Chamar uma função para cada entidade ("Mapeamento")
Suponhamos que precisa de obter as entidades Account
correspondentes às entidades Message
devolvidas por uma consulta.
Pode escrever algo como:
No entanto, isto é bastante ineficiente: aguarda a obtenção de uma entidade e, em seguida, usa a entidade; aguarda a próxima entidade e usa a entidade. Existe muito tempo de espera. Outra forma é escrever uma função de chamada de retorno mapeada sobre os resultados da consulta:
Esta versão é executada um pouco mais rapidamente do que o ciclo simples for
acima, porque é possível alguma concorrência.
No entanto, uma vez que a chamada get()
em callback()
continua a ser síncrona, o ganho não é enorme.
Este é um bom local para usar obtenções assíncronas.
GQL
O GQL é uma linguagem semelhante a SQL para obter entidades ou chaves do App Engine Datastore. Embora as funcionalidades do GQL sejam diferentes das de uma linguagem de consulta para uma base de dados relacional tradicional, a sintaxe do GQL é semelhante à do SQL. A sintaxe GQL é descrita na Referência GQL.
Pode usar a GQL para criar consultas. Isto é semelhante à criação de uma consulta
com Model.query()
, mas usa a sintaxe GQL para definir
o filtro e a ordem da consulta. Para utilizar a aplicação:
ndb.gql(querystring)
devolve um objetoQuery
(o mesmo tipo que o devolvido porModel.query()
). Todos os métodos habituais estão disponíveis nesses objetosQuery
:fetch()
,map_async()
,filter()
, etc.Model.gql(querystring)
é uma abreviatura dendb.gql("SELECT * FROM Model " + querystring)
. Normalmente, querystring é algo como"WHERE prop1 > 0 AND prop2 = TRUE"
.- Para consultar modelos que contêm propriedades estruturadas, pode usar
foo.bar
na sintaxe GQL para fazer referência a subpropriedades. - O GQL suporta associações de parâmetros semelhantes a SQL. Uma aplicação pode definir uma consulta e, em seguida, associar-lhe valores:
Chamar a função
bind()
de uma consulta devolve uma nova consulta; não altera a original. - Se a classe do modelo substituir o método de classe
_get_kind()
, a sua consulta GQL deve usar o tipo devolvido por essa função e não o nome da classe. - Se uma propriedade no seu modelo substituir o respetivo nome (por exemplo,
foo = StringProperty('bar')
) a consulta GQL deve usar o nome da propriedade substituído (no exemplo,bar
).
Use sempre a funcionalidade de associação de parâmetros se alguns valores na sua consulta forem variáveis fornecidas pelo utilizador. Isto evita ataques baseados em hacks sintáticos.
É um erro consultar um modelo que não foi importado (ou, mais geralmente, definido).
É um erro usar um nome de propriedade que não esteja definido pela classe de modelo, a menos que esse modelo seja um Expando.
A especificação de um limite ou um desvio para o fetch()
da consulta substitui o limite ou o desvio definido pelas cláusulas OFFSET
e LIMIT
do GQL. Não combine OFFSET
e LIMIT
do GQL com fetch_page()
Tenha em atenção que o máximo de 1000 resultados imposto pelo App Engine nas consultas aplica-se ao deslocamento e ao limite.
Se estiver habituado ao SQL, tenha cuidado com as falsas suposições quando usar o GQL. O GQL é traduzido para a API de consulta nativa do NDB. Isto é diferente de um mapeador relacional de objetos típico (como o SQLAlchemy ou o suporte de base de dados do Django), em que as chamadas API são traduzidas em SQL antes de serem transmitidas para o servidor de base de dados. O GQL não suporta modificações do Datastore (inserções, eliminações ou atualizações); apenas suporta consultas.