Recuperando dados

Este documento aborda os conceitos básicos de recuperação de dados de banco de dados, como os dados são ordenados e como realizar consultas simples nos dados. A recuperação de dados no Admin SDK é implementada de maneira um pouco diferente em diferentes linguagens de programação.

  1. Ouvintes assíncronos: os dados armazenados em um Firebase Realtime Database são recuperados anexando um ouvinte assíncrono a uma referência de banco de dados. O ouvinte é acionado uma vez para o estado inicial dos dados e novamente sempre que os dados são alterados. Um ouvinte de eventos pode receber vários tipos diferentes de eventos . Este modo de recuperação de dados é compatível com Java, Node.js e Python Admin SDKs.
  2. Leituras de bloqueio: os dados armazenados em um Firebase Realtime Database são recuperados invocando um método de bloqueio em uma referência de banco de dados, que retorna os dados armazenados na referência. Cada chamada de método é uma operação única. Isso significa que o SDK não registra nenhum retorno de chamada que ouça atualizações de dados subsequentes. Este modelo de recuperação de dados é compatível com SDKs de administração Python e Go.

Começando

Vamos revisitar o exemplo de blog do artigo anterior para entender como ler dados de um banco de dados Firebase. Lembre-se de que as postagens do blog no aplicativo de exemplo são armazenadas na URL do banco de dados https://docs-examples.firebaseio.com/server/ saving-data/fireblog/posts.json . Para ler os dados da sua postagem, você pode fazer o seguinte:

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);
}); 
Pitão
# 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())
Ir

// 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)
}

Se você executar o código acima, verá um objeto contendo todas as suas postagens registradas no console. No caso de Node.js e Java, a função listener é chamada sempre que novos dados são adicionados à referência do seu banco de dados, e você não precisa escrever nenhum código extra para que isso aconteça.

Em Java e Node.js, a função de retorno de chamada recebe um DataSnapshot , que é um instantâneo dos dados. Um instantâneo é uma imagem dos dados em uma referência de banco de dados específica em um único momento. Chamar val() / getValue() em um instantâneo retorna uma representação dos dados de objeto específico da linguagem. Se não existirem dados no local da referência, o valor do instantâneo será null . O método get() em Python retorna uma representação Python dos dados diretamente. A função Get() em Go descompacta os dados em uma determinada estrutura de dados.

Observe que usamos o tipo de evento value no exemplo acima, que lê todo o conteúdo de uma referência de banco de dados Firebase, mesmo que apenas um dado tenha sido alterado. value é um dos cinco tipos de eventos diferentes listados abaixo que você pode usar para ler dados do banco de dados.

Leia tipos de eventos em Java e Node.js

Valor

O evento value é usado para ler um instantâneo estático do conteúdo em um determinado caminho do banco de dados, conforme existia no momento do evento read. Ele é acionado uma vez com os dados iniciais e novamente sempre que os dados são alterados. O retorno de chamada do evento recebe um instantâneo contendo todos os dados naquele local, incluindo dados filho. No exemplo de código acima, value retornou todas as postagens do blog em seu aplicativo. Cada vez que uma nova postagem no blog é adicionada, a função de retorno de chamada retornará todas as postagens.

Filho adicionado

O evento child_added normalmente é usado ao recuperar uma lista de itens do banco de dados. Ao contrário value que retorna todo o conteúdo do local, child_added é acionado uma vez para cada filho existente e novamente sempre que um novo filho é adicionado ao caminho especificado. O retorno de chamada do evento recebe um instantâneo contendo os dados do novo filho. Para fins de ordenação, também é passado um segundo argumento contendo a chave do filho anterior.

Se quiser recuperar apenas os dados de cada nova postagem adicionada ao seu aplicativo de blog, você pode 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);
});

Neste exemplo, o instantâneo conterá um objeto com uma postagem de blog individual. Como o SDK converte postagens em objetos recuperando o valor, você tem acesso às propriedades autor e título da postagem chamando author e title respectivamente. Você também tem acesso ao ID da postagem anterior do segundo argumento prevChildKey .

Criança alterada

O evento child_changed é acionado sempre que um nó filho é modificado. Isto inclui quaisquer modificações nos descendentes do nó filho. Normalmente é usado em conjunto com child_added e child_removed para responder a alterações em uma lista de itens. O instantâneo passado para o retorno de chamada do evento contém os dados atualizados do filho.

Você pode usar child_changed para ler dados atualizados nas postagens do blog quando elas são editadas:

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

Filho removido

O evento child_removed é acionado quando um filho imediato é removido. Normalmente é usado em conjunto com child_added e child_changed . O instantâneo passado para o retorno de chamada do evento contém os dados do filho removido.

No exemplo do blog, você pode usar child_removed para registrar uma notificação sobre a postagem excluída no console:

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

Criança movida

O evento child_moved é usado ao trabalhar com dados ordenados, que será abordado na próxima seção .

Garantias de eventos

O banco de dados Firebase oferece diversas garantias importantes em relação aos eventos:

