Извлечение данных

В этом документе рассматриваются основы извлечения данных из базы данных, порядок их упорядочивания и выполнение простых запросов к данным. Извлечение данных в Admin SDK реализовано несколько по-разному в разных языках программирования.

  1. Асинхронные прослушиватели: данные, хранящиеся в Firebase Realtime Database извлекаются путём присоединения асинхронного прослушивателя к ссылке на базу данных. Прослушиватель активируется один раз для получения начального состояния данных и повторно при каждом их изменении. Прослушиватель событий может получать несколько различных типов событий . Этот режим извлечения данных поддерживается в Java, Node.js и Python Admin SDK.
  2. Блокирующие чтения: данные, хранящиеся в Firebase Realtime Database извлекаются путём вызова блокирующего метода для ссылки на базу данных, который возвращает данные, хранящиеся по ссылке. Каждый вызов метода — это однократная операция. Это означает, что SDK не регистрирует обратные вызовы, прослушивающие последующие обновления данных. Эта модель извлечения данных поддерживается в Python и Go Admin SDK.

Начиная

Давайте вернёмся к примеру блога из предыдущей статьи, чтобы понять, как читать данные из базы данных Firebase. Напомним, что записи блога в примере приложения хранятся по адресу базы данных https://docs-examples.firebaseio.com/server/saving-data/fireblog/posts.json . Чтобы прочитать данные из записи, выполните следующие действия:

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());
  }
});
// 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);
}); 
# 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())
// 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)
}

Если вы запустите приведённый выше код, вы увидите объект, содержащий все ваши записи, выведенные на консоль. В случае Node.js и Java функция-слушатель вызывается каждый раз при добавлении новых данных в базу данных, и вам не нужно писать дополнительный код для этого.

В Java и Node.js функция обратного вызова получает DataSnapshot — моментальный снимок данных. Снимок — это изображение данных по указанной ссылке в базе данных в определенный момент времени. Вызов val() / getValue() для снимка возвращает объектное представление данных, специфичное для конкретного языка. Если по адресу ссылки данные отсутствуют, значение снимка равно null . Метод get() в Python напрямую возвращает представление данных в Python. Функция Get() в Go демаршалирует данные в заданную структуру данных.

Обратите внимание, что в приведенном выше примере мы использовали тип события value , который считывает все содержимое ссылки базы данных Firebase, даже если изменился только один фрагмент данных. value — это один из пяти различных типов событий, перечисленных ниже, которые можно использовать для чтения данных из базы данных.

Прочитайте типы событий в Java и Node.js

Ценить

Событие value используется для чтения статического снимка содержимого по заданному пути базы данных в том виде, в каком оно существовало на момент чтения. Оно срабатывает один раз с исходными данными и затем снова при каждом изменении данных. Функция обратного вызова события передает снимок, содержащий все данные в этом месте, включая дочерние данные. В приведенном выше примере кода value вернула все записи блога в вашем приложении. При каждом добавлении новой записи блога функция обратного вызова будет возвращать все записи.

Ребенок добавлен

Событие child_added обычно используется при извлечении списка элементов из базы данных. В отличие от value , которое возвращает всё содержимое расположения, child_added срабатывает один раз для каждого существующего дочернего элемента, а затем снова каждый раз при добавлении нового дочернего элемента в указанный путь. Обратному вызову события передаётся снимок, содержащий данные нового дочернего элемента. Для упорядочивания также передаётся второй аргумент, содержащий ключ предыдущего дочернего элемента.

Если вы хотите получить только данные о каждой новой записи, добавленной в ваше приложение для ведения блога, вы можете использовать child_added :

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

В этом примере снимок будет содержать объект с отдельной записью блога. Поскольку SDK преобразует записи в объекты, извлекая значение, вы получаете доступ к свойствам автора и заголовка записи, вызывая методы author и title соответственно. Вы также получаете доступ к идентификатору предыдущей записи из второго аргумента prevChildKey .

Ребенок изменился

Событие child_changed срабатывает при каждом изменении дочернего узла. Это включает любые изменения потомков дочернего узла. Обычно оно используется вместе с child_added и child_removed для реагирования на изменения в списке элементов. Снимок, передаваемый в функцию обратного вызова события, содержит обновлённые данные для дочернего узла.

