Cómo recuperar datos

En este documento se abarcan aspectos básicos de la recuperación de datos de la base de datos, cómo se ordenan los datos y cómo realizar consultas simples sobre estos. La recuperación de datos en el SDK de Admin se implementa de forma ligeramente diferente en distintos lenguajes de programación.

  1. Receptores asíncronos: para recuperar los datos almacenados en Firebase Realtime Database, se debe adjuntar un agente de escucha asíncrono a la referencia de una base de datos. El agente de escucha se activa una vez para el estado inicial de los datos y otra vez cuando los datos cambian. Los objetos de escucha de eventos pueden recibir varios tipos diferentes de eventos. Este modo de recuperación de datos es compatible con Java, Node.js y los SDK de Admin de Python.
  2. Bloqueo de lecturas: para recuperar los datos almacenados en Firebase Realtime Database, se debe invocar un método de bloqueo sobre una referencia de base de datos. De esta forma, se obtienen los datos almacenados en la referencia. Cada llamada de método es una operación única. Esto significa que el SDK no registra ninguna devolución de llamada que escuche las actualizaciones de datos posteriores. Este modelo de recuperación de datos es compatible con los SDK de Admin de Python y Go.

Cómo comenzar

Revisemos nuevamente el ejemplo del blog del artículo anterior para comprender cómo leer datos desde una base de datos de Firebase. Recuerda que las entradas del blog en la app de ejemplo se almacenan en la URL de la base de datos https://docs-examples.firebaseio.com/server/saving-data/fireblog/posts.json. Para leer los datos de entradas, puedes hacer lo siguiente:

Java
public static class Post {

  public String author;
  public String title;

  public Post(String author, String title) {
    // ...
  }

}

// Get a reference to our posts
final FirebaseDatabase database = FirebaseDatabase.getInstance();
DatabaseReference ref = database.getReference("server/saving-data/fireblog/posts");

// Attach a listener to read the data at our posts reference
ref.addValueEventListener(new ValueEventListener() {
  @Override
  public void onDataChange(DataSnapshot dataSnapshot) {
    Post post = dataSnapshot.getValue(Post.class);
    System.out.println(post);
  }

  @Override
  public void onCancelled(DatabaseError databaseError) {
    System.out.println("The read failed: " + databaseError.getCode());
  }
});
Node.js
// Get a database reference to our posts
const db = getDatabase();
const ref = db.ref('server/saving-data/fireblog/posts');

// Attach an asynchronous callback to read the data at our posts reference
ref.on('value', (snapshot) => {
  console.log(snapshot.val());
}, (errorObject) => {
  console.log('The read failed: ' + errorObject.name);
}); 
Python
# Import database module.
from firebase_admin import db

# Get a database reference to our posts
ref = db.reference('server/saving-data/fireblog/posts')

# Read the data at the posts reference (this is a blocking operation)
print(ref.get())
Go

// Post is a json-serializable type.
type Post struct {
	Author string `json:"author,omitempty"`
	Title  string `json:"title,omitempty"`
}

// Create a database client from App.
client, err := app.Database(ctx)
if err != nil {
	log.Fatalln("Error initializing database client:", err)
}

// Get a database reference to our posts
ref := client.NewRef("server/saving-data/fireblog/posts")

// Read the data at the posts reference (this is a blocking operation)
var post Post
if err := ref.Get(ctx, &post); err != nil {
	log.Fatalln("Error reading value:", err)
}

Si ejecutas el código anterior, verás un objeto que contiene todas tus entradas registradas en la consola. En el caso de Node.js y Java, la función de objeto de escucha se llama cada vez que se agregan datos nuevos a tu referencia de la base de datos; no es necesario que escribas más código para que esto suceda.

En Java y Node.js, la función de devolución de llamada recibe una DataSnapshot, que es una instantánea de los datos. Una instantánea es una imagen de los datos en una referencia particular de base de datos en un momento especifico. Si llamas a val() o getValue() en una instantánea, se muestra una representación de objeto específica del lenguaje. Si no existen datos en la ubicación de la referencia, el valor de la instantánea es null. El método get() en Python muestra una representación de Python de los datos directamente. En Go la función Get() desmarca los datos en una estructura de datos determinada.

Ten en cuenta que usamos el tipo de evento value en el ejemplo anterior, lo que permite leer todo el contenido de una referencia de base de datos de Firebase, incluso si solo se modificó una parte de los datos. value es uno de los cinco tipos de eventos que se indican a continuación y que podemos usar para leer el contenido de la base de datos.

Lee tipos de eventos en Java y Node.js

Valor

