swift - docs - firebase firestore set




Firestore: come ottenere documenti casuali in una raccolta (4)

È fondamentale per la mia applicazione poter selezionare più documenti a caso da una raccolta in firebase.

Poiché non esiste alcuna funzione nativa incorporata in Firebase (di cui sono a conoscenza) per ottenere una query che faccia esattamente questo, il mio primo pensiero è stato quello di utilizzare i cursori di query per selezionare un indice di inizio e fine casuale a condizione che io abbia il numero di documenti in la collezione.

Questo approccio avrebbe funzionato, ma solo in modo limitato poiché ogni documento sarebbe stato pubblicato ogni volta in sequenza con i documenti vicini; tuttavia, se fossi in grado di selezionare un documento in base al suo indice nella sua raccolta padre, potrei ottenere una query casuale sul documento, ma il problema è che non riesco a trovare alcuna documentazione che descriva come puoi farlo o anche se puoi farlo.

Ecco cosa mi piacerebbe essere in grado di fare, considera il seguente schema di firestore:

root/
  posts/
     docA
     docB
     docC
     docD

Quindi nel mio client (mi trovo in un ambiente Swift) vorrei scrivere una query che può fare questo:

db.collection("posts")[0, 1, 3] // would return: docA, docB, docD

Posso comunque fare qualcosa del genere? Oppure, c'è un modo diverso di selezionare documenti casuali in modo simile?

Per favore aiuto.


Ho appena fatto questo lavoro in Angular 7 + RxJS, quindi condividilo qui con persone che vogliono un esempio.

Ho usato la risposta di @Dan McGrath e ho scelto queste opzioni: Versione intera casuale + Risciacqua e ripeti per più numeri. Ho anche usato le cose spiegate in questo articolo: RxJS, dov'è l'operatore If-Else? per fare dichiarazioni if ​​/ else a livello di flusso (solo se qualcuno di voi ha bisogno di un primer su quello).

Nota anche che ho usato angularfire2 per una facile integrazione Firebase in Angular.

Ecco il codice:

import { Component, OnInit } from '@angular/core';
import { Observable, merge, pipe } from 'rxjs';
import { map, switchMap, filter, take } from 'rxjs/operators';
import { AngularFirestore, QuerySnapshot } from '@angular/fire/firestore';

@Component({
  selector: 'pp-random',
  templateUrl: './random.component.html',
  styleUrls: ['./random.component.scss']
})
export class RandomComponent implements OnInit {

  constructor(
    public afs: AngularFirestore,
  ) { }

  ngOnInit() {
  }

  public buttonClicked(): void {
    this.getRandom().pipe(take(1)).subscribe();
  }

  public getRandom(): Observable<any[]> {
    const randomNumber = this.getRandomNumber();
    const request$ = this.afs.collection('your-collection', ref => ref.where('random', '>=', randomNumber).orderBy('random').limit(1)).get();
    const retryRequest$ = this.afs.collection('your-collection', ref => ref.where('random', '<=', randomNumber).orderBy('random', 'desc').limit(1)).get();

    const docMap = pipe(
      map((docs: QuerySnapshot<any>) => {
        return docs.docs.map(e => {
          return {
            id: e.id,
            ...e.data()
          } as any;
        });
      })
    );

    const random$ = request$.pipe(docMap).pipe(filter(x => x !== undefined && x[0] !== undefined));

    const retry$ = request$.pipe(docMap).pipe(
      filter(x => x === undefined || x[0] === undefined),
      switchMap(() => retryRequest$),
      docMap
    );

    return merge(random$, retry$);
  }

  public getRandomNumber(): number {
    const min = Math.ceil(Number.MIN_VALUE);
    const max = Math.ceil(Number.MAX_VALUE);
    return Math.floor(Math.random() * (max - min + 1)) + min;
  }
}

Ho un modo per ottenere casualmente un documento elenco in Firebase Firestore, è davvero facile. Quando carico i dati su Firestore creo un nome campo "posizione" con un valore casuale compreso tra 1 e 1 milione. Quando ottengo i dati dal Fire Store imposterò Ordina per campo "Posizione" e aggiornerò il valore per esso, molti dati di caricamento dell'utente e dati si aggiornano sempre e sarà un valore casuale.


Pubblicare questo per aiutare chiunque abbia questo problema in futuro.

Se si utilizzano ID automatici, è possibile generare un nuovo ID automatico e richiedere l'ID automatico più vicino, come indicato nella risposta di Dan McGrath .

Di recente ho creato un api di citazione casuale e avevo bisogno di ottenere citazioni casuali da una collezione di firestore.
Ecco come ho risolto questo problema:

var db = admin.firestore();
var quotes = db.collection("quotes");

var key = quotes.doc().id;

