Consultas al almacén de datos

Una consulta de Datastore obtiene entidades de Cloud Datastore que cumplen un conjunto de condiciones especificado.

Una consulta típica incluye lo siguiente:

  • Un tipo de entidad al que se aplica la consulta
  • Filtros opcionales basados en los valores de las propiedades, las claves y los antecesores de las entidades
  • Opcional: ordenar pedidos para secuenciar los resultados
Cuando se ejecuta, una consulta obtiene todas las entidades del tipo indicado que cumplen todos los filtros especificados, ordenadas según el orden indicado. Las consultas se ejecutan como de solo lectura.

En esta página se describe la estructura y los tipos de consultas que se usan en App Engine para obtener datos de Cloud Datastore.

Filtros

Los filtros de una consulta definen las restricciones de las propiedades, las claves y los ancestros de las entidades que se van a recuperar.

Filtros de propiedad

Un filtro de propiedad especifica

  • Nombre de una propiedad
  • Un operador de comparación
  • Un valor de propiedad
Por ejemplo:

Filter propertyFilter =
    new FilterPredicate("height", FilterOperator.GREATER_THAN_OR_EQUAL, minHeight);
Query q = new Query("Person").setFilter(propertyFilter);

La aplicación debe proporcionar el valor de la propiedad. No puede hacer referencia a otras propiedades ni calcularse en función de ellas. Una entidad cumple las condiciones del filtro si tiene una propiedad con el nombre indicado cuyo valor se compara con el valor especificado en el filtro de la forma descrita por el operador de comparación.

El operador de comparación puede ser cualquiera de los siguientes (definidos como constantes enumeradas en la clase anidada Query.FilterOperator):

Operador Significado
EQUAL Igual a
LESS_THAN Inferior a
LESS_THAN_OR_EQUAL Inferior o igual a
GREATER_THAN Superior a
GREATER_THAN_OR_EQUAL Superior o igual a
NOT_EQUAL No igual a
IN Miembro de (igual a cualquiera de los valores de una lista especificada)

El operador NOT_EQUAL realiza dos consultas: una en la que todos los demás filtros no cambian y el filtro NOT_EQUAL se sustituye por un filtro LESS_THAN y otra en la que se sustituye por un filtro GREATER_THAN. Después, los resultados se combinan en orden. Una consulta no puede tener más de un NOT_EQUAL filtro, y una consulta que tenga uno no puede tener ningún otro filtro de desigualdad.

El operador IN también realiza varias consultas: una por cada elemento de la lista especificada, con el resto de los filtros sin cambios y el filtro IN sustituido por un filtro EQUAL. EQUAL Los resultados se combinan en el orden de los elementos de la lista. Si una consulta tiene más de un filtro IN, se realiza como varias consultas, una por cada combinación de valores posible en las listas IN.

Una sola consulta que contenga operadores NOT_EQUAL o IN puede tener un máximo de 30 subconsultas.

Para obtener más información sobre cómo se traducen las consultas NOT_EQUAL y IN en varias consultas en un framework JDO o JPA, consulta el artículo Consultas con filtros != e IN.

Filtros clave

Para filtrar por el valor de la clave de una entidad, usa la propiedad especial Entity.KEY_RESERVED_PROPERTY:

Filter keyFilter =
    new FilterPredicate(Entity.KEY_RESERVED_PROPERTY, FilterOperator.GREATER_THAN, lastSeenKey);
Query q = new Query("Person").setFilter(keyFilter);

También se admiten las ordenaciones ascendentes en Entity.KEY_RESERVED_PROPERTY.

Al comparar desigualdades, las claves se ordenan según los siguientes criterios:

  1. Ruta de ancestro
  2. Tipo de entidad
  3. Identificador (nombre de clave o ID numérico)

Los elementos de la ruta de ancestros se comparan de forma similar: por tipo (cadena) y, a continuación, por nombre de clave o ID numérico. Los tipos y los nombres de clave son cadenas y se ordenan por valor de byte. Los IDs numéricos son números enteros y se ordenan numéricamente. Si las entidades con el mismo elemento superior y tipo usan una combinación de cadenas de nombres de clave e IDs numéricos, las que tienen IDs numéricos preceden a las que tienen nombres de clave.

Las consultas en claves usan índices al igual que las consultas en propiedades y requieren índices personalizados en los mismos casos, con un par de excepciones: los filtros de desigualdad o un orden de clasificación ascendente en la clave no requieren un índice personalizado, pero sí un orden de clasificación descendente en la clave. Al igual que con todas las consultas, el servidor web de desarrollo crea las entradas adecuadas en el archivo de configuración del índice cuando se prueba una consulta que necesita un índice personalizado.

Filtros de ancestros