El evento value se usa para leer una instantánea estática del contenido de la ruta de acceso de una base de datos, tal como existía cuando se produjo el evento de lectura. Se activa una vez con el estado inicial de los datos y nuevamente cada vez que estos se cambian. La devolución de llamada de evento recibe una instantánea que contiene todos los datos de esa ubicación, incluidos los datos de campos secundarios. En el ejemplo de código anterior, value mostró todas las entradas de blog en tu app. Cada vez que se agrega una entrada de blog nueva, la función de devolución de llamada mostrará todas las entradas.

Child Added

Por lo general, el evento child_added se usa para recuperar una lista de elementos en la base de datos. A diferencia de value, que permite obtener todo el contenido de la ubicación indicada, child_added se activa una vez por cada campo secundario y cada vez que se agregue un nuevo campo secundario en la ruta especificada. La devolución de llamada del evento recibe una instantánea que contiene los datos del nuevo campo secundario. Para establecer un orden, también se recibe un segundo argumento que contiene la clave del anterior campo secundario.

Si solo quieres recuperar los datos de cada entrada nueva que agregaste a tu app de blogs, puedes usar child_added:

Java
ref.addChildEventListener(new ChildEventListener() {
  @Override
  public void onChildAdded(DataSnapshot dataSnapshot, String prevChildKey) {
    Post newPost = dataSnapshot.getValue(Post.class);
    System.out.println("Author: " + newPost.author);
    System.out.println("Title: " + newPost.title);
    System.out.println("Previous Post ID: " + prevChildKey);
  }

  @Override
  public void onChildChanged(DataSnapshot dataSnapshot, String prevChildKey) {}

  @Override
  public void onChildRemoved(DataSnapshot dataSnapshot) {}

  @Override
  public void onChildMoved(DataSnapshot dataSnapshot, String prevChildKey) {}

  @Override
  public void onCancelled(DatabaseError databaseError) {}
});
Node.js
// Retrieve new posts as they are added to our database
ref.on('child_added', (snapshot, prevChildKey) => {
  const newPost = snapshot.val();
  console.log('Author: ' + newPost.author);
  console.log('Title: ' + newPost.title);
  console.log('Previous Post ID: ' + prevChildKey);
});

En este ejemplo, la instantánea contendrá un objeto con una entrada de blog individual. Debido a que el SDK convierte entradas en objetos mediante la recuperación del valor, puedes acceder a las propiedades de autor y título de la entrada con solo llamar a author y a title, respectivamente. También tienes acceso al ID de la entrada anterior a partir del segundo argumento prevChildKey.

Child Changed

El evento child_changed se activa cada vez que se modifica un nodo secundario. Esto incluye cualquier modificación en los descendientes del nodo secundario. Por lo general, se usa junto con child_added y child_removed para responder a los cambios en una lista de elementos. La instantánea que se pasa al callback de eventos contiene los datos actualizados para el campo secundario.

Puedes usar child_changed para leer los datos actualizados en las entradas de blog cuando se editan:

Java
ref.addChildEventListener(new ChildEventListener() {
  @Override
  public void onChildAdded(DataSnapshot dataSnapshot, String prevChildKey) {}

  @Override
  public void onChildChanged(DataSnapshot dataSnapshot, String prevChildKey) {
    Post changedPost = dataSnapshot.getValue(Post.class);
    System.out.println("The updated post title is: " + changedPost.title);
  }

  @Override
  public void onChildRemoved(DataSnapshot dataSnapshot) {}

  @Override
  public void onChildMoved(DataSnapshot dataSnapshot, String prevChildKey) {}

  @Override
  public void onCancelled(DatabaseError databaseError) {}
});
Node.js
// Get the data on a post that has changed
ref.on('child_changed', (snapshot) => {
  const changedPost = snapshot.val();
  console.log('The updated post title is ' + changedPost.title);
});

Child Removed

El evento child_removed se activa cuando se quita un campo secundario inmediato. Por lo general, se usa junto con child_added y child_changed. La instantánea que se pasa al receptor de eventos contiene los datos para el campo secundario que se quitó.

En el ejemplo de blog, puedes usar child_removed para registrar una notificación sobre la publicación que se borró en la consola:

Java
ref.addChildEventListener(new ChildEventListener() {
  @Override
  public void onChildAdded(DataSnapshot dataSnapshot, String prevChildKey) {}

  @Override
  public void onChildChanged(DataSnapshot dataSnapshot, String prevChildKey) {}

  @Override
  public void onChildRemoved(DataSnapshot dataSnapshot) {
    Post removedPost = dataSnapshot.getValue(Post.class);
    System.out.println("The blog post titled " + removedPost.title + " has been deleted");
  }

  @Override
  public void onChildMoved(DataSnapshot dataSnapshot, String prevChildKey) {}

  @Override
  public void onCancelled(DatabaseError databaseError) {}
});
Node.js
// Get a reference to our posts
const ref = db.ref('server/saving-data/fireblog/posts');