quotes.where(admin.firestore.FieldPath.documentId(), '>', key).limit(1).get()
.then(snapshot => {
    if(snapshot.size > 0) {
        snapshot.forEach(doc => {
            console.log(doc.id, '=>', doc.data());
        });
    }
    else {
        var quote = quotes.where(admin.firestore.FieldPath.documentId(), '<', key).limit(1).get()
        .then(snapshot => {
            snapshot.forEach(doc => {
                console.log(doc.id, '=>', doc.data());
            });
        })
        .catch(err => {
            console.log('Error getting documents', err);
        });
    }
})
.catch(err => {
    console.log('Error getting documents', err);
});

La chiave della query è questa:

.where(admin.firestore.FieldPath.documentId(), '>', key)

E richiamandolo nuovamente con l'operazione invertita se non viene trovato alcun documento.

Spero che questo possa essere d'aiuto!
Se interessati, puoi trovare questa parte specifica della mia API su GitHub


Utilizzando indici generati casualmente e semplici query, è possibile selezionare casualmente documenti da una raccolta o un gruppo di raccolta in Cloud Firestore.

Questa risposta è suddivisa in 4 sezioni con diverse opzioni in ciascuna sezione:

  1. Come generare gli indici casuali
  2. Come interrogare gli indici casuali
  3. Selezione di più documenti casuali
  4. Rinnovando la casualità in corso

Come generare gli indici casuali

La base di questa risposta è la creazione di un campo indicizzato che, se ordinato in ordine crescente o decrescente, determina l'ordine casuale di tutto il documento. Esistono diversi modi per creare questo, quindi diamo un'occhiata a 2, iniziando dal più facilmente disponibile.

Versione ID automatico

Se si utilizzano gli ID automatici generati casualmente forniti nelle nostre librerie client, è possibile utilizzare lo stesso sistema per selezionare in modo casuale un documento. In questo caso, l'indice ordinato casualmente è l'ID del documento.

Più avanti nella nostra sezione delle query, il valore casuale che generi è un nuovo ID automatico ( iOS , Android , Web ) e il campo su cui __name__ le query è il campo __name__ e il "valore basso" menzionato più avanti è una stringa vuota. Questo è di gran lunga il metodo più semplice per generare l'indice casuale e funziona indipendentemente dalla lingua e dalla piattaforma.

Per impostazione predefinita, il nome del documento ( __name__ ) è indicizzato solo in ordine crescente e non è inoltre possibile rinominare un documento esistente a meno dell'eliminazione e della ricreazione. Se hai bisogno di uno di questi, puoi comunque utilizzare questo metodo e archiviare un ID automatico come un campo effettivo chiamato random anziché sovraccaricare il nome del documento a questo scopo.

Versione intera casuale

