Recuperar dados

Este documento abrange os conceitos básicos sobre a recuperação dos dados do banco, bem como a ordenação e as consultas simples deles. A recuperação de dados no SDK Admin é implementada de modo ligeiramente distinto em diferentes linguagens de programação.

  1. Listeners assíncronos: os dados armazenados em um Firebase Realtime Database são recuperados anexando um listener assíncrono a uma referência de banco de dados. Ele é acionado uma vez no estado inicial dos dados e posteriormente, quando há alterações. Um listener de eventos pode receber diversos tipos de eventos diferentes. Esse modo de recuperação de dados é compatível com SDKs Admin para Java, Node.js e Python.
  2. Leituras de método de bloqueio: os dados armazenados em um Firebase Realtime Database são recuperados ao invocar um método de bloqueio em uma referência de banco de dados, o 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 callbacks que detectam as próximas atualizações de dados. Esse modelo de recuperação de dados é compatível com o SDKs Admin para Python e Go.

Começar

Vamos reutilizar o exemplo de blog do artigo anterior para compreender como devemos ler os dados de um banco de dados do Firebase. Lembre-se de que as postagens do blog no aplicativo de exemplo são armazenadas no URL do banco de dados https://docs-examples.firebaseio.com/server/saving-data/fireblog/posts.json. Para ler os dados da postagem, faça 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);
}); 
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)
}

Se você executar o código acima, verá um objeto contendo todas as suas postagens registradas no console. No caso do Node.js e Java, a função listener é chamada quando novos dados são adicionados à referência de banco de dados. Não é necessário escrever códigos adicionais para isso.

Em Java e Node.js, a função de callback recebe um DataSnapshot, que é um snapshot dos dados. Um snapshot é uma imagem dos dados em uma referência de banco de dados específica de um determinado momento. Chamar val()/getValue() em um snapshot retorna uma representação de objeto específica da linguagem dos dados. Se não houver dados no local da referência, o valor do snapshot será null. O método get() no Python retorna uma representação Python dos dados diretamente. A função Get() no Go cancela o marshal dos dados em uma determinada estrutura.

Observe que usamos o tipo de evento value no exemplo acima, que lê todo o conteúdo de uma referência do banco de dados do Firebase, mesmo que apenas uma parte dos dados seja alterada. value é um dos cinco tipos de eventos diferentes listados abaixo que podem ser usados para ler dados do banco de dados.

Tipos de eventos de leitura em Java e Node.js

Valor

O evento value é usado para ler um snapshot estático do conteúdo em um determinado caminho de banco de dados, exatamente como ele existia no momento do evento de leitura. O evento é acionado uma vez com os dados iniciais e sempre que os dados forem novamente alterados. O callback do evento recebe um snapshot que contém todos os dados do local, incluindo dados filhos. No exemplo de código acima, value retornou todas as postagens do blog no seu aplicativo. Sempre que uma nova postagem do blog é adicionada, a função de callback retorna todas as postagens.

Child Added

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

Para recuperar apenas os dados de cada nova postagem adicionada ao seu aplicativo de blog, use 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 snapshot vai conter um objeto com uma postagem de blog individual. Como o SDK converte postagens em objetos recuperando o valor, é possível ter acesso às propriedades de autor e título da postagem ao chamar author e title, respectivamente. Também é possível ter acesso ao ID da postagem anterior no segundo argumento prevChildKey.

Child Changed

O evento child_changed é acionado sempre que um nó filho é modificado. Isso inclui todas as modificações nos descendentes do nó filho. Normalmente, ele é usado com child_added e child_removed para responder a alterações em uma lista de itens. O snapshot transmitido ao callback do evento contém os dados atualizados do filho.

Use child_changed para ler dados atualizados nas postagens do blog quando elas forem 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);
});

Child Removed

O evento child_removed é acionado quando um filho imediato é removido. Normalmente, ele é usado com child_added e child_changed. O snapshot transmitido ao callback do evento contém os dados do filho removido.

No exemplo do blog, é possível 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');
});

Child Moved

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

Garantias de eventos

O banco de dados do Firebase oferece várias garantias importantes a respeito dos eventos:

Garantias de evento do banco de dados
Os eventos serão sempre acionados quando o estado local mudar.
No fim, eles sempre refletirão o estado correto dos dados, mesmo quando as operações ou o tempo locais causem diferenças temporárias, como a perda temporária de conexão com a rede.
As gravações de um único cliente sempre serão gravadas no servidor e transmitidas a outros usuários, respeitando a ordem.
Os eventos "value" sempre serão acionados por último e sempre conterão as atualizações de todos os outros eventos ocorridos antes da geração do instantâneo.

Como os eventos "value" sempre são acionados por último, o seguinte exemplo 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);
});

Desconectar retornos de chamada

Para remover callbacks, especifique o tipo de evento e a função a ser removida, da seguinte forma:

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 precisa ser transmitido ao remover o callback:

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

Para remover todos os callbacks de um local, faça 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();

Leitura de dados uma vez

Em alguns casos, pode ser útil chamar um callback uma vez e removê-lo logo em seguida. Criamos uma função auxiliar para esse processo:

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 dados