// Get the data on a post that has been removed
ref.on('child_removed', (snapshot) => {
  const deletedPost = snapshot.val();
  console.log('The blog post titled \'' + deletedPost.title + '\' has been deleted');
});

Child Moved

El evento child_moved se usa cuando se trabaja con datos ordenados. Este tema se aborda en la siguiente sección.

Garantías de evento

La base de datos de Firebase otorga varias garantías importantes acerca de los eventos:

Garantías de eventos de base de datos
Los eventos siempre se activan cuando el estado local se modifica.
Los eventos siempre reflejarán el estado correcto de los datos, incluso cuando las operaciones o tiempos locales generen diferencias temporales, como durante una pérdida temporal de la conexión de red.
Las escrituras desde un cliente único siempre se realizan en el servidor y se navegan en otros usuarios en orden.
Los eventos value siempre se activan al final y se garantiza que contienen actualizaciones de cualquier otro evento que haya ocurrido antes de que se tomara esa instantánea.

Debido a que los eventos value siempre se activan en último lugar, el siguiente ejemplo siempre funcionará:

Java
final AtomicInteger count = new AtomicInteger();

ref.addChildEventListener(new ChildEventListener() {
  @Override
  public void onChildAdded(DataSnapshot dataSnapshot, String prevChildKey) {
    // New child added, increment count
    int newCount = count.incrementAndGet();
    System.out.println("Added " + dataSnapshot.getKey() + ", count is " + newCount);
  }

  // ...
});

// The number of children will always be equal to 'count' since the value of
// the dataSnapshot here will include every child_added event triggered before this point.
ref.addListenerForSingleValueEvent(new ValueEventListener() {
  @Override
  public void onDataChange(DataSnapshot dataSnapshot) {
    long numChildren = dataSnapshot.getChildrenCount();
    System.out.println(count.get() + " == " + numChildren);
  }

  @Override
  public void onCancelled(DatabaseError databaseError) {}
});
Node.js
let count = 0;

ref.on('child_added', (snap) => {
  count++;
  console.log('added:', snap.key);
});

// length will always equal count, since snap.val() will include every child_added event
// triggered before this point
ref.once('value', (snap) => {
  console.log('initial data loaded!', snap.numChildren() === count);
});

Desconexión de devolución de llamada

Las devoluciones de llamada se quitan al especificar el tipo de evento y la función de la devolución de llamada a eliminar, de la siguiente manera:

Java
// Create and attach listener
ValueEventListener listener = new ValueEventListener() {
    // ...
};
ref.addValueEventListener(listener);

// Remove listener
ref.removeEventListener(listener);
Node.js
ref.off('value', originalCallback);

Si pasaste un contexto de ámbito a on(), se debe pasar cuando se desconecta la devolución de llamada:

Java
// Not applicable for Java
Node.js
ref.off('value', originalCallback, ctx);

Si quieres quitar todas las devoluciones de llamada en una ubicación, puedes hacer lo siguiente:

Java
// No Java equivalent, listeners must be removed individually.
Node.js
// Remove all value callbacks
ref.off('value');

// Remove all callbacks of any type
ref.off();

Lectura de datos una sola vez

En algunos casos, puede resultar útil invocar una devolución de llamada una sola vez y luego quitarla de inmediato. Hemos creado una función de ayuda para simplificar este proceso:

Java
ref.addListenerForSingleValueEvent(new ValueEventListener() {
  @Override
  public void onDataChange(DataSnapshot dataSnapshot) {
    // ...
  }

  @Override
  public void onCancelled(DatabaseError databaseError) {
    // ...
  }
});
Node.js
ref.once('value', (data) => {
  // do some stuff once
});
Python
# Import database module.
from firebase_admin import db

# Get a database reference to our posts
ref = db.reference('server/saving-data/fireblog/posts')

# Read the data at the posts reference (this is a blocking operation)
print(ref.get())
Go
// Create a database client from App.
client, err := app.Database(ctx)
if err != nil {
	log.Fatalln("Error initializing database client:", err)
}

// Get a database reference to our posts
ref := client.NewRef("server/saving-data/fireblog/posts")

// Read the data at the posts reference (this is a blocking operation)
var post Post
if err := ref.Get(ctx, &post); err != nil {
	log.Fatalln("Error reading value:", err)
}

Consulta de datos