Garantias de eventos de banco de dados
Os eventos sempre serão acionados quando o estado local mudar.
Os eventos sempre refletirão o estado correto dos dados, mesmo nos casos em que as operações locais ou o tempo causam diferenças temporárias, como na perda temporária da conexão de rede.
As gravações de um único cliente sempre serão gravadas no servidor e transmitidas para outros usuários em ordem.
Os eventos de valor são sempre acionados por último e têm a garantia de conter atualizações de quaisquer outros eventos que ocorreram antes da captura do instantâneo.

Como os eventos de valor são sempre acionados por último, o exemplo a seguir sempre 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);
});

Desanexando retornos de chamada

Os retornos de chamada são removidos especificando o tipo de evento e a função de retorno de chamada a ser removida, como a seguir:

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

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

Se você passou um contexto de escopo para on() , ele deverá ser passado ao desanexar o retorno de chamada:

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

Se quiser remover todos os retornos de chamada em um local, você pode fazer o seguinte:

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

Lendo dados uma vez

Em alguns casos, pode ser útil que um retorno de chamada seja chamado uma vez e imediatamente removido. Criamos uma função auxiliar para facilitar isso:

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
});
Pitão
# 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())
Ir
// 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)
}

Consultando dados

Com as consultas ao banco de dados do Firebase, você pode recuperar dados seletivamente com base em vários fatores. Para construir uma consulta em seu banco de dados, você começa especificando como deseja que seus dados sejam ordenados usando uma das funções de ordenação: orderByChild() , orderByKey() ou orderByValue() . Você pode então combiná-los com cinco outros métodos para conduzir consultas complexas: limitToFirst() , limitToLast() , startAt() , endAt() e equalTo() .

Como todos nós do Firebase achamos que os dinossauros são muito legais, usaremos um trecho de um banco de dados de amostra de fatos sobre dinossauros para demonstrar como você pode consultar dados em seu banco de dados do Firebase.:

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

Você pode ordenar os dados de três maneiras: por chave filha , por chave ou por valor . Uma consulta básica ao banco de dados começa com uma dessas funções de ordenação, cada uma delas explicada abaixo.

Ordenação por uma chave filha especificada

Você pode ordenar os nós por uma chave filha comum, passando essa chave para orderByChild() . Por exemplo, para ler todos os dinossauros ordenados por altura, você pode fazer o seguinte:

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');
});
Pitão
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))
Ir

// 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)
}

Qualquer nó que não tenha a chave filha que estamos consultando é classificado com um valor null , o que significa que ele virá primeiro na ordem. Para obter detalhes sobre como os dados são ordenados, consulte a seção Como os dados são ordenados .

As consultas também podem ser ordenadas por filhos profundamente aninhados, em vez de apenas filhos um nível abaixo. Isso é útil se você tiver dados profundamente aninhados como estes:

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

Para consultar a altura agora, você pode usar o caminho completo para o objeto em vez de uma única chave:

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');
});
Pitão
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))
Ir
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)
}

As consultas só podem ser ordenadas por uma chave por vez. Chamar orderByChild() várias vezes na mesma consulta gera um erro.

Ordenação por chave

Você também pode ordenar os nós por suas chaves usando o método orderByKey() . O exemplo a seguir lê todos os dinossauros em ordem alfabética:

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);
});
Pitão
ref = db.reference('dinosaurs')
snapshot = ref.order_by_key().get()
print(snapshot)
Ir
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)

Ordenação por valor

Você pode ordenar os nós pelo valor de suas chaves filhas usando o método orderByValue() . Digamos que os dinossauros estejam participando de uma competição esportiva de dinossauros e você esteja acompanhando suas pontuações no seguinte formato:

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

Para classificar os dinossauros por pontuação, você poderia construir a seguinte 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());
  });
});
Pitão
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))
Ir
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)
}

Consulte a seção Como os dados são ordenados para obter uma explicação sobre como os valores null , boolean, string e object são classificados ao usar orderByValue() .

Consultas complexas

Agora que está claro como seus dados são ordenados, você pode usar os métodos de limite ou intervalo descritos abaixo para construir consultas mais complexas.

Limitar consultas

As consultas limitToFirst() e limitToLast() são usadas para definir um número máximo de filhos a serem sincronizados para um determinado retorno de chamada. Se você definir um limite de 100, inicialmente receberá apenas até 100 eventos child_added . Se você tiver menos de 100 mensagens armazenadas em seu banco de dados, um evento child_added será acionado para cada mensagem. No entanto, se você tiver mais de 100 mensagens, receberá apenas um evento child_added para 100 dessas mensagens. Estas são as primeiras 100 mensagens ordenadas se você estiver usando limitToFirst() ou as últimas 100 mensagens ordenadas se você estiver usando limitToLast() . À medida que os itens mudam, você receberá eventos child_added para itens que entram na consulta e eventos child_removed para itens que saem dela, de modo que o número total permaneça em 100.

Usando o banco de dados de fatos sobre dinossauros e orderByChild() , você pode encontrar os dois dinossauros mais pesados:

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);
});
Pitão
ref = db.reference('dinosaurs')
snapshot = ref.order_by_child('weight').limit_to_last(2).get()
for key in snapshot:
    print(key)
