Pobieram dane

Ten dokument zawiera podstawowe informacje o pobieraniu danych z bazy danych, porządkowaniu danych i wykonywaniu prostych zapytań na danych. Pobieranie danych w pakiecie Admin SDK jest implementowane nieco inaczej w różnych językach programowania.

  1. Detektory asynchroniczne: dane przechowywane w Firebase Realtime Database są pobierane przez dołączenie detektora asynchronicznego do odwołania do bazy danych. Detektor jest aktywowany raz dla stanu początkowego danych i ponownie za każdym razem, gdy dane się zmienią. Detektor zdarzeń może odbierać kilka różnych typów zdarzeń. Ten tryb pobierania danych jest obsługiwany w pakietach Admin SDK dla Javy, Node.js i Pythona.
  2. Odczyty blokujące: dane przechowywane w Firebase Realtime Database są pobierane przez wywołanie metody blokującej w odniesieniu do bazy danych, która zwraca dane przechowywane w odniesieniu. Każde wywołanie metody jest operacją jednorazową. Oznacza to, że pakiet SDK nie rejestruje żadnych wywołań zwrotnych, które nasłuchują kolejnych aktualizacji danych. Ten model pobierania danych jest obsługiwany w pakietach Admin SDK w językach Python i Go.

Pierwsze kroki

Wróćmy do przykładu z blogiem z poprzedniego artykułu, aby dowiedzieć się, jak odczytywać dane z bazy danych Firebase. Przypominamy, że posty na blogu w przykładowej aplikacji są przechowywane pod adresem URL bazy danych https://docs-examples.firebaseio.com/server/saving-data/fireblog/posts.json. Aby odczytać dane posta, możesz wykonać te czynności:

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

Jeśli uruchomisz powyższy kod, w konsoli zobaczysz obiekt zawierający wszystkie Twoje posty. W przypadku Node.js i Javy funkcja nasłuchująca jest wywoływana za każdym razem, gdy do bazy danych dodawane są nowe dane, i nie trzeba pisać żadnego dodatkowego kodu, aby tak się działo.

W językach Java i Node.js funkcja wywołania zwrotnego otrzymuje obiekt DataSnapshot, który jest zrzutem danych. Zrzut to obraz danych w określonym odwołaniu do bazy danych w danym momencie. Wywołanie metody val() / getValue() na zrzucie zwraca reprezentację danych w postaci obiektu w danym języku. Jeśli w lokalizacji odwołania nie ma danych, wartość migawki to null. Metoda get() w Pythonie zwraca bezpośrednio reprezentację danych w Pythonie. Funkcja Get() w Go przekształca dane do określonej struktury danych.

Zwróć uwagę, że w powyższym przykładzie użyliśmy typu zdarzenia value, który odczytuje całą zawartość odwołania do bazy danych Firebase, nawet jeśli zmienił się tylko jeden element danych. value to jeden z 5 różnych typów zdarzeń wymienionych poniżej, których możesz używać do odczytywania danych z bazy danych.

Odczytywanie typów zdarzeń w językach Java i Node.js

Wartość

Zdarzenie value służy do odczytywania statycznego zrzutu zawartości w danej ścieżce bazy danych, w stanie, w jakim znajdowała się w momencie zdarzenia odczytu. Jest wywoływana raz z danymi początkowymi i ponownie za każdym razem, gdy dane się zmieniają. Do wywołania zwrotnego zdarzenia przekazywany jest zrzut zawierający wszystkie dane w tej lokalizacji, w tym dane podrzędne. W powyższym przykładzie kodu funkcja value zwraca wszystkie posty na blogu w aplikacji. Za każdym razem, gdy zostanie dodany nowy post na blogu, wywołanie zwrotne zwróci wszystkie posty.

Dodano dziecko

Zdarzenie child_added jest zwykle używane podczas pobierania listy elementów z bazy danych. W przeciwieństwie do value, która zwraca całą zawartość lokalizacji, funkcja child_added jest wywoływana raz dla każdego istniejącego elementu podrzędnego, a następnie za każdym razem, gdy do określonej ścieżki zostanie dodany nowy element podrzędny. Do wywołania zwrotnego zdarzenia przekazywany jest zrzut zawierający dane nowego elementu podrzędnego. W celu uporządkowania przekazywany jest też drugi argument zawierający klucz poprzedniego elementu podrzędnego.

