Datastore: [Parte 2] Queries and indexes
Queries
O conceito de queries no datastore não é diferente do que já estamos acostumados com bancos relacionais. O importante é entender as limitações.
Mas, conceitualmente, queries retornam "entidades" do datastore que atendam aos critérios especificados na consulta.
Uma query normalmente inclui:
- Uma "kind" (equivalente a uma tabela de banco relacional)
- Um ou mais "filters": filtros baseados em valores das propriedades, chaves ou ancestrais.
- Uma ou mais opções de ordenação ("sort")
Uma boa prática, com em qualquer banco de dados, é retornar o mínimo possível e necessário de informações, com o intuito de poupar recursos de memória e melhorar a performance.
A função dos Índices
Toda query do datastore utiliza um ou mais índices para ser processada. Atenção neste ponto. Esta é uma diferença fundamental, quando comparamos com bancos de dados relacionais, onde os índices tem a função primária de otimizar uma consulta (performance).
No datastore não. Os índices são parte do mecanismo de busca. Na verdade os índices são tabelas contendo entidades em um sequência especificada pelos índices ou ancestrais. Vamos falar mais sobre índices mais adiante.
Como utilizar queries com Python
A API Python do datastore permite utilizar queries de duas maneiras:
- Query: utiliza métodos para preparar a query. Veja um exemplo da documentação do Google:
class Person(db.Model):
first_name = db.StringProperty()
last_name = db.StringProperty()
city = db.StringProperty()
birth_year = db.IntegerProperty()
height = db.IntegerProperty()
# Query interface constructs a query using instance methods
q = Person.all()
q.filter("last_name =", "Smith")
q.filter("height <=", max_height)
q.order("-height")
# Query is not executed until results are accessed
for p in q.run(limit=5):
print "%s %s, %d inches tall" % (p.first_name, p.last_name, p.height)
- GqlQuery: utiliza uma sintaxe "SQL-Like". Veja o mesmo exemplo:
# GqlQuery interface constructs a query using a GQL query string
q = db.GqlQuery("SELECT * FROM Person " +
"WHERE last_name = :1 AND height <= :2 " +
"ORDER BY height DESC",
"Smith", max_height)
Observação: todas as propriedades filtradas devem ter um índice correspondente criado através do index.yaml.
Filtros
Um filtro pode ser aplicado sobre:
- properties
- Keys
- ancestors
Exemplo de filtro em cima de "Property":
q.filter("height <=", max_height)
Uma série de operadores podem ser utilizados (=, <, <=,>=, !=, IN).
Exemplo de filtro em cima de "Key":
q = Person.all()q.filter('__key__ >', last_seen_key)
Exemplo de filtro em cima de "Ancestor":
q = Person.all()
q.ancestor(ancestor_key)
Ordenação
Veja alguns exemplos da documentação do Google:# Order alphabetically by last name: q = Person.all() q.order('last_name') # Order by height, tallest to shortest: q = Person.all() q.order('-height')
Exemplo de múltiplos "sorts" (um é aplicado após o outro):
q = Person.all() q.order('lastName') q.order('-height')
O uso do hyphen ("-") é utilizado para denotar que a ordenação será descendente.
Restrições de queries
- Entidades sem uma determinada propriedade solicitada na query, será ignorada: como o datastore é um banco "schemaless" (sem esquema), nem todas as entidades terão a mesma estrutura. Se você fizer uma query solicitando uma determinada propriedade que não está em todas as entidades da "kind", somente serão retornadas as entidades que possuírem esta propriedade.
- Filtrar por propriedades não "indexadas" não irá retornar nada. Os índices são fundamentais para os filtros.
- Filtros do tipo "Inequality" (
<,<=,>,>=,!=) só funcionam para uma única propriedade:
SELECT * FROM Person WHERE birth_year >= :min_birth_yearAND birth_year <= :max_birth_year
SELECT * FROM Person WHERE birth_year >= :max_birth_yearAND height <= :max_height # ERROR
SELECT * FROM Person WHERE last_name = :target_last_nameAND city = :target_cityAND birth_year >= :min_birth_yearAND birth_year <= :max_birth_year
- Propriedades usadas em filtros de comparação (
<,<=,>,>=,!=) devem ser ordenados primeiro:
SELECT * FROM Person WHERE birth_year >= :min_birth_yearORDER BY birth_year, last_name
Query inválida:
SELECT * FROM Person WHERE birth_year >= :min_birth_year
ORDER BY last_name # ERROR
- Queries dentro de transações devem incluir o "ancestor": transações no datastore operam somente em entidades de um mesmo "entity group". Para atender a esta restrição, você deve incluir o ancestor no filtro.
Retornando resultados de uma query
Retornando uma única entidade (retornará a primeira ocorrência):q = Person.all()q.filter("last_name =", target_last_name)result = q.get()
Para retornar somente as chaves:
q = Person.all(keys_only=True)
Para limitar o retorno a 5 entidades (neste exemplo):
q = Person.all()q.order("-height")for p in q.run(limit=5):print "%s %s, %d inches tall" % (p.first_name, p.last_name, p.height)
Utilizando Cursor
Cursor, como em uma linguagem SQL tradicional, permite a uma query retornar um conjunto de registros do banco de dados, mantendo um ponteiro para o último resultado retornado. No mundo do datastore, este ponteiro é uma string base64, que pode ser armazenada no próprio datastore, no memcache ou "embedded" na própria página web. Esta string será passada no próximo request, para capturar o próximo conjundo de dados.Veja este exemplo em Python:
from google.appengine.api import memcachefrom google.appengine.ext import db# class Person(db.Model): ...# Start a query for all Person entitiespeople = Person.all()# If the application stored a cursor during a previous request, use itperson_cursor = memcache.get('person_cursor')if person_cursor:people.with_cursor(start_cursor=person_cursor)# Iterate over the resultsfor person in people:# Do something# Get updated cursor and store it for next timeperson_cursor = people.cursor()memcache.set('person_cursor', person_cursor)
Consistência de Dados
No datastore as queries podem ser de 2 tipos:
- Strongly consistent:consistência forte, ou seja, existe a garantia que os dados são "quentes".
- Eventually consistent: queries rodam mais rápido, porém não existe garantia de consistência dos dados.
Queries que utilizam o "ancestor" (chave-pai) são "strongly consistent" por default. Sem o acestor as queries são sempre eventualmente consistentes.
A utilização de modelos strongly ou eventually dependem da natureza da aplicação que está sendo desenvolvida.
Comentários
Postar um comentário