Вы можете использовать child_changed для чтения обновленных данных в записях блога при их редактировании:

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) {}
});
// 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 срабатывает при удалении непосредственного дочернего элемента. Обычно оно используется вместе с child_added и child_changed . Снимок, передаваемый в обратный вызов события, содержит данные об удаленном дочернем элементе.

В примере с блогом вы можете использовать child_removed для вывода уведомления об удаленной записи в консоль:

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) {}
});
// 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 используется при работе с упорядоченными данными, что рассматривается в следующем разделе .

Гарантии мероприятий

База данных Firebase дает несколько важных гарантий относительно событий:

Гарантии событий базы данных
События всегда будут инициироваться при изменении локального состояния.
События всегда в конечном итоге будут отражать правильное состояние данных, даже в случаях, когда локальные операции или время вызывают временные различия, например, при временной потере сетевого соединения.
Записи от одного клиента всегда будут записываться на сервер и передаваться другим пользователям в порядке очереди.
События значений всегда инициируются последними и гарантированно содержат обновления от любых других событий, которые произошли до того, как был сделан этот снимок.

Поскольку события значений всегда инициируются последними, следующий пример всегда будет работать:

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

Отсоединение обратных вызовов

Обратные вызовы удаляются путем указания типа события и функции обратного вызова, которую необходимо удалить, например:

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

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

Если вы передали контекст области действия в on() , его необходимо передать при отсоединении обратного вызова:

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

Если вы хотите удалить все обратные вызовы в определенном месте, вы можете сделать следующее:

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

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

Чтение данных один раз

В некоторых случаях может быть полезно, чтобы обратный вызов был вызван один раз и немедленно удален. Для упрощения этого мы создали вспомогательную функцию:

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

  @Override
  public void onCancelled(DatabaseError databaseError) {
    // ...
  }
});
ref.once('value', (data) => {
  // do some stuff once
});
# 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())
// 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)
}

Запрос данных

Запросы к базе данных Firebase позволяют выборочно извлекать данные на основе различных факторов. Чтобы создать запрос к базе данных, сначала укажите желаемый порядок упорядочивания данных, используя одну из функций упорядочивания: orderByChild() , orderByKey() или orderByValue() . Затем вы можете комбинировать их с пятью другими методами для выполнения сложных запросов: limitToFirst() , limitToLast() , startAt() , endAt() и equalTo() .

Поскольку все мы в Firebase считаем, что динозавры — это очень круто, мы воспользуемся фрагментом из примера базы данных с фактами о динозаврах, чтобы продемонстрировать, как можно запрашивать данные в базе данных Firebase.

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

Данные можно упорядочить тремя способами: по дочернему ключу , по ключу или по значению . Базовый запрос к базе данных начинается с одной из этих функций упорядочивания, каждая из которых описана ниже.

Упорядочивание по указанному дочернему ключу

Вы можете упорядочить узлы по общему дочернему ключу, передав этот ключ в orderByChild() . Например, чтобы прочитать всех динозавров, отсортированных по высоте, можно сделать следующее:

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

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

ref.orderByChild('height').on('child_added', (snapshot) => {
  console.log(snapshot.key + ' was ' + snapshot.val().height + ' meters tall');
});
ref = db.reference('dinosaurs')
snapshot = ref.order_by_child('height').get()
for key, val in snapshot.items():
    print(f'{key} was {val} meters tall')
// 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)
}

Любой узел, не имеющий дочернего ключа, к которому мы обращаемся, сортируется со значением null , то есть он будет первым в упорядочении. Подробнее об упорядочивании данных см. в разделе «Упорядочивание данных» .

Запросы также можно сортировать по глубоко вложенным дочерним элементам, а не только по элементам на один уровень ниже. Это полезно, если у вас есть глубоко вложенные данные, например:

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

Теперь для запроса высоты можно использовать полный путь к объекту, а не один ключ:

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

  // ...
});
const ref = db.ref('dinosaurs');
ref.orderByChild('dimensions/height').on('child_added', (snapshot) => {
  console.log(snapshot.key + ' was ' + snapshot.val().height + ' meters tall');
});
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')
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)
}