Ir
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())
}

O retorno de chamada child_added é acionado exatamente duas vezes, a menos que haja menos de dois dinossauros armazenados no banco de dados. Ele também será acionado para cada dinossauro novo e mais pesado adicionado ao banco de dados. Em Python, a consulta retorna diretamente um OrderedDict contendo os dois dinossauros mais pesados.

Da mesma forma, você pode encontrar os dois dinossauros mais curtos usando limitToFirst() :

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);
});
Pitão
ref = db.reference('dinosaurs')
snapshot = ref.order_by_child('height').limit_to_first(2).get()
for key in snapshot:
    print(key)
Ir
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())
}

O retorno de chamada child_added é acionado exatamente duas vezes, a menos que haja menos de dois dinossauros armazenados no banco de dados. Ele também será acionado novamente se um dos dois primeiros dinossauros for removido do banco de dados, já que um novo dinossauro será agora o segundo mais curto. Em Python, a consulta retorna diretamente um OrderedDict contendo os dinossauros mais curtos.

Você também pode realizar consultas de limite com orderByValue() . Se quiser criar uma tabela de classificação com os 3 dinossauros esportivos com maior pontuação, você pode fazer o seguinte:

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());
  });
});
Pitão
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))
Ir
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 de intervalo

Usar startAt() , endAt() e equalTo() permite que você escolha pontos iniciais e finais arbitrários para suas consultas. Por exemplo, se você quiser encontrar todos os dinossauros com pelo menos três metros de altura, poderá combinar orderByChild() e 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);
});
Pitão
ref = db.reference('dinosaurs')
snapshot = ref.order_by_child('height').start_at(3).get()
for key in snapshot:
    print(key)
Ir
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())
}

Você pode usar endAt() para encontrar lexicograficamente todos os dinossauros cujos nomes vêm antes do Pterodáctilo:

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);
});
Pitão
ref = db.reference('dinosaurs')
snapshot = ref.order_by_key().end_at('pterodactyl').get()
for key in snapshot:
    print(key)
Ir
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())
}

Você pode combinar startAt() e endAt() para limitar ambas as extremidades da sua consulta. O exemplo a seguir encontra todos os dinossauros cujo nome começa com a 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);
});
Pitão
ref = db.reference('dinosaurs')
snapshot = ref.order_by_key().start_at('b').end_at(u'b\uf8ff').get()
for key in snapshot:
    print(key)
Ir
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())
}

O método equalTo() permite filtrar com base em correspondências exatas. Como é o caso das outras consultas de intervalo, ela será acionada para cada nó filho correspondente. Por exemplo, você pode usar a seguinte consulta para encontrar todos os dinossauros com 25 metros de altura:

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);
});
Pitão
ref = db.reference('dinosaurs')
snapshot = ref.order_by_child('height').equal_to(25).get()
for key in snapshot:
    print(key)
Ir
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())
}

Consultas de intervalo também são úteis quando você precisa paginar seus dados.

Juntando tudo

Você pode combinar todas essas técnicas para criar consultas complexas. Por exemplo, você pode encontrar o nome do dinossauro que é um pouco mais curto que o Estegossauro:

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');
      }
    });
});
Pitão
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')
Ir
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")
}

Como os dados são ordenados

Esta seção explica como seus dados são ordenados ao usar cada uma das quatro funções de ordenação.

encomendarByChild

Ao usar orderByChild() , os dados que contêm a chave filha especificada são ordenados da seguinte forma:

  1. Os filhos com um valor null para a chave filha especificada vêm primeiro.
  2. Os filhos com um valor false para a chave filha especificada vêm em seguida. Se vários filhos tiverem um valor false , eles serão classificados lexicograficamente por chave.
  3. Os filhos com um valor true para a chave filha especificada vêm em seguida. Se vários filhos tiverem um valor true , eles serão classificados lexicograficamente por chave.
  4. Os filhos com valor numérico vêm em seguida, classificados em ordem crescente. Se vários filhos tiverem o mesmo valor numérico para o nó filho especificado, eles serão classificados por chave.
  5. As strings vêm depois dos números e são classificadas lexicograficamente em ordem crescente. Se vários filhos tiverem o mesmo valor para o nó filho especificado, eles serão ordenados lexicograficamente por chave.
  6. Os objetos vêm por último e são classificados lexicograficamente por chave em ordem crescente.

pedidoByKey

Ao usar orderByKey() para classificar seus dados, os dados são retornados em ordem crescente por chave, como segue. Lembre-se de que as chaves só podem ser strings.

  1. Os filhos com uma chave que pode ser analisada como um número inteiro de 32 bits vêm primeiro, classificados em ordem crescente.
  2. Os filhos com um valor de string como chave vêm em seguida, classificados lexicograficamente em ordem crescente.

pedidoPorValor

Ao usar orderByValue() , os filhos são ordenados por seu valor. Os critérios de ordenação são os mesmos de orderByChild() , exceto que o valor do nó é usado em vez do valor de uma chave filha especificada.