Puede filtrar sus consultas de Datastore por un antepasado específico para que los resultados devueltos incluyan solo las entidades que desciendan de ese antepasado:

Query q = new Query("Person").setAncestor(ancestorKey);

Tipos de consultas especiales

Merecen una mención especial algunos tipos de consultas concretos:

Consultas independientes del tipo

Una consulta sin tipo y sin filtro de ancestro recupera todas las entidades de una aplicación de Datastore. Esto incluye las entidades creadas y gestionadas por otras funciones de App Engine, como las entidades de estadísticas y las entidades de metadatos de Blobstore (si las hay). Estas consultas sin tipo no pueden incluir filtros ni criterios de ordenación en los valores de las propiedades. Sin embargo, pueden filtrar por claves de entidad especificando Entity.KEY_RESERVED_PROPERTY como nombre de propiedad:

Filter keyFilter =
    new FilterPredicate(Entity.KEY_RESERVED_PROPERTY, FilterOperator.GREATER_THAN, lastSeenKey);
Query q = new Query().setFilter(keyFilter);

Consultas de ancestros

Una consulta con un filtro de ancestro limita sus resultados a la entidad especificada y a sus descendientes:

DatastoreService datastore = DatastoreServiceFactory.getDatastoreService();

Entity tom = new Entity("Person", "Tom");
Key tomKey = tom.getKey();
datastore.put(tom);

Entity weddingPhoto = new Entity("Photo", tomKey);
weddingPhoto.setProperty("imageURL", "http://domain.com/some/path/to/wedding_photo.jpg");

Entity babyPhoto = new Entity("Photo", tomKey);
babyPhoto.setProperty("imageURL", "http://domain.com/some/path/to/baby_photo.jpg");

Entity dancePhoto = new Entity("Photo", tomKey);
dancePhoto.setProperty("imageURL", "http://domain.com/some/path/to/dance_photo.jpg");

Entity campingPhoto = new Entity("Photo");
campingPhoto.setProperty("imageURL", "http://domain.com/some/path/to/camping_photo.jpg");

List<Entity> photoList = Arrays.asList(weddingPhoto, babyPhoto, dancePhoto, campingPhoto);
datastore.put(photoList);

Query photoQuery = new Query("Photo").setAncestor(tomKey);

// This returns weddingPhoto, babyPhoto, and dancePhoto,
// but not campingPhoto, because tom is not an ancestor
List<Entity> results =
    datastore.prepare(photoQuery).asList(FetchOptions.Builder.withDefaults());

Consultas de ancestros independientes del tipo

Una consulta sin tipo que incluya un filtro de ancestro recuperará el ancestro especificado y todos sus descendientes, independientemente del tipo. Este tipo de consulta no requiere índices personalizados. Al igual que todas las consultas sin tipo, no puede incluir filtros ni criterios de ordenación en los valores de las propiedades, pero sí puede filtrar por la clave de la entidad:

Filter keyFilter =
    new FilterPredicate(Entity.KEY_RESERVED_PROPERTY, FilterOperator.GREATER_THAN, lastSeenKey);
Query q = new Query().setAncestor(ancestorKey).setFilter(keyFilter);

En el siguiente ejemplo se muestra cómo obtener todas las entidades que descienden de un ancestro determinado:

DatastoreService datastore = DatastoreServiceFactory.getDatastoreService();

Entity tom = new Entity("Person", "Tom");
Key tomKey = tom.getKey();
datastore.put(tom);

Entity weddingPhoto = new Entity("Photo", tomKey);
weddingPhoto.setProperty("imageURL", "http://domain.com/some/path/to/wedding_photo.jpg");

Entity weddingVideo = new Entity("Video", tomKey);
weddingVideo.setProperty("videoURL", "http://domain.com/some/path/to/wedding_video.avi");

List<Entity> mediaList = Arrays.asList(weddingPhoto, weddingVideo);
datastore.put(mediaList);

// By default, ancestor queries include the specified ancestor itself.
// The following filter excludes the ancestor from the query results.
Filter keyFilter =
    new FilterPredicate(Entity.KEY_RESERVED_PROPERTY, FilterOperator.GREATER_THAN, tomKey);

Query mediaQuery = new Query().setAncestor(tomKey).setFilter(keyFilter);

// Returns both weddingPhoto and weddingVideo,
// even though they are of different entity kinds
List<Entity> results =
    datastore.prepare(mediaQuery).asList(FetchOptions.Builder.withDefaults());

Consultas para obtener solo las claves

Una consulta de solo claves devuelve solo las claves de las entidades de resultado en lugar de las entidades en sí, con una latencia y un coste inferiores a los de la recuperación de entidades completas:

Query q = new Query("Person").setKeysOnly();

A menudo es más económico hacer primero una consulta solo de claves y, a continuación, obtener un subconjunto de entidades de los resultados, en lugar de ejecutar una consulta general que puede obtener más entidades de las que realmente necesitas.