Запросы можно сортировать только по одному ключу за раз. Многократный вызов orderByChild() для одного запроса приводит к ошибке.

Заказ по ключу

Вы также можете упорядочить узлы по их ключам, используя метод orderByKey() . Следующий пример считывает всех динозавров в алфавитном порядке:

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

  // ...
});
var ref = db.ref('dinosaurs');
ref.orderByKey().on('child_added', (snapshot) => {
  console.log(snapshot.key);
});
ref = db.reference('dinosaurs')
snapshot = ref.order_by_key().get()
print(snapshot)
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)

Сортировка по значению

Вы можете упорядочить узлы по значению их дочерних ключей с помощью метода orderByValue() . Предположим, динозавры участвуют в спортивных соревнованиях, и вы отслеживаете их результаты в следующем формате:

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

Чтобы отсортировать динозавров по их оценкам, можно составить следующий запрос:

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

  // ...
});
const scoresRef = db.ref('scores');
scoresRef.orderByValue().on('value', (snapshot) => {
  snapshot.forEach((data) => {
    console.log('The ' + data.key + ' dinosaur\'s score is ' + data.val());
  });
});
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}')
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)
}

См. раздел «Как упорядочиваются данные», где объясняется, как сортируются значения null , boolean, string и object при использовании orderByValue() .

Сложные запросы

Теперь, когда понятно, как упорядочены ваши данные, вы можете использовать описанные ниже методы ограничения или диапазона для построения более сложных запросов.

Ограничить запросы

Запросы limitToFirst() и limitToLast() используются для задания максимального количества дочерних элементов, синхронизируемых для данного обратного вызова. Если установить ограничение в 100, изначально будет получено не более 100 событий child_added . Если в базе данных хранится менее 100 сообщений, событие child_added будет срабатывать для каждого из них. Однако, если сообщений больше 100, событие child_added будет срабатывать только для 100 из них. Это первые 100 упорядоченных сообщений, если используется limitToFirst() , или последние 100 упорядоченных сообщений, если используется limitToLast() . По мере изменения элементов вы будете получать события child_added для элементов, входящих в запрос, и события child_removed для элементов, выходящих из него, так что общее количество останется равным 100.

Используя базу данных фактов о динозаврах и orderByChild() , вы можете найти двух самых тяжелых динозавров:

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

  // ...
});
const ref = db.ref('dinosaurs');
ref.orderByChild('weight').limitToLast(2).on('child_added', (snapshot) => {
  console.log(snapshot.key);
});
ref = db.reference('dinosaurs')
snapshot = ref.order_by_child('weight').limit_to_last(2).get()
for key in snapshot:
    print(key)
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())
}

Обратный вызов child_added срабатывает ровно два раза, если в базе данных хранится не менее двух динозавров. Он также будет срабатывать при добавлении в базу данных каждого нового, более тяжёлого динозавра. В Python запрос напрямую возвращает OrderedDict содержащий данные о двух самых тяжёлых динозаврах.

Аналогично вы можете найти двух самых коротких динозавров, используя limitToFirst() :

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

  // ...
});
const ref = db.ref('dinosaurs');
ref.orderByChild('height').limitToFirst(2).on('child_added', (snapshot) => {
  console.log(snapshot.key);
});
ref = db.reference('dinosaurs')
snapshot = ref.order_by_child('height').limit_to_first(2).get()
for key in snapshot:
    print(key)
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())
}

Обратный вызов child_added срабатывает ровно два раза, если в базе данных хранится не менее двух динозавров. Он также сработает снова, если один из первых двух динозавров будет удалён из базы данных, поскольку новый динозавр теперь будет вторым по длине. В Python запрос напрямую возвращает OrderedDict , содержащий самые короткие динозавры.

Вы также можете выполнять запросы с лимитами с помощью orderByValue() . Если вы хотите создать таблицу лидеров с тремя самыми результативными динозаврами из мира диноспорта, вы можете сделать следующее:

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

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

Запросы по диапазону