Quando scrivi un documento, prima genera un numero intero casuale in un intervallo limitato e impostalo come un campo chiamato random . A seconda del numero di documenti previsti, è possibile utilizzare un intervallo limitato diverso per risparmiare spazio o ridurre il rischio di collisioni (che riducono l'efficacia di questa tecnica).

Dovresti considerare quali lingue sono necessarie in quanto ci saranno diverse considerazioni. Mentre Swift è facile, JavaScript in particolare può avere un gotcha:

  • Numero intero a 32 bit: ottimo per set di dati di piccole dimensioni (~ 10K con probabilità di collisione )
  • Numero intero a 64 bit: set di dati di grandi dimensioni (nota: JavaScript non supporta yet nativamente)

Questo creerà un indice con i tuoi documenti ordinati casualmente. Più avanti nella nostra sezione di query, il valore casuale che genererai sarà un altro di questi valori e il "valore basso" menzionato più avanti sarà -1.

Come interrogare gli indici casuali

Ora che hai un indice casuale, ti consigliamo di interrogarlo. Di seguito esaminiamo alcune semplici varianti per selezionare un documento casuale 1, nonché opzioni per selezionare più di 1.

Per tutte queste opzioni, ti consigliamo di generare un nuovo valore casuale nella stessa forma dei valori indicizzati creati durante la scrittura del documento, indicato dalla variabile random seguito. Useremo questo valore per trovare un punto casuale sull'indice.

Arrotolare

Ora che hai un valore casuale, puoi eseguire una query per un singolo documento:

let postsRef = db.collection("posts")
queryRef = postsRef.whereField("random", isGreaterThanOrEqualTo: random)
                   .order(by: "random")
                   .limit(to: 1)

Verifica che questo abbia restituito un documento. In caso contrario, eseguire nuovamente la query ma utilizzare il "valore basso" per l'indice casuale. Ad esempio, se hai eseguito lowValue casuali, allora lowValue è 0 :

let postsRef = db.collection("posts")
queryRef = postsRef.whereField("random", isGreaterThanOrEqualTo: lowValue)
                   .order(by: "random")
                   .limit(to: 1)

Finché hai un singolo documento, ti verrà garantito di restituire almeno 1 documento.

Bidirezionale

Il metodo avvolgente è semplice da implementare e consente di ottimizzare la memoria con solo un indice crescente abilitato. Un aspetto negativo è la possibilità che i valori vengano ingiustamente protetti. Ad esempio, se i primi 3 documenti (A, B, C) su 10K hanno valori di indice casuali di A: 409496, B: 436496, C: 818992, allora A e C hanno poco meno di 1/10 K di possibilità di essere selezionati, mentre B è effettivamente schermato dalla vicinanza di A e solo all'incirca una possibilità di 1 / 160K.

Anziché eseguire una query in una sola direzione e spostarsi se non viene trovato un valore, è possibile invece selezionare casualmente tra >= e <= , il che riduce della metà la probabilità di valori ingiustamente schermati, al costo del doppio della memoria dell'indice.

Se una direzione non restituisce risultati, passare all'altra direzione:

queryRef = postsRef.whereField("random", isLessThanOrEqualTo: random)
                   .order(by: "random", descending: true)
                   .limit(to: 1)

queryRef = postsRef.whereField("random", isGreaterThanOrEqualTo: random)
                   .order(by: "random")
                   .limit(to: 1)

Selezione di più documenti casuali

Spesso, ti consigliamo di selezionare più di 1 documento casuale alla volta. Esistono 2 modi diversi per adattare le tecniche di cui sopra a seconda dei compromessi che desideri.

Risciacqua e ripeti

Questo metodo è semplice. Basta ripetere il processo, inclusa la selezione di un nuovo numero intero casuale ogni volta.

Questo metodo ti darà sequenze casuali di documenti senza preoccuparti di vedere ripetutamente gli stessi schemi.

Il compromesso è che sarà più lento del metodo successivo poiché richiede un round trip separato al servizio per ciascun documento.

Continuate a venire

In questo approccio, è sufficiente aumentare il numero nel limite dei documenti desiderati. È un po 'più complesso in quanto potresti restituire 0..limit documenti nella chiamata. Dovrai quindi ottenere i documenti mancanti nello stesso modo, ma con il limite ridotto solo alla differenza. Se sai che ci sono più documenti in totale rispetto al numero che stai chiedendo, puoi ottimizzare ignorando il limite per non recuperare mai abbastanza documenti alla seconda chiamata (ma non alla prima).

Il compromesso con questa soluzione è in sequenze ripetute. Mentre i documenti vengono ordinati in modo casuale, se mai finisci per sovrapporsi a intervalli vedrai lo stesso modello che hai visto prima. Ci sono modi per mitigare questa preoccupazione discussa nella prossima sezione sul reseeding.

Questo approccio è più veloce di "Risciacqua e ripeti" poiché nel migliore dei casi richiederai una sola chiamata o nel caso peggiore 2 chiamate.

Rinnovando la casualità in corso

Mentre questo metodo fornisce documenti casualmente se l'insieme di documenti è statico, anche la probabilità che ciascun documento venga restituito sarà statica. Questo è un problema in quanto alcuni valori potrebbero avere probabilità ingiustamente basse o alte in base ai valori casuali iniziali che hanno ottenuto. In molti casi d'uso, questo va bene, ma in alcuni, potresti voler aumentare la casualità a lungo termine per avere una possibilità più uniforme di restituire 1 documento.

Si noti che i documenti inseriti finiranno nel mezzo, cambiando gradualmente le probabilità, così come l'eliminazione dei documenti. Se la velocità di inserimento / eliminazione è troppo piccola dato il numero di documenti, ci sono alcune strategie per risolverlo.

Multi-Random

Invece di preoccuparti del reseeding, puoi sempre creare più indici casuali per documento, quindi selezionare casualmente uno di quegli indici ogni volta. Ad esempio, fare in modo che il campo random sia una mappa con sottocampi da 1 a 3:

{'random': {'1': 32456, '2':3904515723, '3': 766958445}}

Ora eseguirai query su random.1, random.2, random.3 in modo casuale, creando una maggiore diffusione di casualità. Questo essenzialmente scambia un maggiore spazio di archiviazione per risparmiare un maggiore calcolo (scritture di documenti) di dover ridimensionare.

Rinviato alle scritture

Ogni volta che si aggiorna un documento, rigenerare i valori random campo random . Ciò sposterà il documento nell'indice casuale.

Puntato su letture

Se i valori casuali generati non sono distribuiti uniformemente (sono casuali, quindi è previsto), lo stesso documento potrebbe essere prelevato per un periodo di tempo inappropriato. Ciò viene facilmente contrastato aggiornando il documento selezionato in modo casuale con nuovi valori casuali dopo che è stato letto.

Poiché le scritture sono più costose e possono essere hotspot, è possibile scegliere di aggiornare solo in lettura un sottoinsieme dell'ora (ad esempio, if random(0,100) === 0) update; ).





google-cloud-firestore