Jeśli chcesz pobierać tylko dane o każdym nowym poście dodanym do aplikacji blogowej, możesz użyć 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);
});

W tym przykładzie zrzut będzie zawierać obiekt z pojedynczym postem na blogu. Pakiet SDK konwertuje posty na obiekty, pobierając wartość, więc masz dostęp do właściwości autora i tytułu posta, wywołując odpowiednio author i title. Masz też dostęp do identyfikatora poprzedniego posta z drugiego argumentu prevChildKey.

Zmiana podrzędna

Zdarzenie child_changed jest wywoływane za każdym razem, gdy węzeł podrzędny zostanie zmodyfikowany. Obejmuje to wszelkie modyfikacje elementów podrzędnych węzła podrzędnego. Zwykle jest używany w połączeniu z child_added i child_removed, aby reagować na zmiany na liście elementów. Zrzut przekazany do wywołania zwrotnego zdarzenia zawiera zaktualizowane dane elementu podrzędnego.

Możesz użyć child_changed, aby odczytać zaktualizowane dane postów na blogu, gdy zostaną one zmodyfikowane:

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

Dziecko zostało usunięte

Zdarzenie child_removed jest wywoływane, gdy usunięty zostanie element podrzędny bezpośrednio pod nim. Zazwyczaj jest ona używana w połączeniu z właściwościami child_added i child_changed. Zrzut przekazany do wywołania zwrotnego zdarzenia zawiera dane usuniętego elementu podrzędnego.

W przykładzie z blogiem możesz użyć child_removed, aby zarejestrować powiadomienie o usuniętym poście w konsoli:

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

Przeniesiono dziecko

Zdarzenie child_moved jest używane podczas pracy z uporządkowanymi danymi, które zostały opisane w następnej sekcji.

Gwarancje dotyczące wydarzeń

Baza danych Firebase zapewnia kilka ważnych gwarancji dotyczących zdarzeń:

Gwarancje dotyczące zdarzeń bazy danych
Zdarzenia będą zawsze wywoływane, gdy zmieni się stan lokalny.
Zdarzenia zawsze będą odzwierciedlać prawidłowy stan danych, nawet w przypadkach, gdy operacje lokalne lub czas powodują tymczasowe różnice, np. w przypadku tymczasowej utraty połączenia sieciowego.
Zapisy z jednego klienta będą zawsze zapisywane na serwerze i rozpowszechniane wśród innych użytkowników w odpowiedniej kolejności.
Zdarzenia wartości są zawsze wywoływane jako ostatnie i gwarantują, że zawierają aktualizacje ze wszystkich innych zdarzeń, które miały miejsce przed utworzeniem tego zrzutu.

Zdarzenia wartości są zawsze wywoływane na końcu, więc ten przykład zawsze będzie działać:

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

Odłączanie wywołań zwrotnych

Wywołania zwrotne są usuwane przez określenie typu zdarzenia i funkcji wywołania zwrotnego, która ma zostać usunięta, na przykład:

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

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

Jeśli do funkcji on() przekazano kontekst zakresu, należy go przekazać podczas odłączania wywołania zwrotnego:

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

Jeśli chcesz usunąć wszystkie połączenia zwrotne w lokalizacji, możesz wykonać te czynności:

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

Jednorazowy odczyt danych

W niektórych przypadkach może być przydatne jednorazowe wywołanie funkcji i natychmiastowe jej usunięcie. Aby ułatwić Ci to zadanie, utworzyliśmy funkcję pomocniczą:

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

Wysyłanie zapytania o dane

Dzięki zapytaniom do bazy danych Firebase możesz selektywnie pobierać dane na podstawie różnych czynników. Aby utworzyć zapytanie w bazie danych, najpierw określ, jak chcesz uporządkować dane, używając jednej z funkcji porządkowania: orderByChild(), orderByKey() lub orderByValue(). Możesz je połączyć z 5 innymi metodami, aby przeprowadzać złożone zapytania: limitToFirst(), limitToLast(), startAt(), endAt() i equalTo().

Ponieważ wszyscy w Firebase uważamy, że dinozaury są całkiem fajne, użyjemy fragmentu przykładowej bazy danych z faktami o dinozaurach, aby pokazać, jak można wysyłać zapytania do danych w bazie danych Firebase:

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

