445 Stimmen

Zufälliger Datensatz aus MongoDB

Ich möchte einen zufälligen Datensatz aus einer riesigen Sammlung (100 Millionen Datensätze) abrufen.

Was ist der schnellste und effizienteste Weg, dies zu tun?

Die Daten sind bereits vorhanden und es gibt keine Felder, in denen ich eine Zufallszahl generieren und eine zufällige Zeile erhalten kann.

404voto

JohnnyHK Punkte 289697

Seit der Version 3.2 von MongoDB können Sie N zufällige Dokumente aus einer Sammlung abrufen, indem Sie die $sample Aggregations-Pipeline-Betreiber:

// Get one random document from the mycoll collection.
db.mycoll.aggregate([{ $sample: { size: 1 } }])

Wenn Sie das/die Zufallsdokument(e) aus einer gefilterten Teilmenge der Sammlung auswählen möchten, stellen Sie eine $match Stufe in die Pipeline:

// Get one random document matching {a: 10} from the mycoll collection.
db.mycoll.aggregate([
    { $match: { a: 10 } },
    { $sample: { size: 1 } }
])

Wie in den Kommentaren angemerkt, wenn size größer als 1 ist, kann es in der zurückgegebenen Dokumentenprobe Duplikate geben.

123voto

ceejayoz Punkte 170567

Zählen Sie alle Datensätze, generieren Sie eine Zufallszahl zwischen 0 und der Zählung, und machen Sie dann:

db.yourCollection.find().limit(-1).skip(yourRandomNumber).next()

93voto

Michael Punkte 1769

Aktualisierung für MongoDB 3.2

3.2 eingeführt $Probe an die Aggregationspipeline.

Es gibt auch eine gute Blogeintrag in die Praxis umzusetzen.

Für ältere Versionen (vorherige Antwort)

Dies war eigentlich eine Anfrage für eine Funktion: http://jira.mongodb.org/browse/SERVER-533 aber es wurde unter "Wird nicht repariert" abgelegt.

Das Kochbuch enthält ein sehr gutes Rezept für die Auswahl eines beliebigen Dokuments aus einer Sammlung: http://cookbook.mongodb.org/patterns/random-attribute/

Um das Rezept zu paraphrasieren, weisen Sie Ihren Dokumenten Zufallsnummern zu:

db.docs.save( { key : 1, ..., random : Math.random() } )

Wählen Sie dann ein beliebiges Dokument aus:

rand = Math.random()
result = db.docs.findOne( { key : 2, random : { $gte : rand } } )
if ( result == null ) {
  result = db.docs.findOne( { key : 2, random : { $lte : rand } } )
}

Abfrage mit beiden $gte y $lte ist notwendig, um das Dokument mit einer Zufallszahl zu finden, die der rand .

Und natürlich müssen Sie das Zufallsfeld indizieren:

db.docs.ensureIndex( { key : 1, random :1 } )

Wenn Sie bereits eine Abfrage gegen einen Index durchführen, lassen Sie ihn einfach fallen, fügen Sie random: 1 und fügen Sie es wieder hinzu.

57voto

Nico de Poel Punkte 772

Sie können auch die geografische Indizierungsfunktion von MongoDB verwenden, um die Dokumente auszuwählen, die einer Zufallszahl am nächsten liegen.

Aktivieren Sie zunächst die raumbezogene Indizierung einer Sammlung:

db.docs.ensureIndex( { random_point: '2d' } )

So erstellen Sie eine Reihe von Dokumenten mit zufälligen Punkten auf der X-Achse:

for ( i = 0; i < 10; ++i ) {
    db.docs.insert( { key: i, random_point: [Math.random(), 0] } );
}

Dann können Sie ein zufälliges Dokument aus der Sammlung wie folgt abrufen:

db.docs.findOne( { random_point : { $near : [Math.random(), 0] } } )

Oder Sie können mehrere Dokumente abrufen, die einem beliebigen Punkt am nächsten liegen:

db.docs.find( { random_point : { $near : [Math.random(), 0] } } ).limit( 4 )

Dies erfordert nur eine Abfrage und keine Nullprüfungen, und der Code ist sauber, einfach und flexibel. Sie könnten sogar die Y-Achse des Geopunkts verwenden, um eine zweite Zufallsdimension zu Ihrer Abfrage hinzuzufügen.

21voto

spam_eggs Punkte 1058

Das folgende Rezept ist etwas langsamer als die Lösung aus dem Mongo-Kochbuch (Hinzufügen eines Zufallsschlüssels für jedes Dokument), liefert aber gleichmäßiger verteilte Zufallsdokumente. Es ist etwas ungleichmäßiger verteilt als die skip( random ) Lösung, aber viel schneller und ausfallsicherer für den Fall, dass Dokumente entfernt werden.

function draw(collection, query) {
    // query: mongodb query object (optional)
    var query = query || { };
    query['random'] = { $lte: Math.random() };
    var cur = collection.find(query).sort({ rand: -1 });
    if (! cur.hasNext()) {
        delete query.random;
        cur = collection.find(query).sort({ rand: -1 });
    }
    var doc = cur.next();
    doc.random = Math.random();
    collection.update({ _id: doc._id }, doc);
    return doc;
}

Außerdem müssen Sie Ihren Dokumenten ein Zufallsfeld hinzufügen. Vergessen Sie also nicht, dieses bei der Erstellung der Dokumente hinzuzufügen: Möglicherweise müssen Sie Ihre Sammlung wie von Geoffrey gezeigt initialisieren

function addRandom(collection) { 
    collection.find().forEach(function (obj) {
        obj.random = Math.random();
        collection.save(obj);
    }); 
} 
db.eval(addRandom, db.things);

Benchmark-Ergebnisse

Diese Methode ist viel schneller als die skip() Methode (von ceejayoz) und erzeugt gleichmäßigere Zufallsdokumente als die von Michael berichtete "Kochbuch"-Methode:

Für eine Sammlung mit 1.000.000 Elementen:

  • Diese Methode dauert auf meinem Rechner weniger als eine Millisekunde

  • die skip() Methode dauert im Durchschnitt 180 ms

Die Kochbuch-Methode führt dazu, dass eine große Anzahl von Dokumenten nie ausgewählt wird, weil ihre Zufallszahl sie nicht bevorzugt.

  • Mit dieser Methode werden alle Elemente gleichmäßig über die Zeit erfasst.

  • In meinem Benchmark war sie nur 30 % langsamer als die Kochbuchmethode.

  • die Zufälligkeit ist nicht 100%ig perfekt, aber sehr gut (und sie kann bei Bedarf verbessert werden)

Dieses Rezept ist nicht perfekt - die perfekte Lösung wäre eine eingebaute Funktion, wie andere bereits festgestellt haben.
Für viele Zwecke dürfte es jedoch ein guter Kompromiss sein.

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X