Consultas de Datastore

Una consulta de Datastore recupera entidades de Cloud Datastore que cumplen con un conjunto específico de condiciones.

Una consulta típica incluye lo siguiente:

Cuando se ejecuta, la consulta recupera todas las entidades de ese tipo que satisfacen todos los filtros indicados, en el orden que se especificó. Las consultas se ejecutan en modo de solo lectura.

En esta página se describe la estructura y las categorías de consultas usadas dentro de App Engine para recuperar datos de Cloud Datastore.

Filtros

El conjunto de filtros de una consulta limita las propiedades, las claves y los principales de las entidades que se recuperarán.

Filtros de propiedades

Un filtro de propiedad especifica lo siguiente:

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

La aplicación debe proporcionar el valor de la propiedad. No se puede referir a otras propiedades ni calcular en términos de estas. Una entidad satisface el filtro si tiene una propiedad con el nombre dado, cuyo valor se compara con el valor especificado en el filtro, de acuerdo con la descripción del operador de comparación.

El operador de comparación puede ser cualquiera de las siguientes clases anidadas (definidas como constantes enumeradas en la clase anidada Query.FilterOperator):

Operador Significado
EQUAL Igual que
LESS_THAN Menor que
LESS_THAN_OR_EQUAL Menor que o igual que
GREATER_THAN Mayor que
GREATER_THAN_OR_EQUAL Mayor que o igual que
NOT_EQUAL No igual que
IN Miembro de (igual que cualquier valor en una lista especificada)

El operador NOT_EQUAL lleva a cabo dos consultas: una en la que todos los demás filtros permanecen sin cambios y el filtro NOT_EQUAL se reemplaza con un filtro LESS_THAN, y otro donde se reemplaza con un filtro GREATER_THAN. Luego, los resultados se combinan en orden. Una consulta no puede tener más de un filtro NOT_EQUAL y una consulta que tiene uno, no puede tener otros filtros de desigualdad.

El operador IN también realiza consultas múltiples una para cada elemento en la lista especificada, con todos los demás filtros que permanecen sin cambios y el filtro IN reemplazado con un filtro de paridad (EQUAL). Los resultados se combinan en el orden de los elementos en la lista. Si una consulta tiene más de un filtro IN, se realiza como consultas múltiples, una por cada combinación posible de los valores en las listas IN.

Una única consulta que contiene los operadores NOT_EQUAL o IN está limitada a no más de 30 subconsultas.

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

Filtros de clave

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

Java 8

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

Java 7

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

También se admite el orden ascendente en Entity.KEY_RESERVED_PROPERTY.

Cuando se usa un comparador de desigualdad, se aplican los siguientes criterios para clasificar las claves, en orden:

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

La comparación de los elementos de la ruta del principal es similar: por tipo (string), luego, por nombre de clave o ID numérico. Los tipos y los nombres de clave son strings que se ordenan por valor de byte. Los ID numéricos son valores enteros y se ordenan numéricamente. Si varias entidades que tienen el mismo superior y tipo usan una combinación de ID numéricos y strings con nombre de clave, las entidades que tienen ID numéricos anteceden a las que tienen nombres de clave.

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

Filtros principales

Puedes filtrar tus consultas de Datastore a un principal específico, de modo que los resultados que se muestren incluyan solo entidades que descienden de ese principal.

Java 8

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

Java 7

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

Tipos especiales de consultas

Hay algunos tipos específicos de consultas que merecen una mención especial:

Consultas sin categoría

Cuando una consulta no especifica el tipo ni el principal, se muestran todas las entidades de una aplicación del almacén de datos. Esto incluye entidades creadas y administradas por otras características de App Engine, como las entidades estadísticas y las entidades de metadatos de Blobstore (si las hubiera). Estas consultas sin categoría no pueden incluir filtros ni órdenes de clasificación para los valores de las propiedades. Sin embargo, pueden filtrar según las claves de entidad si especifican Entity.KEY_RESERVED_PROPERTY como el nombre de la propiedad:

Java 8

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

Java 7

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

Consultas principales

Una consulta con un filtro principal limita sus resultados a la entidad especificada y a sus secundarios.

Java 8

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());

Java 7

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 principales sin categoría

Una consulta sin categoría que incluye un filtro principal recuperará el principal especificado y todos sus descendientes, sin importar la categoría. Este tipo de consulta no requiere índices personalizados. Al igual que todas las consultas sin categoría, no pueden incluir filtros ni órdenes de clasificación para los valores de las propiedades, pero pueden filtrarse sobre la clave de la entidad.

Java 8

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

Java 7

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

El siguiente ejemplo muestra cómo recuperar todas las entidades descendientes de un principal dado:

Java 8

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());

Java 7

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 de solo claves

Una consulta de solo claves muestra solo las claves de las entidades resultantes, en lugar de las entidades en sí, con una latencia y un costo más bajos que recuperar entidades completas:

A menudo, es más rentable hacer primero consultas de solo claves y luego elegir un subconjunto de entidades de los resultados, en lugar de ejecutar una consulta general que puede arrojar más entidades que las que necesitas.

Consultas de proyección

En ocasiones, lo único que necesitas de los resultados de una consulta son los valores de unas pocas propiedades específicas. En estos casos, puedes usar una consulta de proyección para recuperar solo las propiedades que te interesan realmente, con costos y latencia menores que los de recuperar toda la entidad. Revisa la página sobre consultas de proyección para obtener más detalles.

Órdenes de clasificación

Un orden de clasificación de consulta especifica lo siguiente:

  • El nombre de una propiedad
  • Una dirección de clasificación (ascendente o descendente)

Por ejemplo:

Si una consulta incluye varios órdenes de clasificación, estos se aplican en la secuencia especificada. En el siguiente ejemplo, primero se clasifica por apellido ascendente y, luego, por altura descendente:

Si no se especifica ningún orden de clasificación, los resultados se muestran en el orden en que se recuperaron en Cloud Datastore.

Nota: Debido a la forma en que Cloud Datastore ejecuta las consultas, si una consulta especifica filtros de desigualdad en una propiedad y órdenes de clasificación en otras propiedades, la propiedad que se use en los filtros de desigualdad deberá ordenarse antes de las demás propiedades.

Índices

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

App Engine predefine un índice simple en cada propiedad de una entidad. Una aplicación de App Engine puede definir otros índices personalizados en un archivo de configuración de índices denominado datastore-indexes.xml, que se genera en el directorio /war/WEB-INF/appengine-generated de tu aplicación. El servidor de desarrollo agrega sugerencias a este archivo automáticamente cuando encuentra consultas que no pueden ejecutarse con los índices existentes. Para ajustar los índices de manera manual, edita el archivo antes de subir la aplicación.

Ejemplo de interfaz de consulta

La API de Datastore de Java de bajo nivel proporciona la clase Query si deseas crear consultas y la interfaz PreparedQuery para recuperar entidades del almacén de datos:

Java 8

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");
}

Java 7

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");
}

Observa el uso de FilterPredicate y de CompositeFilter para crear filtros. Si configuras solamente un filtro en una consulta, puedes usar FilterPredicate solo:

Java 8

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

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

Java 7

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

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

Sin embargo, si quieres configurar más de un filtro en una consulta, debes usar CompositeFilter, que requiere al menos dos filtros. El ejemplo anterior utiliza el auxiliar de acceso directo de CompositeFilterOperator.and; el siguiente ejemplo muestra una forma de crear un filtro OR compuesto:

Java 8

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);

Java 7

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);

Pasos siguientes