Consultas de proyección

A veces, lo único que necesitas de los resultados de una consulta son los valores de algunas propiedades específicas. En estos casos, puedes usar una consulta de proyección para obtener solo las propiedades que te interesen, con una latencia y un coste inferiores a los de obtener toda la entidad. Consulta la página Consultas de proyección para obtener más información.

Ordenar pedidos

El orden de clasificación de una consulta especifica

  • Nombre de una propiedad
  • El orden de clasificación (ascendente o descendente)

Por ejemplo:

// Order alphabetically by last name:
Query q1 = new Query("Person").addSort("lastName", SortDirection.ASCENDING);

// Order by height, tallest to shortest:
Query q2 = new Query("Person").addSort("height", SortDirection.DESCENDING);

Si una consulta incluye varios criterios de ordenación, se aplican en la secuencia especificada. En el ejemplo siguiente, se ordena primero por apellido en orden ascendente y, después, por altura en orden descendente:

Query q =
    new Query("Person")
        .addSort("lastName", SortDirection.ASCENDING)
        .addSort("height", SortDirection.DESCENDING);

Si no se especifica ningún orden, los resultados se devuelven en el orden en que se recuperan de Datastore.

Nota: Debido a la forma en que Datastore ejecuta las consultas, si una consulta especifica filtros de desigualdad en una propiedad y criterios de ordenación en otras propiedades, la propiedad utilizada en los filtros de desigualdad debe ordenarse antes que las demás propiedades.

Índices

Cada consulta de Datastore calcula sus resultados mediante uno o varios índices,que contienen claves de entidad en una secuencia especificada por las propiedades del índice y, opcionalmente, los ancestros de la entidad. Los índices se actualizan de forma incremental para reflejar los cambios que la aplicación haga en sus entidades, de modo que los resultados correctos de todas las consultas estén disponibles sin necesidad de realizar más cálculos.

App Engine predefine un índice simple en cada propiedad de una entidad. Una aplicación de App Engine puede definir más índices personalizados en un archivo de configuración de índices llamado datastore-indexes.xml, que se genera en el directorio /war/WEB-INF/appengine-generated de la aplicación. El servidor de desarrollo añade automáticamente sugerencias a este archivo cuando detecta consultas que no se pueden ejecutar con los índices actuales. Puedes ajustar estos índices manualmente modificando el archivo antes de subir la aplicación.

Ejemplo de interfaz de consulta

La API de Datastore de Java de nivel inferior proporciona la clase Query para crear consultas y la interfaz PreparedQuery para recuperar entidades de Datastore:

DatastoreService datastore = DatastoreServiceFactory.getDatastoreService();

Filter heightMinFilter =
    new FilterPredicate("height", FilterOperator.GREATER_THAN_OR_EQUAL, minHeight);

Filter heightMaxFilter =
    new FilterPredicate("height", FilterOperator.LESS_THAN_OR_EQUAL, maxHeight);

// Use CompositeFilter to combine multiple filters
CompositeFilter heightRangeFilter =
    CompositeFilterOperator.and(heightMinFilter, heightMaxFilter);

// Use class Query to assemble a query
Query q = new Query("Person").setFilter(heightRangeFilter);

// Use PreparedQuery interface to retrieve results
PreparedQuery pq = datastore.prepare(q);

for (Entity result : pq.asIterable()) {
  String firstName = (String) result.getProperty("firstName");
  String lastName = (String) result.getProperty("lastName");
  Long height = (Long) result.getProperty("height");

  out.println(firstName + " " + lastName + ", " + height + " inches tall");
}

Fíjate en el uso de FilterPredicate y CompositeFilter para crear filtros. Si solo vas a aplicar un filtro a una consulta, puedes usar FilterPredicate por sí solo:

Filter heightMinFilter =
    new FilterPredicate("height", FilterOperator.GREATER_THAN_OR_EQUAL, minHeight);

Query q = new Query("Person").setFilter(heightMinFilter);

Sin embargo, si quieres definir más de un filtro en una consulta, debes usar CompositeFilter, que requiere al menos dos filtros. En el ejemplo anterior se usa la clase de ayuda de acceso directo CompositeFilterOperator.and. En el siguiente ejemplo se muestra una forma de crear un filtro OR compuesto:

Filter tooShortFilter = new FilterPredicate("height", FilterOperator.LESS_THAN, minHeight);

Filter tooTallFilter = new FilterPredicate("height", FilterOperator.GREATER_THAN, maxHeight);

Filter heightOutOfRangeFilter = CompositeFilterOperator.or(tooShortFilter, tooTallFilter);

Query q = new Query("Person").setFilter(heightOutOfRangeFilter);

Siguientes pasos