Con la base de datos de Firebase, puedes realizar consultas que permiten recuperar datos según varios factores. Para crear una búsqueda en la base de datos, primero se especifica cómo se quiere ordenar los datos mediante una de las funciones de orden: orderByChild(), orderByKey() o orderByValue(). Luego, puedes combinarlas con otros cinco métodos para realizar consultas complejas: limitToFirst(), limitToLast(), startAt(), endAt() y equalTo().

En Firebase, a todos nos gustan los dinosaurios. A continuación, usaremos un fragmento de una base de datos de muestra que contiene información sobre dinosaurios para demostrar cómo realizar consultas:

{
  "lambeosaurus": {
    "height" : 2.1,
    "length" : 12.5,
    "weight": 5000
  },
  "stegosaurus": {
    "height" : 4,
    "length" : 9,
    "weight" : 2500
  }
}

Puedes ordenar los datos por child key, key o value. Una consulta básica de base de datos comienza con una de estas funciones de ordenamiento, cada una de las cuales se explica a continuación.

Ordenamiento con una clave secundaria especificada

Para ordenar los nodos según una clave secundaria común, puedes pasar esa clave a orderByChild(). Por ejemplo, para ver todos los dinosaurios ordenados por altura, puedes hacer lo siguiente:

Java
public static class Dinosaur {

  public int height;
  public int weight;

  public Dinosaur(int height, int weight) {
    // ...
  }

}

final DatabaseReference dinosaursRef = database.getReference("dinosaurs");
dinosaursRef.orderByChild("height").addChildEventListener(new ChildEventListener() {
  @Override
  public void onChildAdded(DataSnapshot dataSnapshot, String prevChildKey) {
    Dinosaur dinosaur = dataSnapshot.getValue(Dinosaur.class);
    System.out.println(dataSnapshot.getKey() + " was " + dinosaur.height + " meters tall.");
  }

  // ...
});
Node.js
const ref = db.ref('dinosaurs');

ref.orderByChild('height').on('child_added', (snapshot) => {
  console.log(snapshot.key + ' was ' + snapshot.val().height + ' meters tall');
});
Python
ref = db.reference('dinosaurs')
snapshot = ref.order_by_child('height').get()
for key, val in snapshot.items():
    print('{0} was {1} meters tall'.format(key, val))
Comienza a usarlo

// Dinosaur is a json-serializable type.
type Dinosaur struct {
	Height int `json:"height"`
	Width  int `json:"width"`
}

ref := client.NewRef("dinosaurs")

results, err := ref.OrderByChild("height").GetOrdered(ctx)
if err != nil {
	log.Fatalln("Error querying database:", err)
}
for _, r := range results {
	var d Dinosaur
	if err := r.Unmarshal(&d); err != nil {
		log.Fatalln("Error unmarshaling result:", err)
	}
	fmt.Printf("%s was %d meteres tall", r.Key(), d.Height)
}

Cualquier nodo que no tenga la clave secundaria que estamos buscando se ordena con el valor null, es decir, aparecerá primero en el orden. Para saber cómo se ordenan los datos, revisa la sección Cómo se ordenan los datos.

Las consultas también se pueden ordenar por campos secundarios profundamente anidados, en lugar de únicamente por campos secundarios de un nivel inferior. Esto resulta útil si tienes datos profundamente anidados, como estos:

{
  "lambeosaurus": {
    "dimensions": {
      "height" : 2.1,
      "length" : 12.5,
      "weight": 5000
    }
  },
  "stegosaurus": {
    "dimensions": {
      "height" : 4,
      "length" : 9,
      "weight" : 2500
    }
  }
}

Para consultar la altura ahora, puedes usar la ruta de acceso completa del objeto en lugar de una única clave:

Java
dinosaursRef.orderByChild("dimensions/height").addChildEventListener(new ChildEventListener() {
  @Override
  public void onChildAdded(DataSnapshot dataSnapshot, String prevChildKey) {
    // ...
  }

  // ...
});
Node.js
const ref = db.ref('dinosaurs');
ref.orderByChild('dimensions/height').on('child_added', (snapshot) => {
  console.log(snapshot.key + ' was ' + snapshot.val().height + ' meters tall');
});
Python
ref = db.reference('dinosaurs')
snapshot = ref.order_by_child('dimensions/height').get()
for key, val in snapshot.items():
    print('{0} was {1} meters tall'.format(key, val))
Go
ref := client.NewRef("dinosaurs")

results, err := ref.OrderByChild("dimensions/height").GetOrdered(ctx)
if err != nil {
	log.Fatalln("Error querying database:", err)
}
for _, r := range results {
	var d Dinosaur
	if err := r.Unmarshal(&d); err != nil {
		log.Fatalln("Error unmarshaling result:", err)
	}
	fmt.Printf("%s was %d meteres tall", r.Key(), d.Height)
}