Dane możesz porządkować na 3 sposoby: według klucza podrzędnego, klucza lub wartości. Podstawowe zapytanie do bazy danych zaczyna się od jednej z tych funkcji porządkowania, które wyjaśniamy poniżej.

Porządkowanie według określonego klucza podrzędnego

Możesz uporządkować węzły według wspólnego klucza podrzędnego, przekazując ten klucz do orderByChild(). Aby na przykład odczytać wszystkie dinozaury uporządkowane według wysokości, możesz wykonać te czynności:

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(f'{key} was {val} meters tall')
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)
}

Wszystkie węzły, które nie mają klucza podrzędnego, o który pytamy, są sortowane z wartością null, co oznacza, że będą na początku listy. Szczegółowe informacje o porządkowaniu danych znajdziesz w sekcji dotyczącej porządkowania danych.

Zapytania można też porządkować według głęboko zagnieżdżonych elementów podrzędnych, a nie tylko elementów podrzędnych o 1 poziom niżej. Jest to przydatne, jeśli masz dane zagnieżdżone na wielu poziomach, np.:

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

Aby teraz sprawdzić wysokość, możesz użyć pełnej ścieżki do obiektu, a nie pojedynczego klucza:

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(f'{key} was {val} meters tall')
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)
}

Zapytania mogą być sortowane tylko według jednego klucza naraz. Wielokrotne wywołanie metody orderByChild() w tym samym zapytaniu powoduje błąd.

Porządkowanie według klucza

Możesz też porządkować węzły według kluczy, używając metody orderByKey(). Poniższy przykład odczytuje wszystkie dinozaury w porządku alfabetycznym:

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)

Porządkowanie według wartości

Możesz porządkować węzły według wartości ich kluczy podrzędnych za pomocą metody orderByValue(). Załóżmy, że dinozaury biorą udział w zawodach sportowych, a Ty śledzisz ich wyniki w tym formacie:

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

Aby posortować dinozaury według wyniku, możesz utworzyć takie zapytanie:

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(f'The {key} dinosaur\'s score is {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)
}

W sekcji Jak dane są porządkowane znajdziesz wyjaśnienie, jak wartości null, wartości logiczne, ciągi znaków i obiekty są sortowane, gdy używasz orderByValue().

Złożone zapytania

Teraz, gdy już wiesz, jak są uporządkowane dane, możesz użyć opisanych poniżej metod limit lub range, aby tworzyć bardziej złożone zapytania.

Ogranicz zapytania

Zapytania limitToFirst() i limitToLast() służą do ustawiania maksymalnej liczby elementów podrzędnych, które mają być synchronizowane w przypadku danego wywołania zwrotnego. Jeśli ustawisz limit na 100, początkowo otrzymasz tylko maksymalnie 100 zdarzeń child_added. Jeśli w bazie danych masz mniej niż 100 wiadomości, dla każdej z nich zostanie wywołane zdarzenie child_added. Jeśli jednak masz ponad 100 wiadomości, zdarzenie child_added otrzymasz tylko w przypadku 100 z nich. Są to pierwsze 100 uporządkowanych wiadomości, jeśli używasz limitToFirst(), lub ostatnie 100 uporządkowanych wiadomości, jeśli używasz limitToLast(). W miarę zmian elementów będziesz otrzymywać zdarzenia child_added dotyczące elementów, które zaczęły spełniać warunki zapytania, oraz zdarzenia child_removed dotyczące elementów, które przestały je spełniać, dzięki czemu łączna liczba elementów będzie wynosić 100.

Korzystając z bazy danych o dinozaurach i funkcji orderByChild(), możesz znaleźć 2 najcięższe dinozaury:

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

Wywołanie zwrotne child_added jest wywoływane dokładnie 2 razy, chyba że w bazie danych jest mniej niż 2 dinozaury. Będzie też uruchamiana za każdym razem, gdy do bazy danych zostanie dodany nowy, cięższy dinozaur. W Pythonie zapytanie bezpośrednio zwraca obiekt OrderedDict zawierający 2 najcięższe dinozaury.

Podobnie możesz znaleźć 2 najniższe dinozaury, używając funkcji 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())
}