Использование startAt() , endAt() и equalTo() позволяет выбирать произвольные начальные и конечные точки для запросов. Например, если вы хотите найти всех динозавров ростом не менее трёх метров, вы можете объединить orderByChild() и startAt() :

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

  // ...
});
const ref = db.ref('dinosaurs');
ref.orderByChild('height').startAt(3).on('child_added', (snapshot) => {
  console.log(snapshot.key);
});
ref = db.reference('dinosaurs')
snapshot = ref.order_by_child('height').start_at(3).get()
for key in snapshot:
    print(key)
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())
}

Вы можете использовать endAt() чтобы найти всех динозавров, названия которых лексикографически предшествуют птеродактилю:

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

  // ...
});
const ref = db.ref('dinosaurs');
ref.orderByKey().endAt('pterodactyl').on('child_added', (snapshot) => {
  console.log(snapshot.key);
});
ref = db.reference('dinosaurs')
snapshot = ref.order_by_key().end_at('pterodactyl').get()
for key in snapshot:
    print(key)
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())
}

Вы можете объединить startAt() и endAt() чтобы ограничить оба конца запроса. Следующий пример находит всех динозавров, названия которых начинаются с буквы «b»:

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

  // ...
});
var ref = db.ref('dinosaurs');
ref.orderByKey().startAt('b').endAt('b\uf8ff').on('child_added', (snapshot) => {
  console.log(snapshot.key);
});
ref = db.reference('dinosaurs')
snapshot = ref.order_by_key().start_at('b').end_at('b\uf8ff').get()
for key in snapshot:
    print(key)
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())
}

Метод equalTo() позволяет фильтровать по точным совпадениям. Как и в случае с другими запросами по диапазону, он срабатывает для каждого совпадающего дочернего узла. Например, следующий запрос можно использовать для поиска всех динозавров высотой 25 метров:

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

  // ...
});
const ref = db.ref('dinosaurs');
ref.orderByChild('height').equalTo(25).on('child_added', (snapshot) => {
  console.log(snapshot.key);
});
ref = db.reference('dinosaurs')
snapshot = ref.order_by_child('height').equal_to(25).get()
for key in snapshot:
    print(key)
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())
}

Диапазонные запросы также полезны, когда вам нужно разбить данные на страницы.

Собираем все вместе

Вы можете комбинировать все эти методы для создания сложных запросов. Например, можно найти название динозавра, которое немного короче слова «стегозавр»:

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) {
    // ...
  }
});
  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');
      }
    });
});
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')
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")
}

Как упорядочены данные

В этом разделе объясняется, как упорядочиваются ваши данные при использовании каждой из четырех функций упорядочивания.

orderByChild

При использовании orderByChild() данные, содержащие указанный дочерний ключ, упорядочиваются следующим образом:

  1. Сначала идут дочерние элементы с null значением для указанного дочернего ключа.
  2. Далее идут дочерние элементы со значением false для указанного ключа. Если значение false есть у нескольких дочерних элементов, они сортируются лексикографически по ключу.
  3. Далее идут дочерние элементы со значением true для указанного дочернего ключа. Если значение true есть у нескольких дочерних элементов, они сортируются лексикографически по ключу.
  4. Далее следуют дочерние элементы с числовым значением, отсортированные по возрастанию. Если несколько дочерних элементов имеют одинаковое числовое значение для указанного дочернего узла, они сортируются по ключу.
  5. Строки следуют за числами и сортируются лексикографически по возрастанию. Если несколько дочерних элементов имеют одинаковое значение для указанного дочернего узла, они упорядочиваются лексикографически по ключу.
  6. Объекты идут последними и сортируются лексикографически по ключу в порядке возрастания.

orderByKey

При использовании orderByKey() для сортировки данных данные возвращаются в порядке возрастания ключа, как показано ниже. Имейте в виду, что ключи могут быть только строками.

  1. Сначала идут потомки с ключом, который можно проанализировать как 32-битное целое число, отсортированные по возрастанию.
  2. Далее следуют дочерние элементы со строковым значением в качестве ключа, отсортированные лексикографически в порядке возрастания.

заказ по значению

При использовании orderByValue() дочерние элементы сортируются по их значению. Критерий упорядочивания тот же, что и в orderByChild() , за исключением того, что вместо значения указанного дочернего ключа используется значение узла.