Las consultas solo pueden ordenarse por una clave a la vez. Si se llama a orderByChild() varias veces en la misma búsqueda, se produce un error.

Ordenamiento por clave

También puedes ordenar los nodos por sus claves con el método orderByKey(). En el siguiente ejemplo, se leen todos los dinosaurios en orden alfabético:

Java
dinosaursRef.orderByKey().addChildEventListener(new ChildEventListener() {
  @Override
  public void onChildAdded(DataSnapshot dataSnapshot, String prevChildKey) {
    System.out.println(dataSnapshot.getKey());
  }

  // ...
});
Node.js
var ref = db.ref('dinosaurs');
ref.orderByKey().on('child_added', (snapshot) => {
  console.log(snapshot.key);
});
Python
ref = db.reference('dinosaurs')
snapshot = ref.order_by_key().get()
print(snapshot)
Go
ref := client.NewRef("dinosaurs")

results, err := ref.OrderByKey().GetOrdered(ctx)
if err != nil {
	log.Fatalln("Error querying database:", err)
}
snapshot := make([]Dinosaur, len(results))
for i, r := range results {
	var d Dinosaur
	if err := r.Unmarshal(&d); err != nil {
		log.Fatalln("Error unmarshaling result:", err)
	}
	snapshot[i] = d
}
fmt.Println(snapshot)

Ordenamiento por valor

Puedes ordenar los nodos por el valor de sus claves secundarias a través del método orderByValue(). Supongamos que los dinosaurios están realizando una competencia deportiva y que llevas un registro de sus puntuaciones con el siguiente formato:

{
  "scores": {
    "bruhathkayosaurus" : 55,
    "lambeosaurus" : 21,
    "linhenykus" : 80,
    "pterodactyl" : 93,
    "stegosaurus" : 5,
    "triceratops" : 22
  }
}

Para ordenar los dinosaurios por sus puntajes, puedes realizar la siguiente consulta:

Java
DatabaseReference scoresRef = database.getReference("scores");
scoresRef.orderByValue().addChildEventListener(new ChildEventListener() {
  @Override
  public void onChildAdded(DataSnapshot dataSnapshot, String prevChildKey) {
    System.out.println("The " + dataSnapshot.getKey() + " score is " + dataSnapshot.getValue());
  }

  // ...
});
Node.js
const scoresRef = db.ref('scores');
scoresRef.orderByValue().on('value', (snapshot) => {
  snapshot.forEach((data) => {
    console.log('The ' + data.key + ' dinosaur\'s score is ' + data.val());
  });
});
Python
ref = db.reference('scores')
snapshot = ref.order_by_value().get()
for key, val in snapshot.items():
    print('The {0} dinosaur\'s score is {1}'.format(key, val))
Comienza a usarlo
ref := client.NewRef("scores")

results, err := ref.OrderByValue().GetOrdered(ctx)
if err != nil {
	log.Fatalln("Error querying database:", err)
}
for _, r := range results {
	var score int
	if err := r.Unmarshal(&score); err != nil {
		log.Fatalln("Error unmarshaling result:", err)
	}
	fmt.Printf("The %s dinosaur's score is %d\n", r.Key(), score)
}

Consulta la sección Cómo se ordenan los datos para ver una explicación sobre cómo se ordenan los valores null, booleanos, de string y de objeto cuando se usa orderByValue().

Consultas complejas

Ahora que sabes cómo se ordenan tus datos, puedes usar los métodos de límite o rango que se describen a continuación para realizar consultas más complejas.

Consultas de límites

Las consultas limitToFirst() y limitToLast() se usan para establecer una cantidad máxima de elementos secundarios que se sincronizarán para una devolución de llamada específica. Si estableces un límite de 100, inicialmente solo recibirás hasta 100 eventos child_added. Si tienes menos de 100 mensajes almacenados en tu base de datos, se activará un evento child_added por cada uno de ellos. Sin embargo, si tienes más de 100 mensajes, solo recibirás un evento child_added por 100 de ellos. Estos son los primeros 100 mensajes ordenados si usas limitToFirst() o los últimos 100 mensajes ordenados si usas limitToLast(). A medida que los elementos cambien, recibirás eventos de tipo child_added para los elementos que ingresen la búsqueda y eventos de tipo child_removed por los elementos que la abandonen, por lo que el número total permanecerá en 100.

Para buscar los dos dinosaurios de mayor peso, puedes usar la base de datos de características de los dinosaurios y orderByChild().