Wywołanie zwrotne child_added jest wywoływane dokładnie 2 razy, chyba że w bazie danych jest mniej niż 2 dinozaury. Zostanie on też uruchomiony ponownie, jeśli jeden z pierwszych 2 dinozaurów zostanie usunięty z bazy danych, ponieważ nowy dinozaur będzie teraz drugim najniższym. W języku Python zapytanie bezpośrednio zwraca obiekt OrderedDict zawierający najkrótsze dinozaury.

Możesz też wykonywać zapytania dotyczące limitów za pomocą orderByValue(). Jeśli chcesz utworzyć tabelę wyników z 3 dinozaurami, które zdobyły najwięcej punktów, możesz wykonać te czynności:

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(f'The {key} dinosaur\'s score is {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)
}

Zapytania dotyczące zakresu

Użycie parametrów startAt(), endAt() i equalTo() pozwala wybrać dowolne punkty początkowe i końcowe zapytań. Jeśli na przykład chcesz znaleźć wszystkie dinozaury, które mają co najmniej 3 metry wysokości, możesz połączyć metody orderByChild() i 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())
}

Możesz użyć endAt(), aby znaleźć wszystkie dinozaury, których nazwy są przed pterodaktylem w porządku leksykograficznym:

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

Możesz połączyć startAt() i endAt(), aby ograniczyć oba końce zapytania. W tym przykładzie wyszukiwane są wszystkie dinozaury, których nazwa zaczyna się na literę „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('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())
}

Metoda equalTo() umożliwia filtrowanie na podstawie dopasowań ścisłych. Podobnie jak w przypadku innych zapytań o zakres, zostanie ono wywołane dla każdego pasującego węzła podrzędnego. Możesz na przykład użyć tego zapytania, aby znaleźć wszystkie dinozaury o wysokości 25 metrów:

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

Zapytania o zakres są też przydatne, gdy musisz podzielić dane na strony.

Łączę wszystko w całość

Możesz połączyć wszystkie te techniki, aby tworzyć złożone zapytania. Możesz na przykład znaleźć nazwę dinozaura, który jest tylko trochę krótszy od stegozaura:

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(f'The dinosaur just shorter than the stegosaurus is {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")
}

Jak są uporządkowane dane

W tej sekcji wyjaśniamy, jak są porządkowane dane, gdy używasz każdej z 4 funkcji porządkowania.

orderByChild

Gdy używasz metody orderByChild(), dane zawierające określony klucz podrzędny są porządkowane w ten sposób:

  1. Dzieci z wartością null dla określonego klucza podrzędnego są wyświetlane jako pierwsze.
  2. Następnie wyświetlane są elementy podrzędne z wartością false dla określonego klucza podrzędnego. Jeśli wiele elementów podrzędnych ma wartość false, są one sortowane leksykograficznie według klucza.
  3. Następnie wyświetlane są elementy podrzędne z wartością true dla określonego klucza podrzędnego. Jeśli wiele elementów podrzędnych ma wartość true, są one sortowane leksykograficznie według klucza.
  4. Następnie wyświetlane są dzieci z wartością liczbową, posortowane w kolejności rosnącej. Jeśli kilka węzłów podrzędnych ma tę samą wartość liczbową dla określonego węzła podrzędnego, są one sortowane według klucza.
  5. Ciągi znaków występują po liczbach i są sortowane leksykograficznie w kolejności rosnącej. Jeśli wiele elementów podrzędnych ma tę samą wartość dla określonego węzła podrzędnego, są one uporządkowane leksykograficznie według klucza.
  6. Obiekty są na końcu i są sortowane leksykograficznie według klucza w kolejności rosnącej.

orderByKey

Gdy używasz orderByKey() do sortowania danych, są one zwracane w kolejności rosnącej według klucza w następujący sposób. Pamiętaj, że klucze mogą być tylko ciągami znaków.

  1. Najpierw wyświetlane są elementy podrzędne z kluczem, który można przeanalizować jako 32-bitową liczbę całkowitą, posortowane w kolejności rosnącej.
  2. Następnie wyświetlane są elementy podrzędne z wartością ciągu znaków jako kluczem, posortowane leksykograficznie w porządku rosnącym.

orderByValue

W przypadku używania orderByValue() elementy podrzędne są uporządkowane według wartości. Kryteria porządkowania są takie same jak w przypadku orderByChild(), ale zamiast wartości określonego klucza podrzędnego używana jest wartość węzła.