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

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

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

Начиная

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

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())
Идти
// 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 :

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

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

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

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

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

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

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

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

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

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

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

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

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

Отключение обратных вызовов

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

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

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

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

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

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

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

Считывание данных один раз

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

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())
Идти
// 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() . Например, чтобы прочитать всех динозавров, упорядоченных по высоте, вы можете сделать следующее:

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')
Идти
// 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
    }
  }
}

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

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')
Идти
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() . В следующем примере считываются все динозавры в алфавитном порядке:

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)
Идти
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
  }
}

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

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}')
Идти
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() , вы можете найти двух самых тяжелых динозавров:

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)
Идти
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() :

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)
Идти
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() . Если вы хотите создать таблицу лидеров с тремя динозаврами, набравшими наибольшее количество очков в играх про динозавров, вы можете сделать следующее:

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}')
Идти
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() :

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)
Идти
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() , чтобы найти всех динозавров, чьи названия лексикографически предшествуют названию птеродактиля:

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)
Идти
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":

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)
Идти
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 метров:

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)
Идти
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())
}

Запросы с диапазоном значений также полезны, когда необходимо разбить данные на страницы.

Собираем всё воедино

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

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')
Идти
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

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