Java
dinosaursRef.orderByChild("weight").limitToLast(2).addChildEventListener(new ChildEventListener() {
  @Override
  public void onChildAdded(DataSnapshot dataSnapshot, String prevChildKey) {
    System.out.println(dataSnapshot.getKey());
  }

  // ...
});
Node.js
const ref = db.ref('dinosaurs');
ref.orderByChild('weight').limitToLast(2).on('child_added', (snapshot) => {
  console.log(snapshot.key);
});
Python
ref = db.reference('dinosaurs')
snapshot = ref.order_by_child('weight').limit_to_last(2).get()
for key in snapshot:
    print(key)
Go
ref := client.NewRef("dinosaurs")

results, err := ref.OrderByChild("weight").LimitToLast(2).GetOrdered(ctx)
if err != nil {
	log.Fatalln("Error querying database:", err)
}
for _, r := range results {
	fmt.Println(r.Key())
}

La devolución de llamada child_added se activa exactamente dos veces, a menos que haya menos de dos dinosaurios almacenados en la base de datos. También se activará para cada nuevo dinosaurio más pesado que se agregue a la base de datos. En Python la consulta muestra directamente un OrderedDict que contiene los dos dinosaurios más pesados.

Asimismo, puedes usar limitToFirst() para encontrar los dos dinosaurios de menor tamaño:

Java
dinosaursRef.orderByChild("weight").limitToFirst(2).addChildEventListener(new ChildEventListener() {
  @Override
  public void onChildAdded(DataSnapshot dataSnapshot, String prevChildKey) {
    System.out.println(dataSnapshot.getKey());
  }

  // ...
});
Node.js
const ref = db.ref('dinosaurs');
ref.orderByChild('height').limitToFirst(2).on('child_added', (snapshot) => {
  console.log(snapshot.key);
});
Python
ref = db.reference('dinosaurs')
snapshot = ref.order_by_child('height').limit_to_first(2).get()
for key in snapshot:
    print(key)
Go
ref := client.NewRef("dinosaurs")

results, err := ref.OrderByChild("height").LimitToFirst(2).GetOrdered(ctx)
if err != nil {
	log.Fatalln("Error querying database:", err)
}
for _, r := range results {
	fmt.Println(r.Key())
}

La devolución de llamada child_added se activa exactamente dos veces, a menos que haya menos de dos dinosaurios almacenados en la base de datos. También se activará si se quita uno de los dos primeros dinosaurios de la base de datos, ya que ahora un dinosaurio nuevo será el segundo de menos longitud. En Python, la búsqueda muestra directamente un OrderedDict que contiene los dinosaurios más cortos.

También puedes realizar búsquedas con límites mediante orderByValue(). Si deseas crear una tabla de clasificación con los 3 dinosaurios que obtuvieron los mejores puntajes en la competencia deportiva, podrías hacer lo siguiente:

Java
scoresRef.orderByValue().limitToFirst(3).addChildEventListener(new ChildEventListener() {
  @Override
  public void onChildAdded(DataSnapshot dataSnapshot, String prevChildKey) {
    System.out.println("The " + dataSnapshot.getKey() + " score is " + dataSnapshot.getValue());
  }

  // ...
});
Node.js
const scoresRef = db.ref('scores');
scoresRef.orderByValue().limitToLast(3).on('value', (snapshot)  =>{
  snapshot.forEach((data) => {
    console.log('The ' + data.key + ' dinosaur\'s score is ' + data.val());
  });
});
Python
scores_ref = db.reference('scores')
snapshot = scores_ref.order_by_value().limit_to_last(3).get()
for key, val in snapshot.items():
    print('The {0} dinosaur\'s score is {1}'.format(key, val))
Go
ref := client.NewRef("scores")

results, err := ref.OrderByValue().LimitToLast(3).GetOrdered(ctx)
if err != nil {
	log.Fatalln("Error querying database:", err)
}
for _, r := range results {
	var score int
	if err := r.Unmarshal(&score); err != nil {
		log.Fatalln("Error unmarshaling result:", err)
	}
	fmt.Printf("The %s dinosaur's score is %d\n", r.Key(), score)
}

Consultas por rango

Con startAt(), endAt() y equalTo(), puedes elegir puntos arbitrarios de inicio y término para tus consultas. Por ejemplo, si deseas buscar todos los dinosaurios que miden al menos tres metros de altura, puedes combinar orderByChild() y startAt():

Java
dinosaursRef.orderByChild("height").startAt(3).addChildEventListener(new ChildEventListener() {
  @Override
  public void onChildAdded(DataSnapshot dataSnapshot, String prevChildKey) {
    System.out.println(dataSnapshot.getKey());
  }

  // ...
});
Node.js
const ref = db.ref('dinosaurs');
ref.orderByChild('height').startAt(3).on('child_added', (snapshot) => {
  console.log(snapshot.key);
});
Python
ref = db.reference('dinosaurs')
snapshot = ref.order_by_child('height').start_at(3).get()
for key in snapshot:
    print(key)
