Оптимизируйте свои подборки
Сохраняйте и классифицируйте контент в соответствии со своими настройками.
В этом документе описаны основы получения данных из базы данных, порядок их упорядочения и выполнение простых запросов к данным. Получение данных в Admin SDK реализовано несколько по-разному в разных языках программирования.
Асинхронные прослушиватели. Данные, хранящиеся в Firebase Realtime Database извлекаются путем прикрепления асинхронного прослушивателя к ссылке на базу данных. Прослушиватель запускается один раз для исходного состояния данных и снова при каждом изменении данных. Слушатель событий может получать несколько различных типов событий . Этот режим получения данных поддерживается в Java, Node.js и Python Admin SDK.
Блокирующее чтение: данные, хранящиеся в Firebase Realtime Database извлекаются путем вызова метода блокировки для ссылки на базу данных, который возвращает данные, хранящиеся по ссылке. Каждый вызов метода является одноразовой операцией. Это означает, что SDK не регистрирует никаких обратных вызовов, которые прослушивают последующие обновления данных. Эта модель получения данных поддерживается в SDK Python и Go Admin.
Начиная
Давайте вернемся к примеру с блогом из предыдущей статьи, чтобы понять, как читать данные из базы данных Firebase. Напомним, что сообщения блога в примере приложения хранятся по URL-адресу базы данных https://docs-examples.firebaseio.com/server/saving-data/fireblog/posts.json . Чтобы прочитать данные публикации, вы можете сделать следующее:
Ява
publicstaticclassPost{publicStringauthor;publicStringtitle;publicPost(Stringauthor,Stringtitle){// ...}}// Get a reference to our postsfinalFirebaseDatabasedatabase=FirebaseDatabase.getInstance();DatabaseReferenceref=database.getReference("server/saving-data/fireblog/posts");// Attach a listener to read the data at our posts referenceref.addValueEventListener(newValueEventListener(){@OverridepublicvoidonDataChange(DataSnapshotdataSnapshot){Postpost=dataSnapshot.getValue(Post.class);System.out.println(post);}@OverridepublicvoidonCancelled(DatabaseErrordatabaseError){System.out.println("The read failed: "+databaseError.getCode());}});
Node.js
// Get a database reference to our postsconstdb=getDatabase();constref=db.ref('server/saving-data/fireblog/posts');// Attach an asynchronous callback to read the data at our posts referenceref.on('value',(snapshot)=>{console.log(snapshot.val());},(errorObject)=>{console.log('The read failed: '+errorObject.name);});
Питон
# Import database module.fromfirebase_adminimportdb# Get a database reference to our postsref=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.typePoststruct{Authorstring`json:"author,omitempty"`Titlestring`json:"title,omitempty"`}// Create a database client from App.client,err:=app.Database(ctx)iferr!=nil{log.Fatalln("Error initializing database client:",err)}// Get a database reference to our postsref:=client.NewRef("server/saving-data/fireblog/posts")// Read the data at the posts reference (this is a blocking operation)varpostPostiferr:=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(newChildEventListener(){@OverridepublicvoidonChildAdded(DataSnapshotdataSnapshot,StringprevChildKey){PostnewPost=dataSnapshot.getValue(Post.class);System.out.println("Author: "+newPost.author);System.out.println("Title: "+newPost.title);System.out.println("Previous Post ID: "+prevChildKey);}@OverridepublicvoidonChildChanged(DataSnapshotdataSnapshot,StringprevChildKey){}@OverridepublicvoidonChildRemoved(DataSnapshotdataSnapshot){}@OverridepublicvoidonChildMoved(DataSnapshotdataSnapshot,StringprevChildKey){}@OverridepublicvoidonCancelled(DatabaseErrordatabaseError){}});
Node.js
// Retrieve new posts as they are added to our databaseref.on('child_added',(snapshot,prevChildKey)=>{constnewPost=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(newChildEventListener(){@OverridepublicvoidonChildAdded(DataSnapshotdataSnapshot,StringprevChildKey){}@OverridepublicvoidonChildChanged(DataSnapshotdataSnapshot,StringprevChildKey){PostchangedPost=dataSnapshot.getValue(Post.class);System.out.println("The updated post title is: "+changedPost.title);}@OverridepublicvoidonChildRemoved(DataSnapshotdataSnapshot){}@OverridepublicvoidonChildMoved(DataSnapshotdataSnapshot,StringprevChildKey){}@OverridepublicvoidonCancelled(DatabaseErrordatabaseError){}});
Node.js
// Get the data on a post that has changedref.on('child_changed',(snapshot)=>{constchangedPost=snapshot.val();console.log('The updated post title is '+changedPost.title);});
Ребенок удален
Событие child_removed запускается при удалении непосредственного дочернего элемента. Обычно он используется вместе с child_added и child_changed . Снимок, передаваемый обратному вызову события, содержит данные об удаленном дочернем элементе.
В примере с блогом вы можете использовать child_removed для вывода уведомления об удаленном сообщении на консоль:
Ява
ref.addChildEventListener(newChildEventListener(){@OverridepublicvoidonChildAdded(DataSnapshotdataSnapshot,StringprevChildKey){}@OverridepublicvoidonChildChanged(DataSnapshotdataSnapshot,StringprevChildKey){}@OverridepublicvoidonChildRemoved(DataSnapshotdataSnapshot){PostremovedPost=dataSnapshot.getValue(Post.class);System.out.println("The blog post titled "+removedPost.title+" has been deleted");}@OverridepublicvoidonChildMoved(DataSnapshotdataSnapshot,StringprevChildKey){}@OverridepublicvoidonCancelled(DatabaseErrordatabaseError){}});
Node.js
// Get a reference to our postsconstref=db.ref('server/saving-data/fireblog/posts');// Get the data on a post that has been removedref.on('child_removed',(snapshot)=>{constdeletedPost=snapshot.val();console.log('The blog post titled \''+deletedPost.title+'\' has been deleted');});
Ребенок переехал
Событие child_moved используется при работе с упорядоченными данными, о чем рассказывается в следующем разделе .
Гарантии на мероприятия
База данных Firebase дает несколько важных гарантий относительно событий:
Гарантии событий базы данных
События всегда будут запускаться при изменении локального состояния.
События всегда в конечном итоге отражают правильное состояние данных, даже в тех случаях, когда локальные операции или время вызывают временные различия, например, при временной потере сетевого подключения.
Записи от одного клиента всегда будут записываться на сервер и передаваться другим пользователям по порядку.
События значений всегда запускаются последними и гарантированно содержат обновления любых других событий, произошедших до создания этого моментального снимка.
Поскольку события значения всегда запускаются последними, следующий пример всегда будет работать:
Ява
finalAtomicIntegercount=newAtomicInteger();ref.addChildEventListener(newChildEventListener(){@OverridepublicvoidonChildAdded(DataSnapshotdataSnapshot,StringprevChildKey){// New child added, increment countintnewCount=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(newValueEventListener(){@OverridepublicvoidonDataChange(DataSnapshotdataSnapshot){longnumChildren=dataSnapshot.getChildrenCount();System.out.println(count.get()+" == "+numChildren);}@OverridepublicvoidonCancelled(DatabaseErrordatabaseError){}});
Node.js
letcount=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 pointref.once('value',(snap)=>{console.log('initial data loaded!',snap.numChildren()===count);});
Отключение обратных вызовов
Обратные вызовы удаляются путем указания типа события и удаляемой функции обратного вызова, как показано ниже:
Ява
// Create and attach listenerValueEventListenerlistener=newValueEventListener(){// ...};ref.addValueEventListener(listener);// Remove listenerref.removeEventListener(listener);
Node.js
ref.off('value',originalCallback);
Если вы передали контекст области в on() , он должен быть передан при отключении обратного вызова:
Ява
// Not applicable for Java
Node.js
ref.off('value',originalCallback,ctx);
Если вы хотите удалить все обратные вызовы в определенном месте, вы можете сделать следующее:
Ява
// No Java equivalent, listeners must be removed individually.
Node.js
// Remove all value callbacksref.off('value');// Remove all callbacks of any typeref.off();
Чтение данных один раз
В некоторых случаях может оказаться полезным, чтобы обратный вызов был вызван один раз и затем немедленно удален. Мы создали вспомогательную функцию, чтобы упростить это:
ref.once('value',(data)=>{// do some stuff once});
Питон
# Import database module.fromfirebase_adminimportdb# Get a database reference to our postsref=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)iferr!=nil{log.Fatalln("Error initializing database client:",err)}// Get a database reference to our postsref:=client.NewRef("server/saving-data/fireblog/posts")// Read the data at the posts reference (this is a blocking operation)varpostPostiferr:=ref.Get(ctx,&post);err!=nil{log.Fatalln("Error reading value:",err)}
Запрос данных
С помощью запросов к базе данных Firebase вы можете выборочно получать данные на основе различных факторов. Чтобы создать запрос в базе данных, вы начинаете с указания того, как вы хотите, чтобы ваши данные были упорядочены, используя одну из функций упорядочивания: orderByChild() , orderByKey() или orderByValue() . Затем вы можете объединить их с пятью другими методами для выполнения сложных запросов: limitToFirst() , limitToLast() , startAt() , endAt() и equalTo() .
Поскольку все мы в Firebase считаем, что динозавры — это очень круто, мы воспользуемся фрагментом из образца базы данных с фактами о динозаврах, чтобы продемонстрировать, как вы можете запрашивать данные в своей базе данных Firebase.:
Вы можете упорядочить данные тремя способами: по дочернему ключу , по ключу или по значению . Базовый запрос к базе данных начинается с одной из этих функций упорядочивания, каждая из которых описана ниже.
Заказ по указанному дочернему ключу
Вы можете упорядочить узлы по общему дочернему ключу, передав этот ключ в orderByChild() . Например, чтобы прочитать всех динозавров, упорядоченных по росту, вы можете сделать следующее:
Ява
publicstaticclassDinosaur{publicintheight;publicintweight;publicDinosaur(intheight,intweight){// ...}}finalDatabaseReferencedinosaursRef=database.getReference("dinosaurs");dinosaursRef.orderByChild("height").addChildEventListener(newChildEventListener(){@OverridepublicvoidonChildAdded(DataSnapshotdataSnapshot,StringprevChildKey){Dinosaurdinosaur=dataSnapshot.getValue(Dinosaur.class);System.out.println(dataSnapshot.getKey()+" was "+dinosaur.height+" meters tall.");}// ...});
Node.js
constref=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()forkey,valinsnapshot.items():print('{0} was {1} meters tall'.format(key,val))
Идти
// Dinosaur is a json-serializable type.typeDinosaurstruct{Heightint`json:"height"`Widthint`json:"width"`}ref:=client.NewRef("dinosaurs")results,err:=ref.OrderByChild("height").GetOrdered(ctx)iferr!=nil{log.Fatalln("Error querying database:",err)}for_,r:=rangeresults{vardDinosauriferr:=r.Unmarshal(&d);err!=nil{log.Fatalln("Error unmarshaling result:",err)}fmt.Printf("%s was %d meteres tall",r.Key(),d.Height)}
Любой узел, у которого нет дочернего ключа, к которому мы запрашиваем, сортируется со значением null , что означает, что он будет первым в порядке. Подробную информацию о том, как упорядочиваются данные, см. в разделе «Как упорядочиваются данные» .
Запросы также можно упорядочивать по глубоко вложенным дочерним элементам, а не только по дочерним элементам, расположенным на один уровень ниже. Это полезно, если у вас есть глубоко вложенные данные, например:
constref=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()forkey,valinsnapshot.items():print('{0} was {1} meters tall'.format(key,val))
Идти
ref:=client.NewRef("dinosaurs")results,err:=ref.OrderByChild("dimensions/height").GetOrdered(ctx)iferr!=nil{log.Fatalln("Error querying database:",err)}for_,r:=rangeresults{vardDinosauriferr:=r.Unmarshal(&d);err!=nil{log.Fatalln("Error unmarshaling result:",err)}fmt.Printf("%s was %d meteres tall",r.Key(),d.Height)}
Запросы могут упорядочиваться только по одному ключу за раз. Вызов orderByChild() несколько раз по одному и тому же запросу приводит к ошибке.
Заказ по ключу
Вы также можете упорядочить узлы по их ключам, используя метод orderByKey() . В следующем примере все динозавры считываются в алфавитном порядке:
Вы можете упорядочить узлы по значению их дочерних ключей, используя метод orderByValue() . Допустим, динозавры устраивают спортивные соревнования динозавров, и вы отслеживаете их результаты в следующем формате:
Чтобы отсортировать динозавров по их количеству, вы можете составить следующий запрос:
Ява
DatabaseReferencescoresRef=database.getReference("scores");scoresRef.orderByValue().addChildEventListener(newChildEventListener(){@OverridepublicvoidonChildAdded(DataSnapshotdataSnapshot,StringprevChildKey){System.out.println("The "+dataSnapshot.getKey()+" score is "+dataSnapshot.getValue());}// ...});
Node.js
constscoresRef=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()forkey,valinsnapshot.items():print('The {0} dinosaur\'s score is {1}'.format(key,val))
Идти
ref:=client.NewRef("scores")results,err:=ref.OrderByValue().GetOrdered(ctx)iferr!=nil{log.Fatalln("Error querying database:",err)}for_,r:=rangeresults{varscoreintiferr:=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 , логические, строковые и объектные значения при использовании orderByValue() .
Сложные запросы
Теперь, когда понятно, как упорядочены ваши данные, вы можете использовать методы ограничения или диапазона , описанные ниже, для построения более сложных запросов.
Ограничить запросы
Запросы limitToFirst() и limitToLast() используются для установки максимального количества дочерних элементов, которые будут синхронизироваться для данного обратного вызова. Если вы установите ограничение в 100, изначально вы будете получать только до 100 событий child_added . Если в вашей базе данных хранится менее 100 сообщений, для каждого сообщения будет срабатывать событие child_added . Однако если у вас более 100 сообщений, вы получите событие child_added только для 100 из этих сообщений. Это первые 100 упорядоченных сообщений, если вы используете limitToFirst() , или последние 100 упорядоченных сообщений, если вы используете limitToLast() . По мере изменения элементов вы будете получать события child_added для элементов, которые входят в запрос, и события child_removed для элементов, которые его покидают, так что общее число остается равным 100.
Используя базу данных фактов о динозаврах и orderByChild() , вы можете найти двух самых тяжелых динозавров:
Обратный вызов child_added срабатывает ровно два раза, если в базе данных не хранится менее двух динозавров. Он также будет уволен за каждого нового, более тяжелого динозавра, добавленного в базу данных. В Python запрос напрямую возвращает OrderedDict содержащий двух самых тяжелых динозавров.
Аналогичным образом вы можете найти двух самых коротких динозавров, используя limitToFirst() :
Обратный вызов child_added срабатывает ровно два раза, если в базе данных не хранится менее двух динозавров. Он также будет запущен снова, если один из первых двух динозавров будет удален из базы данных, поскольку новый динозавр теперь будет вторым по длине. В Python запрос напрямую возвращает OrderedDict , содержащий самых коротких динозавров.
Вы также можете выполнять запросы лимитов с помощью orderByValue() . Если вы хотите создать таблицу лидеров с тремя самыми результативными спортивными динозаврами, вы можете сделать следующее:
Ява
scoresRef.orderByValue().limitToFirst(3).addChildEventListener(newChildEventListener(){@OverridepublicvoidonChildAdded(DataSnapshotdataSnapshot,StringprevChildKey){System.out.println("The "+dataSnapshot.getKey()+" score is "+dataSnapshot.getValue());}// ...});
Node.js
constscoresRef=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()forkey,valinsnapshot.items():print('The {0} dinosaur\'s score is {1}'.format(key,val))
Идти
ref:=client.NewRef("scores")results,err:=ref.OrderByValue().LimitToLast(3).GetOrdered(ctx)iferr!=nil{log.Fatalln("Error querying database:",err)}for_,r:=rangeresults{varscoreintiferr:=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() :
Вы можете объединить startAt() и endAt() чтобы ограничить оба конца вашего запроса. В следующем примере находятся все динозавры, имена которых начинаются с буквы «b»:
МетодqualTo equalTo() позволяет фильтровать на основе точных совпадений. Как и в случае с другими запросами диапазона, он будет срабатывать для каждого соответствующего дочернего узла. Например, вы можете использовать следующий запрос, чтобы найти всех динозавров ростом 25 метров:
Запросы диапазона также полезны, когда вам нужно разбить данные на страницы.
Собираем все это вместе
Вы можете комбинировать все эти методы для создания сложных запросов. Например, вы можете найти название динозавра, которое короче Стегозавра:
Ява
dinosaursRef.child("stegosaurus").child("height").addValueEventListener(newValueEventListener(){@OverridepublicvoidonDataChange(DataSnapshotstegoHeightSnapshot){IntegerfavoriteDinoHeight=stegoHeightSnapshot.getValue(Integer.class);Queryquery=dinosaursRef.orderByChild("height").endAt(favoriteDinoHeight).limitToLast(2);query.addValueEventListener(newValueEventListener(){@OverridepublicvoidonDataChange(DataSnapshotdataSnapshot){// Data is ordered by increasing height, so we want the first entryDataSnapshotfirstChild=dataSnapshot.getChildren().iterator().next();System.out.println("The dinosaur just shorter than the stegosaurus is: "+firstChild.getKey());}@OverridepublicvoidonCancelled(DatabaseErrordatabaseError){// ...}});}@OverridepublicvoidonCancelled(DatabaseErrordatabaseError){// ...}});
Node.js
constref=db.ref('dinosaurs');ref.child('stegosaurus').child('height').on('value',(stegosaurusHeightSnapshot)=>{constfavoriteDinoHeight=stegosaurusHeightSnapshot.val();constqueryRef=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 entryquerySnapshot.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 timereturntrue;});}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()iflen(snapshot)==2:# Data is ordered by increasing height, so we want the first entry.# Second entry is stegosarus.forkeyinsnapshot:print('The dinosaur just shorter than the stegosaurus is {0}'.format(key))returnelse:print('The stegosaurus is the shortest dino')
Идти
ref:=client.NewRef("dinosaurs")varfavDinoHeightintiferr:=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)iferr!=nil{log.Fatalln("Error querying database:",err)}iflen(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() данные, содержащие указанный дочерний ключ, упорядочиваются следующим образом:
Дочерние элементы с null значением для указанного дочернего ключа идут первыми.
Следующими идут дочерние элементы со значением false для указанного дочернего ключа. Если несколько дочерних элементов имеют значение false , они сортируются лексикографически по ключу.
Следующими идут дочерние элементы со значением true для указанного дочернего ключа. Если несколько дочерних элементов имеют значение true , они сортируются лексикографически по ключу.
Следующими идут дети с числовым значением, отсортированные в порядке возрастания. Если несколько дочерних узлов имеют одинаковое числовое значение для указанного дочернего узла, они сортируются по ключу.
Строки идут после чисел и сортируются лексикографически по возрастанию. Если несколько дочерних узлов имеют одинаковое значение для указанного дочернего узла, они упорядочиваются лексикографически по ключу.
Объекты идут последними и сортируются лексикографически по ключу в порядке возрастания.
заказ по ключу
При использовании orderByKey() для сортировки данных данные возвращаются в порядке возрастания ключа следующим образом. Имейте в виду, что ключи могут быть только строками.
Первыми идут дочерние элементы с ключом, который можно проанализировать как 32-битное целое число, отсортированное в порядке возрастания.
Следующими идут дочерние элементы со строковым значением в качестве ключа, отсортированные лексикографически в порядке возрастания.
заказ по значению
При использовании orderByValue() дочерние элементы упорядочиваются по их значению. Критерии упорядочивания такие же, как и в orderByChild() , за исключением того, что вместо значения указанного дочернего ключа используется значение узла.
,
В этом документе описаны основы получения данных из базы данных, порядок их упорядочения и выполнение простых запросов к данным. Получение данных в Admin SDK реализовано несколько по-разному в разных языках программирования.
Асинхронные прослушиватели. Данные, хранящиеся в Firebase Realtime Database извлекаются путем прикрепления асинхронного прослушивателя к ссылке на базу данных. Прослушиватель запускается один раз для исходного состояния данных и снова при каждом изменении данных. Слушатель событий может получать несколько различных типов событий . Этот режим получения данных поддерживается в Java, Node.js и Python Admin SDK.
Блокирующее чтение: данные, хранящиеся в Firebase Realtime Database извлекаются путем вызова метода блокировки для ссылки на базу данных, который возвращает данные, хранящиеся по ссылке. Каждый вызов метода является одноразовой операцией. Это означает, что SDK не регистрирует никаких обратных вызовов, которые прослушивают последующие обновления данных. Эта модель получения данных поддерживается в SDK Python и Go Admin.
Начиная
Давайте вернемся к примеру с блогом из предыдущей статьи, чтобы понять, как читать данные из базы данных Firebase. Напомним, что сообщения блога в примере приложения хранятся по URL-адресу базы данных https://docs-examples.firebaseio.com/server/saving-data/fireblog/posts.json . Чтобы прочитать данные публикации, вы можете сделать следующее:
Ява
publicstaticclassPost{publicStringauthor;publicStringtitle;publicPost(Stringauthor,Stringtitle){// ...}}// Get a reference to our postsfinalFirebaseDatabasedatabase=FirebaseDatabase.getInstance();DatabaseReferenceref=database.getReference("server/saving-data/fireblog/posts");// Attach a listener to read the data at our posts referenceref.addValueEventListener(newValueEventListener(){@OverridepublicvoidonDataChange(DataSnapshotdataSnapshot){Postpost=dataSnapshot.getValue(Post.class);System.out.println(post);}@OverridepublicvoidonCancelled(DatabaseErrordatabaseError){System.out.println("The read failed: "+databaseError.getCode());}});
Node.js
// Get a database reference to our postsconstdb=getDatabase();constref=db.ref('server/saving-data/fireblog/posts');// Attach an asynchronous callback to read the data at our posts referenceref.on('value',(snapshot)=>{console.log(snapshot.val());},(errorObject)=>{console.log('The read failed: '+errorObject.name);});
Питон
# Import database module.fromfirebase_adminimportdb# Get a database reference to our postsref=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.typePoststruct{Authorstring`json:"author,omitempty"`Titlestring`json:"title,omitempty"`}// Create a database client from App.client,err:=app.Database(ctx)iferr!=nil{log.Fatalln("Error initializing database client:",err)}// Get a database reference to our postsref:=client.NewRef("server/saving-data/fireblog/posts")// Read the data at the posts reference (this is a blocking operation)varpostPostiferr:=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(newChildEventListener(){@OverridepublicvoidonChildAdded(DataSnapshotdataSnapshot,StringprevChildKey){PostnewPost=dataSnapshot.getValue(Post.class);System.out.println("Author: "+newPost.author);System.out.println("Title: "+newPost.title);System.out.println("Previous Post ID: "+prevChildKey);}@OverridepublicvoidonChildChanged(DataSnapshotdataSnapshot,StringprevChildKey){}@OverridepublicvoidonChildRemoved(DataSnapshotdataSnapshot){}@OverridepublicvoidonChildMoved(DataSnapshotdataSnapshot,StringprevChildKey){}@OverridepublicvoidonCancelled(DatabaseErrordatabaseError){}});
Node.js
// Retrieve new posts as they are added to our databaseref.on('child_added',(snapshot,prevChildKey)=>{constnewPost=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(newChildEventListener(){@OverridepublicvoidonChildAdded(DataSnapshotdataSnapshot,StringprevChildKey){}@OverridepublicvoidonChildChanged(DataSnapshotdataSnapshot,StringprevChildKey){PostchangedPost=dataSnapshot.getValue(Post.class);System.out.println("The updated post title is: "+changedPost.title);}@OverridepublicvoidonChildRemoved(DataSnapshotdataSnapshot){}@OverridepublicvoidonChildMoved(DataSnapshotdataSnapshot,StringprevChildKey){}@OverridepublicvoidonCancelled(DatabaseErrordatabaseError){}});
Node.js
// Get the data on a post that has changedref.on('child_changed',(snapshot)=>{constchangedPost=snapshot.val();console.log('The updated post title is '+changedPost.title);});
Ребенок удален
Событие child_removed запускается при удалении непосредственного дочернего элемента. Обычно он используется вместе с child_added и child_changed . Снимок, передаваемый обратному вызову события, содержит данные об удаленном дочернем элементе.
В примере с блогом вы можете использовать child_removed для вывода уведомления об удаленном сообщении на консоль:
Ява
ref.addChildEventListener(newChildEventListener(){@OverridepublicvoidonChildAdded(DataSnapshotdataSnapshot,StringprevChildKey){}@OverridepublicvoidonChildChanged(DataSnapshotdataSnapshot,StringprevChildKey){}@OverridepublicvoidonChildRemoved(DataSnapshotdataSnapshot){PostremovedPost=dataSnapshot.getValue(Post.class);System.out.println("The blog post titled "+removedPost.title+" has been deleted");}@OverridepublicvoidonChildMoved(DataSnapshotdataSnapshot,StringprevChildKey){}@OverridepublicvoidonCancelled(DatabaseErrordatabaseError){}});
Node.js
// Get a reference to our postsconstref=db.ref('server/saving-data/fireblog/posts');// Get the data on a post that has been removedref.on('child_removed',(snapshot)=>{constdeletedPost=snapshot.val();console.log('The blog post titled \''+deletedPost.title+'\' has been deleted');});
Ребенок переехал
Событие child_moved используется при работе с упорядоченными данными, о чем рассказывается в следующем разделе .
Гарантии на мероприятия
База данных Firebase дает несколько важных гарантий относительно событий:
Гарантии событий базы данных
События всегда будут запускаться при изменении локального состояния.
События всегда в конечном итоге отражают правильное состояние данных, даже в тех случаях, когда локальные операции или время вызывают временные различия, например, при временной потере сетевого подключения.
Записи от одного клиента всегда будут записываться на сервер и передаваться другим пользователям по порядку.
События значений всегда запускаются последними и гарантированно содержат обновления любых других событий, произошедших до создания этого моментального снимка.
Поскольку события значения всегда запускаются последними, следующий пример всегда будет работать:
Ява
finalAtomicIntegercount=newAtomicInteger();ref.addChildEventListener(newChildEventListener(){@OverridepublicvoidonChildAdded(DataSnapshotdataSnapshot,StringprevChildKey){// New child added, increment countintnewCount=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(newValueEventListener(){@OverridepublicvoidonDataChange(DataSnapshotdataSnapshot){longnumChildren=dataSnapshot.getChildrenCount();System.out.println(count.get()+" == "+numChildren);}@OverridepublicvoidonCancelled(DatabaseErrordatabaseError){}});
Node.js
letcount=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 pointref.once('value',(snap)=>{console.log('initial data loaded!',snap.numChildren()===count);});
Отключение обратных вызовов
Обратные вызовы удаляются путем указания типа события и удаляемой функции обратного вызова, как показано ниже:
Ява
// Create and attach listenerValueEventListenerlistener=newValueEventListener(){// ...};ref.addValueEventListener(listener);// Remove listenerref.removeEventListener(listener);
Node.js
ref.off('value',originalCallback);
Если вы передали контекст области в on() , он должен быть передан при отключении обратного вызова:
Ява
// Not applicable for Java
Node.js
ref.off('value',originalCallback,ctx);
Если вы хотите удалить все обратные вызовы в определенном месте, вы можете сделать следующее:
Ява
// No Java equivalent, listeners must be removed individually.
Node.js
// Remove all value callbacksref.off('value');// Remove all callbacks of any typeref.off();
Чтение данных один раз
В некоторых случаях может оказаться полезным, чтобы обратный вызов был вызван один раз и затем немедленно удален. Мы создали вспомогательную функцию, чтобы упростить это:
ref.once('value',(data)=>{// do some stuff once});
Питон
# Import database module.fromfirebase_adminimportdb# Get a database reference to our postsref=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)iferr!=nil{log.Fatalln("Error initializing database client:",err)}// Get a database reference to our postsref:=client.NewRef("server/saving-data/fireblog/posts")// Read the data at the posts reference (this is a blocking operation)varpostPostiferr:=ref.Get(ctx,&post);err!=nil{log.Fatalln("Error reading value:",err)}
Запрос данных
С помощью запросов к базе данных Firebase вы можете выборочно получать данные на основе различных факторов. Чтобы создать запрос в базе данных, вы начинаете с указания того, как вы хотите, чтобы ваши данные были упорядочены, используя одну из функций упорядочивания: orderByChild() , orderByKey() или orderByValue() . Затем вы можете объединить их с пятью другими методами для выполнения сложных запросов: limitToFirst() , limitToLast() , startAt() , endAt() и equalTo() .
Поскольку все мы в Firebase считаем, что динозавры — это очень круто, мы воспользуемся фрагментом из образца базы данных с фактами о динозаврах, чтобы продемонстрировать, как вы можете запрашивать данные в своей базе данных Firebase.:
Вы можете упорядочить данные тремя способами: по дочернему ключу , по ключу или по значению . Базовый запрос к базе данных начинается с одной из этих функций упорядочивания, каждая из которых описана ниже.
Заказ по указанному дочернему ключу
Вы можете упорядочить узлы по общему дочернему ключу, передав этот ключ в orderByChild() . Например, чтобы прочитать всех динозавров, упорядоченных по росту, вы можете сделать следующее:
Ява
publicstaticclassDinosaur{publicintheight;publicintweight;publicDinosaur(intheight,intweight){// ...}}finalDatabaseReferencedinosaursRef=database.getReference("dinosaurs");dinosaursRef.orderByChild("height").addChildEventListener(newChildEventListener(){@OverridepublicvoidonChildAdded(DataSnapshotdataSnapshot,StringprevChildKey){Dinosaurdinosaur=dataSnapshot.getValue(Dinosaur.class);System.out.println(dataSnapshot.getKey()+" was "+dinosaur.height+" meters tall.");}// ...});
Node.js
constref=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()forkey,valinsnapshot.items():print('{0} was {1} meters tall'.format(key,val))
Идти
// Dinosaur is a json-serializable type.typeDinosaurstruct{Heightint`json:"height"`Widthint`json:"width"`}ref:=client.NewRef("dinosaurs")results,err:=ref.OrderByChild("height").GetOrdered(ctx)iferr!=nil{log.Fatalln("Error querying database:",err)}for_,r:=rangeresults{vardDinosauriferr:=r.Unmarshal(&d);err!=nil{log.Fatalln("Error unmarshaling result:",err)}fmt.Printf("%s was %d meteres tall",r.Key(),d.Height)}
Любой узел, у которого нет дочернего ключа, к которому мы запрашиваем, сортируется со значением null , что означает, что он будет первым в порядке. Подробную информацию о том, как упорядочиваются данные, см. в разделе «Как упорядочиваются данные» .
Запросы также можно упорядочивать по глубоко вложенным дочерним элементам, а не только по дочерним элементам, расположенным на один уровень ниже. Это полезно, если у вас есть глубоко вложенные данные, например:
constref=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()forkey,valinsnapshot.items():print('{0} was {1} meters tall'.format(key,val))
Идти
ref:=client.NewRef("dinosaurs")results,err:=ref.OrderByChild("dimensions/height").GetOrdered(ctx)iferr!=nil{log.Fatalln("Error querying database:",err)}for_,r:=rangeresults{vardDinosauriferr:=r.Unmarshal(&d);err!=nil{log.Fatalln("Error unmarshaling result:",err)}fmt.Printf("%s was %d meteres tall",r.Key(),d.Height)}
Запросы могут упорядочиваться только по одному ключу за раз. Вызов orderByChild() несколько раз по одному и тому же запросу приводит к ошибке.
Заказ по ключу
Вы также можете упорядочить узлы по их ключам, используя метод orderByKey() . В следующем примере все динозавры считываются в алфавитном порядке:
Вы можете упорядочить узлы по значению их дочерних ключей, используя метод orderByValue() . Допустим, динозавры устраивают спортивные соревнования динозавров, и вы отслеживаете их результаты в следующем формате:
Чтобы отсортировать динозавров по их количеству, вы можете составить следующий запрос:
Ява
DatabaseReferencescoresRef=database.getReference("scores");scoresRef.orderByValue().addChildEventListener(newChildEventListener(){@OverridepublicvoidonChildAdded(DataSnapshotdataSnapshot,StringprevChildKey){System.out.println("The "+dataSnapshot.getKey()+" score is "+dataSnapshot.getValue());}// ...});
Node.js
constscoresRef=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()forkey,valinsnapshot.items():print('The {0} dinosaur\'s score is {1}'.format(key,val))
Идти
ref:=client.NewRef("scores")results,err:=ref.OrderByValue().GetOrdered(ctx)iferr!=nil{log.Fatalln("Error querying database:",err)}for_,r:=rangeresults{varscoreintiferr:=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 , логические, строковые и объектные значения при использовании orderByValue() .
Сложные запросы
Теперь, когда понятно, как упорядочены ваши данные, вы можете использовать методы ограничения или диапазона , описанные ниже, для построения более сложных запросов.
Ограничить запросы
Запросы limitToFirst() и limitToLast() используются для установки максимального количества дочерних элементов, которые будут синхронизироваться для данного обратного вызова. Если вы установите ограничение в 100, изначально вы будете получать только до 100 событий child_added . Если в вашей базе данных хранится менее 100 сообщений, для каждого сообщения будет срабатывать событие child_added . Однако если у вас более 100 сообщений, вы получите событие child_added только для 100 из этих сообщений. Это первые 100 упорядоченных сообщений, если вы используете limitToFirst() , или последние 100 упорядоченных сообщений, если вы используете limitToLast() . По мере изменения элементов вы будете получать события child_added для элементов, которые входят в запрос, и события child_removed для элементов, которые его покидают, так что общее число остается равным 100.
Используя базу данных фактов о динозаврах и orderByChild() , вы можете найти двух самых тяжелых динозавров:
Обратный вызов child_added срабатывает ровно два раза, если в базе данных не хранится менее двух динозавров. Он также будет уволен за каждого нового, более тяжелого динозавра, добавленного в базу данных. В Python запрос напрямую возвращает OrderedDict содержащий двух самых тяжелых динозавров.
Аналогичным образом вы можете найти двух самых коротких динозавров, используя limitToFirst() :
Обратный вызов child_added срабатывает ровно два раза, если в базе данных не хранится менее двух динозавров. Он также будет запущен снова, если один из первых двух динозавров будет удален из базы данных, поскольку новый динозавр теперь будет вторым по длине. В Python запрос напрямую возвращает OrderedDict , содержащий самых коротких динозавров.
Вы также можете выполнять запросы лимитов с помощью orderByValue() . Если вы хотите создать таблицу лидеров с тремя самыми результативными спортивными динозаврами, вы можете сделать следующее:
Ява
scoresRef.orderByValue().limitToFirst(3).addChildEventListener(newChildEventListener(){@OverridepublicvoidonChildAdded(DataSnapshotdataSnapshot,StringprevChildKey){System.out.println("The "+dataSnapshot.getKey()+" score is "+dataSnapshot.getValue());}// ...});
Node.js
constscoresRef=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()forkey,valinsnapshot.items():print('The {0} dinosaur\'s score is {1}'.format(key,val))
Идти
ref:=client.NewRef("scores")results,err:=ref.OrderByValue().LimitToLast(3).GetOrdered(ctx)iferr!=nil{log.Fatalln("Error querying database:",err)}for_,r:=rangeresults{varscoreintiferr:=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() :
Вы можете объединить startAt() и endAt() чтобы ограничить оба конца вашего запроса. В следующем примере находятся все динозавры, имена которых начинаются с буквы «b»:
МетодqualTo equalTo() позволяет фильтровать на основе точных совпадений. Как и в случае с другими запросами диапазона, он будет срабатывать для каждого соответствующего дочернего узла. Например, вы можете использовать следующий запрос, чтобы найти всех динозавров ростом 25 метров:
Запросы диапазона также полезны, когда вам нужно разбить данные на страницы.
Собираем все это вместе
Вы можете комбинировать все эти методы для создания сложных запросов. Например, вы можете найти название динозавра, которое короче Стегозавра:
Ява
dinosaursRef.child("stegosaurus").child("height").addValueEventListener(newValueEventListener(){@OverridepublicvoidonDataChange(DataSnapshotstegoHeightSnapshot){IntegerfavoriteDinoHeight=stegoHeightSnapshot.getValue(Integer.class);Queryquery=dinosaursRef.orderByChild("height").endAt(favoriteDinoHeight).limitToLast(2);query.addValueEventListener(newValueEventListener(){@OverridepublicvoidonDataChange(DataSnapshotdataSnapshot){// Data is ordered by increasing height, so we want the first entryDataSnapshotfirstChild=dataSnapshot.getChildren().iterator().next();System.out.println("The dinosaur just shorter than the stegosaurus is: "+firstChild.getKey());}@OverridepublicvoidonCancelled(DatabaseErrordatabaseError){// ...}});}@OverridepublicvoidonCancelled(DatabaseErrordatabaseError){// ...}});
Node.js
constref=db.ref('dinosaurs');ref.child('stegosaurus').child('height').on('value',(stegosaurusHeightSnapshot)=>{constfavoriteDinoHeight=stegosaurusHeightSnapshot.val();constqueryRef=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 entryquerySnapshot.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 timereturntrue;});}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()iflen(snapshot)==2:# Data is ordered by increasing height, so we want the first entry.# Second entry is stegosarus.forkeyinsnapshot:print('The dinosaur just shorter than the stegosaurus is {0}'.format(key))returnelse:print('The stegosaurus is the shortest dino')
Идти
ref:=client.NewRef("dinosaurs")varfavDinoHeightintiferr:=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)iferr!=nil{log.Fatalln("Error querying database:",err)}iflen(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() данные, содержащие указанный дочерний ключ, упорядочиваются следующим образом:
Дочерние элементы с null значением для указанного дочернего ключа идут первыми.
Следующими идут дочерние элементы со значением false для указанного дочернего ключа. Если несколько дочерних элементов имеют значение false , они сортируются лексикографически по ключу.
Следующими идут дочерние элементы со значением true для указанного дочернего ключа. Если несколько дочерних элементов имеют значение true , они сортируются лексикографически по ключу.
Следующими идут дети с числовым значением, отсортированные в порядке возрастания. Если несколько дочерних узлов имеют одинаковое числовое значение для указанного дочернего узла, они сортируются по ключу.
Строки идут после чисел и сортируются лексикографически по возрастанию. Если несколько дочерних узлов имеют одинаковое значение для указанного дочернего узла, они упорядочиваются лексикографически по ключу.
Объекты идут последними и сортируются лексикографически по ключу в порядке возрастания.
заказ по ключу
При использовании orderByKey() для сортировки данных данные возвращаются в порядке возрастания ключа следующим образом. Имейте в виду, что ключи могут быть только строками.
Первыми идут дочерние элементы с ключом, который можно проанализировать как 32-битное целое число, отсортированное в порядке возрастания.
Следующими идут дочерние элементы со строковым значением в качестве ключа, отсортированные лексикографически в порядке возрастания.
заказ по значению
При использовании orderByValue() дочерние элементы упорядочиваются по их значению. Критерии упорядочивания такие же, как и в orderByChild() , за исключением того, что вместо значения указанного дочернего ключа используется значение узла.
,
В этом документе описаны основы получения данных из базы данных, порядок их упорядочения и выполнение простых запросов к данным. Получение данных в Admin SDK реализовано несколько по-разному в разных языках программирования.
Асинхронные прослушиватели. Данные, хранящиеся в Firebase Realtime Database извлекаются путем прикрепления асинхронного прослушивателя к ссылке на базу данных. Прослушиватель запускается один раз для исходного состояния данных и снова при каждом изменении данных. Слушатель событий может получать несколько различных типов событий . Этот режим получения данных поддерживается в Java, Node.js и Python Admin SDK.
Блокирующее чтение: данные, хранящиеся в Firebase Realtime Database извлекаются путем вызова метода блокировки для ссылки на базу данных, который возвращает данные, хранящиеся по ссылке. Каждый вызов метода является одноразовой операцией. Это означает, что SDK не регистрирует никаких обратных вызовов, которые прослушивают последующие обновления данных. Эта модель получения данных поддерживается в SDK Python и Go Admin.
Начиная
Давайте вернемся к примеру с блогом из предыдущей статьи, чтобы понять, как читать данные из базы данных Firebase. Напомним, что сообщения блога в примере приложения хранятся по URL-адресу базы данных https://docs-examples.firebaseio.com/server/saving-data/fireblog/posts.json . Чтобы прочитать данные публикации, вы можете сделать следующее:
Ява
publicstaticclassPost{publicStringauthor;publicStringtitle;publicPost(Stringauthor,Stringtitle){// ...}}// Get a reference to our postsfinalFirebaseDatabasedatabase=FirebaseDatabase.getInstance();DatabaseReferenceref=database.getReference("server/saving-data/fireblog/posts");// Attach a listener to read the data at our posts referenceref.addValueEventListener(newValueEventListener(){@OverridepublicvoidonDataChange(DataSnapshotdataSnapshot){Postpost=dataSnapshot.getValue(Post.class);System.out.println(post);}@OverridepublicvoidonCancelled(DatabaseErrordatabaseError){System.out.println("The read failed: "+databaseError.getCode());}});
Node.js
// Get a database reference to our postsconstdb=getDatabase();constref=db.ref('server/saving-data/fireblog/posts');// Attach an asynchronous callback to read the data at our posts referenceref.on('value',(snapshot)=>{console.log(snapshot.val());},(errorObject)=>{console.log('The read failed: '+errorObject.name);});
Питон
# Import database module.fromfirebase_adminimportdb# Get a database reference to our postsref=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.typePoststruct{Authorstring`json:"author,omitempty"`Titlestring`json:"title,omitempty"`}// Create a database client from App.client,err:=app.Database(ctx)iferr!=nil{log.Fatalln("Error initializing database client:",err)}// Get a database reference to our postsref:=client.NewRef("server/saving-data/fireblog/posts")// Read the data at the posts reference (this is a blocking operation)varpostPostiferr:=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(newChildEventListener(){@OverridepublicvoidonChildAdded(DataSnapshotdataSnapshot,StringprevChildKey){PostnewPost=dataSnapshot.getValue(Post.class);System.out.println("Author: "+newPost.author);System.out.println("Title: "+newPost.title);System.out.println("Previous Post ID: "+prevChildKey);}@OverridepublicvoidonChildChanged(DataSnapshotdataSnapshot,StringprevChildKey){}@OverridepublicvoidonChildRemoved(DataSnapshotdataSnapshot){}@OverridepublicvoidonChildMoved(DataSnapshotdataSnapshot,StringprevChildKey){}@OverridepublicvoidonCancelled(DatabaseErrordatabaseError){}});
Node.js
// Retrieve new posts as they are added to our databaseref.on('child_added',(snapshot,prevChildKey)=>{constnewPost=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(newChildEventListener(){@OverridepublicvoidonChildAdded(DataSnapshotdataSnapshot,StringprevChildKey){}@OverridepublicvoidonChildChanged(DataSnapshotdataSnapshot,StringprevChildKey){PostchangedPost=dataSnapshot.getValue(Post.class);System.out.println("The updated post title is: "+changedPost.title);}@OverridepublicvoidonChildRemoved(DataSnapshotdataSnapshot){}@OverridepublicvoidonChildMoved(DataSnapshotdataSnapshot,StringprevChildKey){}@OverridepublicvoidonCancelled(DatabaseErrordatabaseError){}});
Node.js
// Get the data on a post that has changedref.on('child_changed',(snapshot)=>{constchangedPost=snapshot.val();console.log('The updated post title is '+changedPost.title);});
Ребенок удален
Событие child_removed запускается при удалении непосредственного дочернего элемента. Обычно он используется вместе с child_added и child_changed . Снимок, передаваемый обратному вызову события, содержит данные об удаленном дочернем элементе.
В примере с блогом вы можете использовать child_removed для вывода уведомления об удаленном сообщении на консоль:
Ява
ref.addChildEventListener(newChildEventListener(){@OverridepublicvoidonChildAdded(DataSnapshotdataSnapshot,StringprevChildKey){}@OverridepublicvoidonChildChanged(DataSnapshotdataSnapshot,StringprevChildKey){}@OverridepublicvoidonChildRemoved(DataSnapshotdataSnapshot){PostremovedPost=dataSnapshot.getValue(Post.class);System.out.println("The blog post titled "+removedPost.title+" has been deleted");}@OverridepublicvoidonChildMoved(DataSnapshotdataSnapshot,StringprevChildKey){}@OverridepublicvoidonCancelled(DatabaseErrordatabaseError){}});
Node.js
// Get a reference to our postsconstref=db.ref('server/saving-data/fireblog/posts');// Get the data on a post that has been removedref.on('child_removed',(snapshot)=>{constdeletedPost=snapshot.val();console.log('The blog post titled \''+deletedPost.title+'\' has been deleted');});
Ребенок переехал
Событие child_moved используется при работе с упорядоченными данными, о чем рассказывается в следующем разделе .
Гарантии на мероприятия
База данных Firebase дает несколько важных гарантий относительно событий:
Гарантии событий базы данных
События всегда будут запускаться при изменении локального состояния.
События всегда в конечном итоге отражают правильное состояние данных, даже в тех случаях, когда локальные операции или время вызывают временные различия, например, при временной потере сетевого подключения.
Записи от одного клиента всегда будут записываться на сервер и передаваться другим пользователям по порядку.
События значений всегда запускаются последними и гарантированно содержат обновления любых других событий, произошедших до создания этого моментального снимка.
Поскольку события значения всегда запускаются последними, следующий пример всегда будет работать:
Ява
finalAtomicIntegercount=newAtomicInteger();ref.addChildEventListener(newChildEventListener(){@OverridepublicvoidonChildAdded(DataSnapshotdataSnapshot,StringprevChildKey){// New child added, increment countintnewCount=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(newValueEventListener(){@OverridepublicvoidonDataChange(DataSnapshotdataSnapshot){longnumChildren=dataSnapshot.getChildrenCount();System.out.println(count.get()+" == "+numChildren);}@OverridepublicvoidonCancelled(DatabaseErrordatabaseError){}});
Node.js
letcount=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 pointref.once('value',(snap)=>{console.log('initial data loaded!',snap.numChildren()===count);});
Отключение обратных вызовов
Обратные вызовы удаляются путем указания типа события и удаляемой функции обратного вызова, как показано ниже:
Ява
// Create and attach listenerValueEventListenerlistener=newValueEventListener(){// ...};ref.addValueEventListener(listener);// Remove listenerref.removeEventListener(listener);
Node.js
ref.off('value',originalCallback);
Если вы передали контекст области в on() , он должен быть передан при отключении обратного вызова:
Ява
// Not applicable for Java
Node.js
ref.off('value',originalCallback,ctx);
Если вы хотите удалить все обратные вызовы в определенном месте, вы можете сделать следующее:
Ява
// No Java equivalent, listeners must be removed individually.
Node.js
// Remove all value callbacksref.off('value');// Remove all callbacks of any typeref.off();
Чтение данных один раз
В некоторых случаях может оказаться полезным, чтобы обратный вызов был вызван один раз и затем немедленно удален. Мы создали вспомогательную функцию, чтобы упростить это:
ref.once('value',(data)=>{// do some stuff once});
Питон
# Import database module.fromfirebase_adminimportdb# Get a database reference to our postsref=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)iferr!=nil{log.Fatalln("Error initializing database client:",err)}// Get a database reference to our postsref:=client.NewRef("server/saving-data/fireblog/posts")// Read the data at the posts reference (this is a blocking operation)varpostPostiferr:=ref.Get(ctx,&post);err!=nil{log.Fatalln("Error reading value:",err)}
Запрос данных
С помощью запросов к базе данных Firebase вы можете выборочно получать данные на основе различных факторов. Чтобы создать запрос в базе данных, вы начинаете с указания того, как вы хотите, чтобы ваши данные были упорядочены, используя одну из функций упорядочивания: orderByChild() , orderByKey() или orderByValue() . Затем вы можете объединить их с пятью другими методами для выполнения сложных запросов: limitToFirst() , limitToLast() , startAt() , endAt() и equalTo() .
Поскольку все мы в Firebase считаем, что динозавры — это очень круто, мы воспользуемся фрагментом из образца базы данных с фактами о динозаврах, чтобы продемонстрировать, как вы можете запрашивать данные в своей базе данных Firebase.:
Вы можете упорядочить данные тремя способами: по дочернему ключу , по ключу или по значению . Базовый запрос к базе данных начинается с одной из этих функций упорядочивания, каждая из которых описана ниже.
Заказ по указанному дочернему ключу
Вы можете упорядочить узлы по общему дочернему ключу, передав этот ключ в orderByChild() . Например, чтобы прочитать всех динозавров, упорядоченных по росту, вы можете сделать следующее:
Ява
publicstaticclassDinosaur{publicintheight;publicintweight;publicDinosaur(intheight,intweight){// ...}}finalDatabaseReferencedinosaursRef=database.getReference("dinosaurs");dinosaursRef.orderByChild("height").addChildEventListener(newChildEventListener(){@OverridepublicvoidonChildAdded(DataSnapshotdataSnapshot,StringprevChildKey){Dinosaurdinosaur=dataSnapshot.getValue(Dinosaur.class);System.out.println(dataSnapshot.getKey()+" was "+dinosaur.height+" meters tall.");}// ...});
Node.js
constref=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()forkey,valinsnapshot.items():print('{0} was {1} meters tall'.format(key,val))
Идти
// Dinosaur is a json-serializable type.typeDinosaurstruct{Heightint`json:"height"`Widthint`json:"width"`}ref:=client.NewRef("dinosaurs")results,err:=ref.OrderByChild("height").GetOrdered(ctx)iferr!=nil{log.Fatalln("Error querying database:",err)}for_,r:=rangeresults{vardDinosauriferr:=r.Unmarshal(&d);err!=nil{log.Fatalln("Error unmarshaling result:",err)}fmt.Printf("%s was %d meteres tall",r.Key(),d.Height)}
Любой узел, у которого нет дочернего ключа, к которому мы запрашиваем, сортируется со значением null , что означает, что он будет первым в порядке. Подробную информацию о том, как упорядочиваются данные, см. в разделе «Как упорядочиваются данные» .
Запросы также можно упорядочивать по глубоко вложенным дочерним элементам, а не только по дочерним элементам, расположенным на один уровень ниже. Это полезно, если у вас есть глубоко вложенные данные, например:
constref=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()forkey,valinsnapshot.items():print('{0} was {1} meters tall'.format(key,val))
Идти
ref:=client.NewRef("dinosaurs")results,err:=ref.OrderByChild("dimensions/height").GetOrdered(ctx)iferr!=nil{log.Fatalln("Error querying database:",err)}for_,r:=rangeresults{vardDinosauriferr:=r.Unmarshal(&d);err!=nil{log.Fatalln("Error unmarshaling result:",err)}fmt.Printf("%s was %d meteres tall",r.Key(),d.Height)}
Запросы могут упорядочиваться только по одному ключу за раз. Вызов orderByChild() несколько раз по одному и тому же запросу приводит к ошибке.
Заказ по ключу
Вы также можете упорядочить узлы по их ключам, используя метод orderByKey() . В следующем примере все динозавры считываются в алфавитном порядке:
Вы можете упорядочить узлы по значению их дочерних ключей, используя метод orderByValue() . Допустим, динозавры устраивают спортивные соревнования динозавров, и вы отслеживаете их результаты в следующем формате:
Чтобы отсортировать динозавров по их количеству, вы можете составить следующий запрос:
Ява
DatabaseReferencescoresRef=database.getReference("scores");scoresRef.orderByValue().addChildEventListener(newChildEventListener(){@OverridepublicvoidonChildAdded(DataSnapshotdataSnapshot,StringprevChildKey){System.out.println("The "+dataSnapshot.getKey()+" score is "+dataSnapshot.getValue());}// ...});
Node.js
constscoresRef=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()forkey,valinsnapshot.items():print('The {0} dinosaur\'s score is {1}'.format(key,val))
Идти
ref:=client.NewRef("scores")results,err:=ref.OrderByValue().GetOrdered(ctx)iferr!=nil{log.Fatalln("Error querying database:",err)}for_,r:=rangeresults{varscoreintiferr:=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 , логические, строковые и объектные значения при использовании orderByValue() .
Сложные запросы
Теперь, когда понятно, как упорядочены ваши данные, вы можете использовать методы ограничения или диапазона , описанные ниже, для построения более сложных запросов.
Ограничить запросы
Запросы limitToFirst() и limitToLast() используются для установки максимального количества дочерних элементов, которые будут синхронизироваться для данного обратного вызова. Если вы установите ограничение в 100, изначально вы будете получать только до 100 событий child_added . Если в вашей базе данных хранится менее 100 сообщений, для каждого сообщения будет срабатывать событие child_added . Однако если у вас более 100 сообщений, вы получите событие child_added только для 100 из этих сообщений. Это первые 100 упорядоченных сообщений, если вы используете limitToFirst() , или последние 100 упорядоченных сообщений, если вы используете limitToLast() . По мере изменения элементов вы будете получать события child_added для элементов, которые входят в запрос, и события child_removed для элементов, которые его покидают, так что общее число остается равным 100.
Используя базу данных фактов о динозаврах и orderByChild() , вы можете найти двух самых тяжелых динозавров:
Обратный вызов child_added срабатывает ровно два раза, если в базе данных не хранится менее двух динозавров. Он также будет уволен за каждого нового, более тяжелого динозавра, добавленного в базу данных. В Python запрос напрямую возвращает OrderedDict содержащий двух самых тяжелых динозавров.
Аналогичным образом вы можете найти двух самых коротких динозавров, используя limitToFirst() :
Обратный вызов child_added срабатывает ровно два раза, если в базе данных не хранится менее двух динозавров. Он также будет запущен снова, если один из первых двух динозавров будет удален из базы данных, поскольку новый динозавр теперь будет вторым по длине. В Python запрос напрямую возвращает OrderedDict , содержащий самых коротких динозавров.
Вы также можете выполнять запросы лимитов с помощью orderByValue() . Если вы хотите создать таблицу лидеров с тремя самыми результативными спортивными динозаврами, вы можете сделать следующее:
Ява
scoresRef.orderByValue().limitToFirst(3).addChildEventListener(newChildEventListener(){@OverridepublicvoidonChildAdded(DataSnapshotdataSnapshot,StringprevChildKey){System.out.println("The "+dataSnapshot.getKey()+" score is "+dataSnapshot.getValue());}// ...});
Node.js
constscoresRef=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()forkey,valinsnapshot.items():print('The {0} dinosaur\'s score is {1}'.format(key,val))
Идти
ref:=client.NewRef("scores")results,err:=ref.OrderByValue().LimitToLast(3).GetOrdered(ctx)iferr!=nil{log.Fatalln("Error querying database:",err)}for_,r:=rangeresults{varscoreintiferr:=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() :
Вы можете объединить startAt() и endAt() чтобы ограничить оба конца вашего запроса. В следующем примере находятся все динозавры, имена которых начинаются с буквы «b»:
МетодqualTo equalTo() позволяет фильтровать на основе точных совпадений. Как и в случае с другими запросами диапазона, он будет срабатывать для каждого соответствующего дочернего узла. Например, вы можете использовать следующий запрос, чтобы найти всех динозавров ростом 25 метров:
Запросы диапазона также полезны, когда вам нужно разбить данные на страницы.
Собираем все это вместе
Вы можете комбинировать все эти методы для создания сложных запросов. Например, вы можете найти название динозавра, которое короче Стегозавра:
Ява
dinosaursRef.child("stegosaurus").child("height").addValueEventListener(newValueEventListener(){@OverridepublicvoidonDataChange(DataSnapshotstegoHeightSnapshot){IntegerfavoriteDinoHeight=stegoHeightSnapshot.getValue(Integer.class);Queryquery=dinosaursRef.orderByChild("height").endAt(favoriteDinoHeight).limitToLast(2);query.addValueEventListener(newValueEventListener(){@OverridepublicvoidonDataChange(DataSnapshotdataSnapshot){// Data is ordered by increasing height, so we want the first entryDataSnapshotfirstChild=dataSnapshot.getChildren().iterator().next();System.out.println("The dinosaur just shorter than the stegosaurus is: "+firstChild.getKey());}@OverridepublicvoidonCancelled(DatabaseErrordatabaseError){// ...}});}@OverridepublicvoidonCancelled(DatabaseErrordatabaseError){// ...}});
Node.js
constref=db.ref('dinosaurs');ref.child('stegosaurus').child('height').on('value',(stegosaurusHeightSnapshot)=>{constfavoriteDinoHeight=stegosaurusHeightSnapshot.val();constqueryRef=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 entryquerySnapshot.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 timereturntrue;});}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()iflen(snapshot)==2:# Data is ordered by increasing height, so we want the first entry.# Second entry is stegosarus.forkeyinsnapshot:print('The dinosaur just shorter than the stegosaurus is {0}'.format(key))returnelse:print('The stegosaurus is the shortest dino')
Идти
ref:=client.NewRef("dinosaurs")varfavDinoHeightintiferr:=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)iferr!=nil{log.Fatalln("Error querying database:",err)}iflen(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() данные, содержащие указанный дочерний ключ, упорядочиваются следующим образом:
Дочерние элементы с null значением для указанного дочернего ключа идут первыми.
Следующими идут дочерние элементы со значением false для указанного дочернего ключа. Если несколько дочерних элементов имеют значение false , они сортируются лексикографически по ключу.
Следующими идут дочерние элементы со значением true для указанного дочернего ключа. Если несколько дочерних элементов имеют значение true , они сортируются лексикографически по ключу.
Следующими идут дети с числовым значением, отсортированные в порядке возрастания. Если несколько дочерних узлов имеют одинаковое числовое значение для указанного дочернего узла, они сортируются по ключу.
Строки идут после чисел и сортируются лексикографически по возрастанию. Если несколько дочерних узлов имеют одинаковое значение для указанного дочернего узла, они упорядочиваются лексикографически по ключу.
Объекты идут последними и сортируются лексикографически по ключу в порядке возрастания.
заказ по ключу
При использовании orderByKey() для сортировки данных данные возвращаются в порядке возрастания ключа следующим образом. Имейте в виду, что ключи могут быть только строками.
Первыми идут дочерние элементы с ключом, который можно проанализировать как 32-битное целое число, отсортированное в порядке возрастания.
Следующими идут дочерние элементы со строковым значением в качестве ключа, отсортированные лексикографически в порядке возрастания.
заказ по значению
При использовании orderByValue() дочерние элементы упорядочиваются по их значению. Критерии упорядочивания такие же, как и в orderByChild() , за исключением того, что вместо значения указанного дочернего ключа используется значение узла.
,
В этом документе описаны основы получения данных из базы данных, порядок их упорядочения и выполнение простых запросов к данным. Получение данных в Admin SDK реализовано несколько по-разному в разных языках программирования.
Асинхронные прослушиватели. Данные, хранящиеся в Firebase Realtime Database извлекаются путем прикрепления асинхронного прослушивателя к ссылке на базу данных. Прослушиватель запускается один раз для исходного состояния данных и снова при каждом изменении данных. Слушатель событий может получать несколько различных типов событий . Этот режим получения данных поддерживается в Java, Node.js и Python Admin SDK.
Блокирующее чтение: данные, хранящиеся в Firebase Realtime Database извлекаются путем вызова метода блокировки для ссылки на базу данных, который возвращает данные, хранящиеся по ссылке. Каждый вызов метода является одноразовой операцией. Это означает, что SDK не регистрирует никаких обратных вызовов, которые прослушивают последующие обновления данных. Эта модель получения данных поддерживается в SDK Python и Go Admin.
Начиная
Давайте вернемся к примеру с блогом из предыдущей статьи, чтобы понять, как читать данные из базы данных Firebase. Напомним, что сообщения блога в примере приложения хранятся по URL-адресу базы данных https://docs-examples.firebaseio.com/server/saving-data/fireblog/posts.json . Чтобы прочитать данные публикации, вы можете сделать следующее:
Ява
publicstaticclassPost{publicStringauthor;publicStringtitle;publicPost(Stringauthor,Stringtitle){// ...}}// Get a reference to our postsfinalFirebaseDatabasedatabase=FirebaseDatabase.getInstance();DatabaseReferenceref=database.getReference("server/saving-data/fireblog/posts");// Attach a listener to read the data at our posts referenceref.addValueEventListener(newValueEventListener(){@OverridepublicvoidonDataChange(DataSnapshotdataSnapshot){Postpost=dataSnapshot.getValue(Post.class);System.out.println(post);}@OverridepublicvoidonCancelled(DatabaseErrordatabaseError){System.out.println("The read failed: "+databaseError.getCode());}});
Node.js
// Get a database reference to our postsconstdb=getDatabase();constref=db.ref('server/saving-data/fireblog/posts');// Attach an asynchronous callback to read the data at our posts referenceref.on('value',(snapshot)=>{console.log(snapshot.val());},(errorObject)=>{console.log('The read failed: '+errorObject.name);});
Питон
# Import database module.fromfirebase_adminimportdb# Get a database reference to our postsref=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.typePoststruct{Authorstring`json:"author,omitempty"`Titlestring`json:"title,omitempty"`}// Create a database client from App.client,err:=app.Database(ctx)iferr!=nil{log.Fatalln("Error initializing database client:",err)}// Get a database reference to our postsref:=client.NewRef("server/saving-data/fireblog/posts")// Read the data at the posts reference (this is a blocking operation)varpostPostiferr:=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(newChildEventListener(){@OverridepublicvoidonChildAdded(DataSnapshotdataSnapshot,StringprevChildKey){PostnewPost=dataSnapshot.getValue(Post.class);System.out.println("Author: "+newPost.author);System.out.println("Title: "+newPost.title);System.out.println("Previous Post ID: "+prevChildKey);}@OverridepublicvoidonChildChanged(DataSnapshotdataSnapshot,StringprevChildKey){}@OverridepublicvoidonChildRemoved(DataSnapshotdataSnapshot){}@OverridepublicvoidonChildMoved(DataSnapshotdataSnapshot,StringprevChildKey){}@OverridepublicvoidonCancelled(DatabaseErrordatabaseError){}});
Node.js
// Retrieve new posts as they are added to our databaseref.on('child_added',(snapshot,prevChildKey)=>{constnewPost=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(newChildEventListener(){@OverridepublicvoidonChildAdded(DataSnapshotdataSnapshot,StringprevChildKey){}@OverridepublicvoidonChildChanged(DataSnapshotdataSnapshot,StringprevChildKey){PostchangedPost=dataSnapshot.getValue(Post.class);System.out.println("The updated post title is: "+changedPost.title);}@OverridepublicvoidonChildRemoved(DataSnapshotdataSnapshot){}@OverridepublicvoidonChildMoved(DataSnapshotdataSnapshot,StringprevChildKey){}@OverridepublicvoidonCancelled(DatabaseErrordatabaseError){}});
Node.js
// Get the data on a post that has changedref.on('child_changed',(snapshot)=>{constchangedPost=snapshot.val();console.log('The updated post title is '+changedPost.title);});
Ребенок удален
Событие child_removed запускается при удалении непосредственного дочернего элемента. Обычно он используется вместе с child_added и child_changed . Снимок, передаваемый обратному вызову события, содержит данные об удаленном дочернем элементе.
В примере с блогом вы можете использовать child_removed для вывода уведомления об удаленном сообщении на консоль:
Ява
ref.addChildEventListener(newChildEventListener(){@OverridepublicvoidonChildAdded(DataSnapshotdataSnapshot,StringprevChildKey){}@OverridepublicvoidonChildChanged(DataSnapshotdataSnapshot,StringprevChildKey){}@OverridepublicvoidonChildRemoved(DataSnapshotdataSnapshot){PostremovedPost=dataSnapshot.getValue(Post.class);System.out.println("The blog post titled "+removedPost.title+" has been deleted");}@OverridepublicvoidonChildMoved(DataSnapshotdataSnapshot,StringprevChildKey){}@OverridepublicvoidonCancelled(DatabaseErrordatabaseError){}});
Node.js
// Get a reference to our postsconstref=db.ref('server/saving-data/fireblog/posts');// Get the data on a post that has been removedref.on('child_removed',(snapshot)=>{constdeletedPost=snapshot.val();console.log('The blog post titled \''+deletedPost.title+'\' has been deleted');});
Ребенок переехал
Событие child_moved используется при работе с упорядоченными данными, о чем рассказывается в следующем разделе .
Гарантии на мероприятия
База данных Firebase дает несколько важных гарантий относительно событий:
Гарантии событий базы данных
События всегда будут запускаться при изменении локального состояния.
События всегда в конечном итоге отражают правильное состояние данных, даже в тех случаях, когда локальные операции или время вызывают временные различия, например, при временной потере сетевого подключения.
Записи от одного клиента всегда будут записываться на сервер и передаваться другим пользователям по порядку.
События значений всегда запускаются последними и гарантированно содержат обновления любых других событий, произошедших до создания этого моментального снимка.
Поскольку события значения всегда запускаются последними, следующий пример всегда будет работать:
Ява
finalAtomicIntegercount=newAtomicInteger();ref.addChildEventListener(newChildEventListener(){@OverridepublicvoidonChildAdded(DataSnapshotdataSnapshot,StringprevChildKey){// New child added, increment countintnewCount=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(newValueEventListener(){@OverridepublicvoidonDataChange(DataSnapshotdataSnapshot){longnumChildren=dataSnapshot.getChildrenCount();System.out.println(count.get()+" == "+numChildren);}@OverridepublicvoidonCancelled(DatabaseErrordatabaseError){}});
Node.js
letcount=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 pointref.once('value',(snap)=>{console.log('initial data loaded!',snap.numChildren()===count);});
Отключение обратных вызовов
Обратные вызовы удаляются путем указания типа события и удаляемой функции обратного вызова, как показано ниже:
Ява
// Create and attach listenerValueEventListenerlistener=newValueEventListener(){// ...};ref.addValueEventListener(listener);// Remove listenerref.removeEventListener(listener);
Node.js
ref.off('value',originalCallback);
Если вы передали контекст области в on() , он должен быть передан при отключении обратного вызова:
Ява
// Not applicable for Java
Node.js
ref.off('value',originalCallback,ctx);
Если вы хотите удалить все обратные вызовы в определенном месте, вы можете сделать следующее:
Ява
// No Java equivalent, listeners must be removed individually.
Node.js
// Remove all value callbacksref.off('value');// Remove all callbacks of any typeref.off();
Чтение данных один раз
В некоторых случаях может оказаться полезным, чтобы обратный вызов был вызван один раз и затем немедленно удален. Мы создали вспомогательную функцию, чтобы упростить это:
ref.once('value',(data)=>{// do some stuff once});
Питон
# Import database module.fromfirebase_adminimportdb# Get a database reference to our postsref=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)iferr!=nil{log.Fatalln("Error initializing database client:",err)}// Get a database reference to our postsref:=client.NewRef("server/saving-data/fireblog/posts")// Read the data at the posts reference (this is a blocking operation)varpostPostiferr:=ref.Get(ctx,&post);err!=nil{log.Fatalln("Error reading value:",err)}
Запрос данных
С помощью запросов к базе данных Firebase вы можете выборочно получать данные на основе различных факторов. Чтобы создать запрос в базе данных, вы начинаете с указания того, как вы хотите, чтобы ваши данные были упорядочены, используя одну из функций упорядочивания: orderByChild() , orderByKey() или orderByValue() . Затем вы можете объединить их с пятью другими методами для выполнения сложных запросов: limitToFirst() , limitToLast() , startAt() , endAt() и equalTo() .
Поскольку все мы в Firebase считаем, что динозавры — это очень круто, мы воспользуемся фрагментом из образца базы данных с фактами о динозаврах, чтобы продемонстрировать, как вы можете запрашивать данные в своей базе данных Firebase.:
Вы можете упорядочить данные тремя способами: по дочернему ключу , по ключу или по значению . Базовый запрос к базе данных начинается с одной из этих функций упорядочивания, каждая из которых описана ниже.
Заказ по указанному дочернему ключу
Вы можете упорядочить узлы по общему дочернему ключу, передав этот ключ в orderByChild() . Например, чтобы прочитать всех динозавров, упорядоченных по росту, вы можете сделать следующее:
Ява
publicstaticclassDinosaur{publicintheight;publicintweight;publicDinosaur(intheight,intweight){// ...}}finalDatabaseReferencedinosaursRef=database.getReference("dinosaurs");dinosaursRef.orderByChild("height").addChildEventListener(newChildEventListener(){@OverridepublicvoidonChildAdded(DataSnapshotdataSnapshot,StringprevChildKey){Dinosaurdinosaur=dataSnapshot.getValue(Dinosaur.class);System.out.println(dataSnapshot.getKey()+" was "+dinosaur.height+" meters tall.");}// ...});
Node.js
constref=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()forkey,valinsnapshot.items():print('{0} was {1} meters tall'.format(key,val))
Идти
// Dinosaur is a json-serializable type.typeDinosaurstruct{Heightint`json:"height"`Widthint`json:"width"`}ref:=client.NewRef("dinosaurs")results,err:=ref.OrderByChild("height").GetOrdered(ctx)iferr!=nil{log.Fatalln("Error querying database:",err)}for_,r:=rangeresults{vardDinosauriferr:=r.Unmarshal(&d);err!=nil{log.Fatalln("Error unmarshaling result:",err)}fmt.Printf("%s was %d meteres tall",r.Key(),d.Height)}
Любой узел, у которого нет дочернего ключа, к которому мы запрашиваем, сортируется со значением null , что означает, что он будет первым в порядке. Подробную информацию о том, как упорядочиваются данные, см. в разделе «Как упорядочиваются данные» .
Запросы также можно упорядочивать по глубоко вложенным дочерним элементам, а не только по дочерним элементам, расположенным на один уровень ниже. Это полезно, если у вас есть глубоко вложенные данные, например:
constref=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()forkey,valinsnapshot.items():print('{0} was {1} meters tall'.format(key,val))
Идти
ref:=client.NewRef("dinosaurs")results,err:=ref.OrderByChild("dimensions/height").GetOrdered(ctx)iferr!=nil{log.Fatalln("Error querying database:",err)}for_,r:=rangeresults{vardDinosauriferr:=r.Unmarshal(&d);err!=nil{log.Fatalln("Error unmarshaling result:",err)}fmt.Printf("%s was %d meteres tall",r.Key(),d.Height)}
Запросы могут упорядочиваться только по одному ключу за раз. Вызов orderByChild() несколько раз по одному и тому же запросу приводит к ошибке.
Заказ по ключу
Вы также можете упорядочить узлы по их ключам, используя метод orderByKey() . В следующем примере все динозавры считываются в алфавитном порядке:
Вы можете упорядочить узлы по значению их дочерних ключей, используя метод orderByValue() . Допустим, динозавры устраивают спортивные соревнования динозавров, и вы отслеживаете их результаты в следующем формате:
Чтобы отсортировать динозавров по их количеству, вы можете составить следующий запрос:
Ява
DatabaseReferencescoresRef=database.getReference("scores");scoresRef.orderByValue().addChildEventListener(newChildEventListener(){@OverridepublicvoidonChildAdded(DataSnapshotdataSnapshot,StringprevChildKey){System.out.println("The "+dataSnapshot.getKey()+" score is "+dataSnapshot.getValue());}// ...});
Node.js
constscoresRef=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()forkey,valinsnapshot.items():print('The {0} dinosaur\'s score is {1}'.format(key,val))
Идти
ref:=client.NewRef("scores")results,err:=ref.OrderByValue().GetOrdered(ctx)iferr!=nil{log.Fatalln("Error querying database:",err)}for_,r:=rangeresults{varscoreintiferr:=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 , логические, строковые и объектные значения при использовании orderByValue() .
Сложные запросы
Теперь, когда понятно, как упорядочены ваши данные, вы можете использовать методы ограничения или диапазона , описанные ниже, для построения более сложных запросов.
Ограничить запросы
Запросы limitToFirst() и limitToLast() используются для установки максимального количества дочерних элементов, которые будут синхронизироваться для данного обратного вызова. Если вы установите ограничение в 100, изначально вы будете получать только до 100 событий child_added . Если в вашей базе данных хранится менее 100 сообщений, для каждого сообщения будет срабатывать событие child_added . Однако если у вас более 100 сообщений, вы получите событие child_added только для 100 из этих сообщений. Это первые 100 упорядоченных сообщений, если вы используете limitToFirst() , или последние 100 упорядоченных сообщений, если вы используете limitToLast() . По мере изменения элементов вы будете получать события child_added для элементов, которые входят в запрос, и события child_removed для элементов, которые его покидают, так что общее число остается равным 100.
Используя базу данных фактов о динозаврах и orderByChild() , вы можете найти двух самых тяжелых динозавров:
Обратный вызов child_added срабатывает ровно два раза, если в базе данных не хранится менее двух динозавров. Он также будет уволен за каждого нового, более тяжелого динозавра, добавленного в базу данных. В Python запрос напрямую возвращает OrderedDict содержащий двух самых тяжелых динозавров.
Аналогичным образом вы можете найти двух самых коротких динозавров, используя limitToFirst() :
Обратный вызов child_added срабатывает ровно два раза, если в базе данных не хранится менее двух динозавров. Он также будет запущен снова, если один из первых двух динозавров будет удален из базы данных, поскольку новый динозавр теперь будет вторым по длине. В Python запрос напрямую возвращает OrderedDict , содержащий самых коротких динозавров.
Вы также можете выполнять запросы лимитов с помощью orderByValue() . Если вы хотите создать таблицу лидеров с тремя самыми результативными спортивными динозаврами, вы можете сделать следующее:
Ява
scoresRef.orderByValue().limitToFirst(3).addChildEventListener(newChildEventListener(){@OverridepublicvoidonChildAdded(DataSnapshotdataSnapshot,StringprevChildKey){System.out.println("The "+dataSnapshot.getKey()+" score is "+dataSnapshot.getValue());}// ...});
Node.js
constscoresRef=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()forkey,valinsnapshot.items():print('The {0} dinosaur\'s score is {1}'.format(key,val))
Идти
ref:=client.NewRef("scores")results,err:=ref.OrderByValue().LimitToLast(3).GetOrdered(ctx)iferr!=nil{log.Fatalln("Error querying database:",err)}for_,r:=rangeresults{varscoreintiferr:=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() :
Вы можете объединить startAt() и endAt() чтобы ограничить оба конца вашего запроса. В следующем примере находятся все динозавры, имена которых начинаются с буквы «b»:
МетодqualTo equalTo() позволяет фильтровать на основе точных совпадений. Как и в случае с другими запросами диапазона, он будет срабатывать для каждого соответствующего дочернего узла. Например, вы можете использовать следующий запрос, чтобы найти всех динозавров ростом 25 метров:
Запросы диапазона также полезны, когда вам нужно разбить данные на страницы.
Собираем все это вместе
Вы можете комбинировать все эти методы для создания сложных запросов. Например, вы можете найти название динозавра, которое короче Стегозавра:
Ява
dinosaursRef.child("stegosaurus").child("height").addValueEventListener(newValueEventListener(){@OverridepublicvoidonDataChange(DataSnapshotstegoHeightSnapshot){IntegerfavoriteDinoHeight=stegoHeightSnapshot.getValue(Integer.class);Queryquery=dinosaursRef.orderByChild("height").endAt(favoriteDinoHeight).limitToLast(2);query.addValueEventListener(newValueEventListener(){@OverridepublicvoidonDataChange(DataSnapshotdataSnapshot){// Data is ordered by increasing height, so we want the first entryDataSnapshotfirstChild=dataSnapshot.getChildren().iterator().next();System.out.println("The dinosaur just shorter than the stegosaurus is: "+firstChild.getKey());}@OverridepublicvoidonCancelled(DatabaseErrordatabaseError){// ...}});}@OverridepublicvoidonCancelled(DatabaseErrordatabaseError){// ...}});
Node.js
constref=db.ref('dinosaurs');ref.child('stegosaurus').child('height').on('value',(stegosaurusHeightSnapshot)=>{constfavoriteDinoHeight=stegosaurusHeightSnapshot.val();constqueryRef=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 entryquerySnapshot.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 timereturntrue;});}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()iflen(snapshot)==2:# Data is ordered by increasing height, so we want the first entry.# Second entry is stegosarus.forkeyinsnapshot:print('The dinosaur just shorter than the stegosaurus is {0}'.format(key))returnelse:print('The stegosaurus is the shortest dino')
Идти
ref:=client.NewRef("dinosaurs")varfavDinoHeightintiferr:=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)iferr!=nil{log.Fatalln("Error querying database:",err)}iflen(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() данные, содержащие указанный дочерний ключ, упорядочиваются следующим образом:
Дочерние элементы с null значением для указанного дочернего ключа идут первыми.
Следующими идут дочерние элементы со значением false для указанного дочернего ключа. Если несколько дочерних элементов имеют значение false , они сортируются лексикографически по ключу.
Следующими идут дочерние элементы со значением true для указанного дочернего ключа. Если несколько дочерних элементов имеют значение true , они сортируются лексикографически по ключу.
Следующими идут дети с числовым значением, отсортированные в порядке возрастания. Если несколько дочерних узлов имеют одинаковое числовое значение для указанного дочернего узла, они сортируются по ключу.
Строки идут после чисел и сортируются лексикографически по возрастанию. Если несколько дочерних узлов имеют одинаковое значение для указанного дочернего узла, они упорядочиваются лексикографически по ключу.
Объекты идут последними и сортируются лексикографически по ключу в порядке возрастания.
заказ по ключу
При использовании orderByKey() для сортировки данных данные возвращаются в порядке возрастания ключа следующим образом. Имейте в виду, что ключи могут быть только строками.
Первыми идут дочерние элементы с ключом, который можно проанализировать как 32-битное целое число, отсортированное в порядке возрастания.
Следующими идут дочерние элементы со строковым значением в качестве ключа, отсортированные лексикографически в порядке возрастания.
заказ по значению
При использовании orderByValue() дочерние элементы упорядочиваются по их значению. Критерии упорядочивания такие же, как и в orderByChild() , за исключением того, что вместо значения указанного дочернего ключа используется значение узла.
[[["Прост для понимания","easyToUnderstand","thumb-up"],["Помог мне решить мою проблему","solvedMyProblem","thumb-up"],["Другое","otherUp","thumb-up"]],[["Отсутствует нужная мне информация","missingTheInformationINeed","thumb-down"],["Слишком сложен/слишком много шагов","tooComplicatedTooManySteps","thumb-down"],["Устарел","outOfDate","thumb-down"],["Проблема с переводом текста","translationIssue","thumb-down"],["Проблемы образцов/кода","samplesCodeIssue","thumb-down"],["Другое","otherDown","thumb-down"]],["Последнее обновление: 2025-04-04 UTC."],[],[]]