Выберите случайный документ из Firestore
У меня есть 1000 документов в одной коллекции в облачном Firestore, возможно ли получить случайные документы?
Скажем, например: Students
это коллекция в Firestore, и у меня 1000 студентов в этой коллекции, мое требование - выбирать 10 студентов случайным образом при каждом вызове.
Переведено автоматически
Ответ 1
Согласно ответу Алекса я получил подсказку, как получить дубликаты записей из базы данных Firebase Firestore (специально для небольшого объема данных)
У меня возникли некоторые проблемы в его вопросе следующим образом:
- Он выдает все записи такими же, как
randomNumber
не обновляется. - В конечном списке могут быть повторяющиеся записи, даже если мы обновляем
randomNumber
каждый раз. - В нем могут быть дублирующиеся записи, которые мы уже показываем.
Я обновил ответ следующим образом:
FirebaseFirestore database = FirebaseFirestore.getInstance();
CollectionReference collection = database.collection(VIDEO_PATH);
collection.get().addOnCompleteListener(new OnCompleteListener<QuerySnapshot>() {
@Override
public void onComplete(@NonNull Task<QuerySnapshot> task) {
if (task.isSuccessful()) {
List<VideoModel> videoModelList = new ArrayList<>();
for (DocumentSnapshot document : Objects.requireNonNull(task.getResult())) {
VideoModel student = document.toObject(VideoModel.class);
videoModelList.add(student);
}
/* Get Size of Total Items */
int size = videoModelList.size();
/* Random Array List */
ArrayList<VideoModel> randomVideoModels = new ArrayList<>();
/* for-loop: It will loop all the data if you want
* RANDOM + UNIQUE data.
* */
for (int i = 0; i < size; i++) {
// Getting random number (inside loop just because every time we'll generate new number)
int randomNumber = new Random().nextInt(size);
VideoModel model = videoModelList.get(randomNumber);
// Check with current items whether its same or not
// It will helpful when you want to show related items excepting current item
if (!model.getTitle().equals(mTitle)) {
// Check whether current list is contains same item.
// May random number get similar again then its happens
if (!randomVideoModels.contains(model))
randomVideoModels.add(model);
// How many random items you want
// I want 6 items so It will break loop if size will be 6.
if (randomVideoModels.size() == 6) break;
}
}
// Bind adapter
if (randomVideoModels.size() > 0) {
adapter = new RelatedVideoAdapter(VideoPlayerActivity.this, randomVideoModels, VideoPlayerActivity.this);
binding.recyclerView.setAdapter(adapter);
}
} else {
Log.d("TAG", "Error getting documents: ", task.getException());
}
}
});
Надеюсь, эта логика поможет всем, у кого небольшой объем данных, и я не думаю, что это создаст какие-либо проблемы для данных от 1000 до 5000.
Спасибо.
Ответ 2
Да, это так, и для достижения этой цели, пожалуйста, используйте следующий код:
FirebaseFirestore rootRef = FirebaseFirestore.getInstance();
CollectionReference studentsCollectionReference = rootRef.collection("students");
studentsCollectionReference.get().addOnCompleteListener(new OnCompleteListener<QuerySnapshot>() {
@Override
public void onComplete(@NonNull Task<QuerySnapshot> task) {
if (task.isSuccessful()) {
List<Student> studentList = new ArrayList<>();
for (DocumentSnapshot document : task.getResult()) {
Student student = document.toObject(Student.class);
studentList.add(student);
}
int studentListSize = studentList.size();
List<Students> randomStudentList = new ArrayList<>();
for(int i = 0; i < studentListSize; i++) {
Student randomStudent = studentList.get(new Random().nextInt(studentListSize));
if(!randomStudentList.contains(randomStudent)) {
randomStudentList.add(randomStudent);
if(randomStudentList.size() == 10) {
break;
}
}
}
} else {
Log.d(TAG, "Error getting documents: ", task.getException());
}
}
});
Это называется классическим решением, и вы можете использовать его для коллекций, содержащих всего несколько записей, но если вы боитесь получить огромное количество чтений, я порекомендую вам этот второй подход. Это также включает небольшое изменение в вашей базе данных путем добавления нового документа, который может содержать массив со всеми идентификаторами учащихся. Итак, чтобы получить эти случайные 10 студентов, вам нужно будет выполнить только get()
вызов, который подразумевает только одну операцию чтения. Получив этот массив, вы можете использовать тот же алгоритм и получить эти 10 случайных идентификаторов. Получив эти случайные идентификаторы, вы можете получить соответствующие документы и добавить их в список. Таким образом, вы выполняете еще только 10 операций чтения, чтобы получить реальных случайных учеников. Всего выполняется только 11 операций чтения документа.
Эта практика называется денормализацией (дублированием данных) и является обычной практикой, когда дело доходит до Firebase. Если вы новичок в базе данных NoSQL, поэтому для лучшего понимания я рекомендую вам посмотреть это видео, Денормализация является обычной в базе данных Firebase. Это для базы данных Firebase в реальном времени, но те же принципы применимы и к облачному Firestore.
Но помните, что при добавлении случайных продуктов в этот новый созданный узел вам нужно точно так же удалять их, когда они больше не нужны.
Чтобы добавить идентификатор студента в массив, просто используйте:
FieldValue.arrayUnion("yourArrayProperty")
А чтобы удалить студенческий билет, пожалуйста, используйте:
FieldValue.arrayRemove("yourArrayProperty")
Чтобы получить всех 10 случайных учеников одновременно, вы можете использовать List<Task<DocumentSnapshot>>
, а затем вызвать Tasks.whenAllSuccess(tasks)
, как описано в моем ответе из этого поста:
Ответ 3
Я столкнулся с аналогичной проблемой (мне нужно было получать только один случайный документ каждые 24 часа или когда пользователи обновляют страницу вручную, но вы можете применить это решение и в вашем случае), и у меня сработало следующее:
Техника
- Впервые прочитайте небольшой список документов, скажем, от 1 до 10 документов (от 10 до 30 или 50 в вашем случае).
- Выберите случайный документ (ы) на основе случайно сгенерированного числа (ов) в пределах диапазона списка документов.
- Сохраните последний идентификатор выбранного вами документа локально на клиентском устройстве (возможно, в общих настройках, как это сделал я).
- если вам нужен новый случайный документ (ы), вы будете использовать сохраненный идентификатор документа, чтобы снова запустить процесс (шаги 1-3) после сохраненного идентификатора документа, который исключит все документы, которые появлялись ранее.
- Повторяйте процесс до тех пор, пока после сохраненного идентификатора документа больше не останется документов, затем начните заново с начала, предполагая, что вы впервые запускаете этот алгоритм (установив для идентификатора сохраненного документа значение null) и запустите процесс снова (шаги 1-4).
Плюсы и минусы метода
Плюсы:
- Вы можете определять размер перехода каждый раз, когда получаете новый случайный документ (ы).
- Нет необходимости изменять исходный класс модели вашего объекта.
- Нет необходимости изменять базу данных, которая у вас уже есть или которую вы разработали.
- Нет необходимости добавлять документ в коллекцию и обрабатывать добавление случайного идентификатора для каждого документа при добавлении нового документа в коллекцию, как в решении, упомянутом здесь.
- Нет необходимости загружать большой список документов, чтобы получить только один документ или список документов небольшого размера,
- Хорошо работает, если вы используете автоматически сгенерированный firestore идентификатор (потому что документы внутри коллекции уже слегка рандомизированы)
- Хорошо работает, если вам нужен один случайный документ или случайный список документов небольшого размера.
- Работает на всех платформах (включая iOS, Android, Web).
Минусы
- Обработайте сохранение идентификатора документа для использования в следующем запросе на получение случайных документов (что лучше, чем обработка нового поля в каждом документе или обработка добавления идентификаторов для каждого документа в коллекции в новый документ в основной коллекции)
- Некоторые документы могут быть получены более одного раза, если список недостаточно большой (в моем случае это не было проблемой), и я не нашел никакого решения, которое полностью избегало бы этого случая.
Реализация (kotlin на Android):
var documentId = //get document id from shared preference (will be null if not set before)
getRandomDocument(documentId)
fun getRandomDocument(documentId: String?) {
if (documentId == null) {
val query = FirebaseFirestore.getInstance()
.collection(COLLECTION_NAME)
.limit(getLimitSize())
loadDataWithQuery(query)
} else {
val docRef = FirebaseFirestore.getInstance()
.collection(COLLECTION_NAME).document(documentId)
docRef.get().addOnSuccessListener { documentSnapshot ->
val query = FirebaseFirestore.getInstance()
.collection(COLLECTION_NAME)
.startAfter(documentSnapshot)
.limit(getLimitSize())
loadDataWithQuery(query)
}.addOnFailureListener { e ->
// handle on failure
}
}
}
fun loadDataWithQuery(query: Query) {
query.get().addOnSuccessListener { queryDocumentSnapshots ->
val documents = queryDocumentSnapshots.documents
if (documents.isNotEmpty() && documents[documents.size - 1].exists()) {
//select one document from the loaded list (I selected the last document in the list)
val snapshot = documents[documents.size - 1]
var documentId = snapshot.id
//SAVE the document id in shared preferences here
//handle the random document here
} else {
//handle in case you reach to the end of the list of documents
//so we start over again as this is the first time we get a random document
//by calling getRandomDocument() with a null as a documentId
getRandomDocument(null)
}
}
}
fun getLimitSize(): Long {
val random = Random()
val listLimit = 10
return (random.nextInt(listLimit) + 1).toLong()
}
Ответ 4
На основе ответа @ajzbc я написал это для Unity3D, и это работает у меня.
FirebaseFirestore db;
void Start()
{
db = FirebaseFirestore.DefaultInstance;
}
public void GetRandomDocument()
{
Query query1 = db.Collection("Sports").WhereGreaterThanOrEqualTo(FieldPath.DocumentId, db.Collection("Sports").Document().Id).Limit(1);
Query query2 = db.Collection("Sports").WhereLessThan(FieldPath.DocumentId, db.Collection("Sports").Document().Id).Limit(1);
query1.GetSnapshotAsync().ContinueWithOnMainThread((querySnapshotTask1) =>
{
if(querySnapshotTask1.Result.Count > 0)
{
foreach (DocumentSnapshot documentSnapshot in querySnapshotTask1.Result.Documents)
{
Debug.Log("Random ID: "+documentSnapshot.Id);
}
} else
{
query2.GetSnapshotAsync().ContinueWithOnMainThread((querySnapshotTask2) =>
{
foreach (DocumentSnapshot documentSnapshot in querySnapshotTask2.Result.Documents)
{
Debug.Log("Random ID: " + documentSnapshot.Id);
}
});
}
});
}