Go
ref := client.NewRef("dinosaurs")

results, err := ref.OrderByChild("height").StartAt(3).GetOrdered(ctx)
if err != nil {
	log.Fatalln("Error querying database:", err)
}
for _, r := range results {
	fmt.Println(r.Key())
}

Puedes usar endAt() para encontrar todos los dinosaurios cuyos nombres antecedan al pterodáctilo, lexicográficamente. En el siguiente ejemplo, se usa el término “Pterodactyl”, en inglés:

Java
dinosaursRef.orderByKey().endAt("pterodactyl").addChildEventListener(new ChildEventListener() {
  @Override
  public void onChildAdded(DataSnapshot dataSnapshot, String prevChildKey) {
    System.out.println(dataSnapshot.getKey());
  }

  // ...
});
Node.js
const ref = db.ref('dinosaurs');
ref.orderByKey().endAt('pterodactyl').on('child_added', (snapshot) => {
  console.log(snapshot.key);
});
Python
ref = db.reference('dinosaurs')
snapshot = ref.order_by_key().end_at('pterodactyl').get()
for key in snapshot:
    print(key)
Go
ref := client.NewRef("dinosaurs")

results, err := ref.OrderByKey().EndAt("pterodactyl").GetOrdered(ctx)
if err != nil {
	log.Fatalln("Error querying database:", err)
}
for _, r := range results {
	fmt.Println(r.Key())
}

Puedes combinar startAt() y endAt() para limitar ambos extremos de tu consulta. El siguiente ejemplo permite encontrar todos los dinosaurios cuyos nombres empiecen con la letra “b”:

Java
dinosaursRef.orderByKey().startAt("b").endAt("b\uf8ff").addChildEventListener(new ChildEventListener() {
  @Override
  public void onChildAdded(DataSnapshot dataSnapshot, String prevChildKey) {
    System.out.println(dataSnapshot.getKey());
  }

  // ...
});
Node.js
var ref = db.ref('dinosaurs');
ref.orderByKey().startAt('b').endAt('b\uf8ff').on('child_added', (snapshot) => {
  console.log(snapshot.key);
});
Python
ref = db.reference('dinosaurs')
snapshot = ref.order_by_key().start_at('b').end_at(u'b\uf8ff').get()
for key in snapshot:
    print(key)
Go
ref := client.NewRef("dinosaurs")

results, err := ref.OrderByKey().StartAt("b").EndAt("b\uf8ff").GetOrdered(ctx)
if err != nil {
	log.Fatalln("Error querying database:", err)
}
for _, r := range results {
	fmt.Println(r.Key())
}

El método equalTo() te permite filtrar según coincidencias exactas. Como sucede con las otras consultas por rango, se activará por cada nodo de campo secundario que se active. Por ejemplo, puedes usar la siguiente consulta para encontrar todos los dinosaurios que tengan una altura de 25 metros:

Java
dinosaursRef.orderByChild("height").equalTo(25).addChildEventListener(new ChildEventListener() {
  @Override
  public void onChildAdded(DataSnapshot dataSnapshot, String prevChildKey) {
    System.out.println(dataSnapshot.getKey());
  }

  // ...
});
Node.js
const ref = db.ref('dinosaurs');
ref.orderByChild('height').equalTo(25).on('child_added', (snapshot) => {
  console.log(snapshot.key);
});
Python
ref = db.reference('dinosaurs')
snapshot = ref.order_by_child('height').equal_to(25).get()
for key in snapshot:
    print(key)
Go
ref := client.NewRef("dinosaurs")

results, err := ref.OrderByChild("height").EqualTo(25).GetOrdered(ctx)
if err != nil {
	log.Fatalln("Error querying database:", err)
}
for _, r := range results {
	fmt.Println(r.Key())
}

Las consultas por rango también son útiles cuando debes paginar tus datos.

Revisión general

Puedes combinar todas estas técnicas para crear consultas complejas. Por ejemplo, puedes encontrar el nombre del dinosaurio cuya altura es inferior a la del estegosaurio:

Java
dinosaursRef.child("stegosaurus").child("height").addValueEventListener(new ValueEventListener() {
  @Override
  public void onDataChange(DataSnapshot stegoHeightSnapshot) {
    Integer favoriteDinoHeight = stegoHeightSnapshot.getValue(Integer.class);
    Query query = dinosaursRef.orderByChild("height").endAt(favoriteDinoHeight).limitToLast(2);
    query.addValueEventListener(new ValueEventListener() {
      @Override
      public void onDataChange(DataSnapshot dataSnapshot) {
        // Data is ordered by increasing height, so we want the first entry
        DataSnapshot firstChild = dataSnapshot.getChildren().iterator().next();
        System.out.println("The dinosaur just shorter than the stegosaurus is: " + firstChild.getKey());
      }

      @Override
      public void onCancelled(DatabaseError databaseError) {
        // ...
      }
    });
  }

  @Override
  public void onCancelled(DatabaseError databaseError) {
    // ...
  }
});
Node.js
  const ref = db.ref('dinosaurs');
  ref.child('stegosaurus').child('height').on('value', (stegosaurusHeightSnapshot) => {
    const favoriteDinoHeight = stegosaurusHeightSnapshot.val();

    const queryRef = ref.orderByChild('height').endAt(favoriteDinoHeight).limitToLast(2);
    queryRef.on('value', (querySnapshot) => {
      if (querySnapshot.numChildren() === 2) {
        // Data is ordered by increasing height, so we want the first entry
        querySnapshot.forEach((dinoSnapshot) => {
          console.log('The dinosaur just shorter than the stegasaurus is ' + dinoSnapshot.key);

          // Returning true means that we will only loop through the forEach() one time
          return true;
        });
      } else {
        console.log('The stegosaurus is the shortest dino');
      }
    });
});
Python
ref = db.reference('dinosaurs')
favotire_dino_height = ref.child('stegosaurus').child('height').get()
query = ref.order_by_child('height').end_at(favotire_dino_height).limit_to_last(2)
snapshot = query.get()
if len(snapshot) == 2:
    # Data is ordered by increasing height, so we want the first entry.
    # Second entry is stegosarus.
    for key in snapshot:
        print('The dinosaur just shorter than the stegosaurus is {0}'.format(key))
        return
else:
    print('The stegosaurus is the shortest dino')
Go
ref := client.NewRef("dinosaurs")

var favDinoHeight int
if err := ref.Child("stegosaurus").Child("height").Get(ctx, &favDinoHeight); err != nil {
	log.Fatalln("Error querying database:", err)
}

query := ref.OrderByChild("height").EndAt(favDinoHeight).LimitToLast(2)
results, err := query.GetOrdered(ctx)
if err != nil {
	log.Fatalln("Error querying database:", err)
}
if len(results) == 2 {
	// Data is ordered by increasing height, so we want the first entry.
	// Second entry is stegosarus.
	fmt.Printf("The dinosaur just shorter than the stegosaurus is %s\n", results[0].Key())
} else {
	fmt.Println("The stegosaurus is the shortest dino")
}

Cómo se ordenan los datos

En esta sección se explica la manera en la que los datos se ordenan al usar cada una de las cuatro funciones de ordenamiento.

orderByChild

Cuando usas orderByChild(), los datos que contienen la clave secundaria especificada se ordenan de la siguiente manera:

  1. Los elementos secundarios cuyas claves secundarias especificadas posean el valor null irán en primer lugar.
  2. A continuación, aparecerán los elementos secundarios que tengan el valor false en la clave secundaria especificada. Si hay varios elementos secundarios con el valor false, se ordenan lexicográficamente por clave.
  3. A continuación, aparecerán los elementos secundarios que tengan el valor true en la clave secundaria especificada. Si hay varios elementos secundarios con el valor true, se ordenan lexicográficamente por clave.
  4. Luego vienen los elementos secundarios con valor numérico, que se ordenan en sentido ascendente. Si varios campos secundarios tienen el mismo valor numérico para el nodo secundario especificado, se ordenan por clave.
  5. Las strings van después de los números y se ordenan de manera lexicográfica, en sentido ascendente. Si varios campos secundarios tienen el mismo valor para el nodo secundario especificado, se ordenan de manera lexicográfica por clave.
  6. Los objetos van al final y se ordenan de manera lexicográfica por clave, en sentido ascendente.

orderByKey

Cuando usas orderByKey() para ordenar tus datos, los datos se muestran en orden ascendente por clave de la siguiente manera. Ten en cuenta que las claves solo pueden ser strings.

  1. Los campos secundarios con una clave que pueden analizarse como un elemento entero de 32 bits van primero, ordenados en sentido ascendente.
  2. Los campos secundarios con un valor de string como clave van después y ordenados de manera lexicográfica, en sentido ascendente.

orderByValue

Cuando usas orderByValue(), los elementos secundarios se ordenan según el valor. Se utilizan los mismos criterios de orden que para orderByChild(), excepto que se usa el valor del nodo en lugar del valor de una clave secundaria especificada.