Com consultas no banco de dados do Firebase, podemos recuperar dados seletivamente de acordo com vários fatores. Para criar uma consulta no banco de dados, especifique como os dados devem ser ordenados usando uma das funções de ordenação: orderByChild(), orderByKey() ou orderByValue(). Em seguida, é possível combiná-los com cinco outros métodos para realizar consultas complexas: limitToFirst(), limitToLast(), startAt(), endAt() e equalTo().

Já que a equipe do Firebase gosta muito de dinossauros, usaremos um snippet de uma amostra de banco de dados de fatos sobre esses animais para demonstrar como consultar dados no 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 formas: por chave filha, por chave ou por valor. Uma consulta básica do banco de dados começa com uma dessas funções de ordenação. Todas elas são explicadas a seguir.

Ordenação por uma chave filha especificada

É possível ordenar os nós por uma chave filha comum passando essa chave para orderByChild(). Por exemplo, para ler todos os dinossauros ordenados por altura, faça 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');
});
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))
Go
// 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 de null, o que significa que ele virá primeiro na ordem. Para ver mais detalhes sobre como os dados são ordenados, consulte a seção Ordenação dos dados.

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

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

Agora, para consultar a altura, use 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');
});
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)
}

As consultas podem ser ordenadas apenas de chave em chave. Chamar orderByChild() várias vezes na mesma consulta gera um erro.

Ordenação por chave

Também é possível ordenar os nós pelas 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);
});
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)

Ordenação por valor

É possível ordenar os nós pelo valor das chaves filho usando o método orderByValue(). Vamos supor que os dinossauros estejam participando de uma olimpíada jurássica e que estamos monitorando as pontuações no seguinte formato:

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

Para ordenar os dinossauros por pontuação, podemos criar 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());
  });
});
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))
Go
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 ver uma explicação sobre como os valores de null, booleano, string e objeto são classificados ao usar orderByValue().

Consultas complexas

Agora que está claro como os dados são ordenados, use os métodos de limite ou intervalo descritos abaixo para criar consultas mais complexas.

Consultas de limite

As consultas limitToFirst() e limitToLast() são usadas para definir um número máximo de filhos a serem sincronizados para um determinado callback. Caso um limite de 100 seja definido, apenas até 100 eventos child_added serão recebidos inicialmente. Se houver menos de 100 mensagens armazenadas no banco de dados, um evento child_added será disparado para cada mensagem. No entanto, se houver mais de 100 mensagens, apenas um evento child_added será recebido para 100 delas. Estas são as primeiras 100 mensagens ordenadas se limitToFirst() estiver em uso ou as últimas 100 mensagens ordenadas se limitToLast() estiver em uso. Conforme os itens forem alterados, você receberá eventos child_added para itens que entram na consulta e eventos child_removed para itens que saem dela, para que o número total permaneça em 100.

Usando o banco de dados de fatos de dinossauros e orderByChild(), é possível 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);
});
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())
}

O callback child_added é acionado exatamente duas vezes, a não ser que haja menos de dois dinossauros armazenados no banco de dados. Ele também será acionado para todo dinossauro novo, com peso maior, adicionado ao banco de dados. No Python, a consulta retorna diretamente um OrderedDict contendo os dois dinossauros mais pesados.

Do mesmo modo, é possível encontrar os dois dinossauros mais baixos 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);
});
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())
}

O callback child_added é acionado exatamente duas vezes, a não ser 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, porque um novo dinossauro passará a ser o segundo mais baixo. No Python, a consulta retorna diretamente um OrderedDict contendo os dinossauros mais baixos.

Também é possível realizar consultas limitadas com orderByValue(). Se quiser criar um placar com os três dinossauros com a maior pontuação, faça 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());
  });
});
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 de intervalo

Usar startAt(), endAt() e equalTo() permite escolher pontos arbitrários de início e término para as consultas. Por exemplo, para encontrar todos os dinossauros com pelo menos três metros de altura, é possível 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);
});
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())
}

É possível usar endAt() para encontrar todos os dinossauros que tenham nomes que venham alfabeticamente antes de 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);
});
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())
}

É possível combinar startAt() e endAt() para limitar as duas extremidades da consulta. Este exemplo descobre 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);
});
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())
}

O método equalTo() permite filtrar com base em correspondências exatas. Como nas outras consultas de intervalo, ele será acionado 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);
});
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())
}

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

Como reunir tudo

É possível combinar todas essas técnicas para criar consultas complexas. Por exemplo, você pode descobrir o nome do primeiro dinossauro mais baixo 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');
      }
    });
});
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")
}

Ordenação dos dados

Esta seção explica a ordenação dos seus dados usando cada uma das quatro funções de ordenação.

orderByChild

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

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

orderByKey

Ao usar orderByKey() para classificar seus dados, eles são retornados em ordem crescente por chave da forma mostrada a seguir. Lembre-se de que as chaves só podem ser strings.

  1. Filhos com uma chave que possa ser analisada como um número inteiro de 32 bits vêm primeiro, ordenados em ordem crescente.
  2. Filhos com um valor de string como chave são os próximos, ordenados alfabeticamente em ordem crescente.

orderByValue

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