Zufällige Aufzeichnung von MongoDB

336

Ich suche nach einer zufälligen Aufzeichnung von einer riesigen (100 Millionen Aufzeichnung) mongodb.

Was ist der schnellste und effizienteste Weg, dies zu tun? Die Daten sind bereits vorhanden und es gibt kein Feld, in dem ich eine Zufallszahl generieren und eine Zufallszeile erhalten kann.

Irgendwelche Vorschläge?

Will M.
quelle
2
Siehe auch diese SO-Frage mit dem Titel "Eine Ergebnismenge zufällig in Mongo bestellen" . Das Nachdenken über die zufällige Reihenfolge einer Ergebnismenge ist eine allgemeinere Version dieser Frage - leistungsfähiger und nützlicher.
David J.
11
Diese Frage taucht immer wieder auf. Die neuesten Informationen finden Sie wahrscheinlich in der Funktionsanforderung, um zufällige Elemente aus einer Sammlung im MongoDB-Ticket-Tracker abzurufen. Bei einer nativen Implementierung wäre dies wahrscheinlich die effizienteste Option. (Wenn Sie die Funktion möchten, stimmen Sie ab.)
David J.
Ist das eine Sharded-Sammlung?
Dylan Tong
3
Die richtige Antwort wurde von @JohnnyHK unten gegeben: db.mycoll.aggregate ({$ sample: {size: 1}})
Florian
Weiß jemand, wie viel langsamer das ist, als nur die erste Platte aufzunehmen? Ich überlege, ob es sich lohnt, eine Zufallsstichprobe zu nehmen, um etwas zu tun, anstatt es nur in der richtigen Reihenfolge zu tun.
David Kong

Antworten:

247

Ab der Version 3.2 von MongoDB können Sie mithilfe des $sampleAggregationspipeline-Operators N zufällige Dokumente aus einer Sammlung abrufen :

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

Wenn Sie die zufälligen Dokumente aus einer gefilterten Teilmenge der Sammlung auswählen möchten, stellen Sie $matchder Pipeline eine Stufe voran :

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

Wie in den Kommentaren angegeben, sizekann das zurückgegebene Dokumentmuster Duplikate enthalten , wenn es größer als 1 ist.

JohnnyHK
quelle
12
Dies ist ein guter Weg, aber denken Sie daran, dass NICHT garantiert wird, dass das Beispiel keine Kopien desselben Objekts enthält.
Matheus Araujo
10
@ MatheusAraujo, was nicht wichtig ist, wenn Sie eine Platte wollen, aber trotzdem ein guter Punkt
Toby
3
Um nicht pedantisch zu sein, aber die Frage gibt keine MongoDB-Version an, daher würde ich davon ausgehen, dass es vernünftig ist, die neueste Version zu haben.
Dalanmiller
2
@Nepoxx Informationen zur Verarbeitung finden Sie in den Dokumenten .
JohnnyHK
2
@brycejl Das hätte den fatalen Fehler, nichts zu finden, wenn die $ sample-Phase keine passenden Dokumente ausgewählt hätte.
JohnnyHK
115

Zählen Sie alle Datensätze, generieren Sie eine Zufallszahl zwischen 0 und der Zählung und führen Sie dann Folgendes aus:

db.yourCollection.find().limit(-1).skip(yourRandomNumber).next()
ceejayoz
quelle
139
Leider ist skip () ziemlich ineffizient, da so viele Dokumente gescannt werden müssen. Es gibt auch eine Race-Bedingung, wenn Zeilen zwischen dem Abrufen der Anzahl und dem Ausführen der Abfrage entfernt werden.
mstearn
6
Beachten Sie, dass die Zufallszahl zwischen 0 und der Anzahl liegen sollte (exklusiv). Wenn Sie also 10 Elemente haben, sollte die Zufallszahl zwischen 0 und 9 liegen. Andernfalls könnte der Cursor versuchen, das letzte Element zu überspringen, und es wird nichts zurückgegeben.
Matt
4
Danke, hat perfekt für meine Zwecke funktioniert. @mstearn, Ihre Kommentare zu Effizienz und Rennbedingungen sind gültig, aber für Sammlungen, bei denen es nicht darauf ankommt (einmaliger serverseitiger Batch-Extrakt in einer Sammlung, in der Datensätze nicht gelöscht werden), ist dies dem Hacky (IMO) weit überlegen. Lösung im Mongo-Kochbuch.
Michael Moussa
4
Was bewirkt das Setzen des Limits auf -1?
MonkeyBonkey
@MonkeyBonkey docs.mongodb.org/meta-driver/latest/legacy/… "Wenn numberToReturn 0 ist, verwendet die Datenbank die Standardrückgabegröße. Wenn die Zahl negativ ist, gibt die Datenbank diese Zahl zurück und schließt den Cursor. ""
Ceejayoz
86

Update für MongoDB 3.2

3.2 führte $ sample in die Aggregationspipeline ein.

Es gibt auch einen guten Blog-Beitrag zur praktischen Umsetzung.

Für ältere Versionen (vorherige Antwort)

Dies war eigentlich eine Feature-Anfrage: http://jira.mongodb.org/browse/SERVER-533, aber sie wurde unter "Wird nicht repariert" abgelegt.

Das Kochbuch enthält ein sehr gutes Rezept, um ein zufälliges Dokument aus einer Sammlung auszuwählen: http://cookbook.mongodb.org/patterns/random-attribute/

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

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

Wählen Sie dann ein zufälliges 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 $gteund $lteist erforderlich, um das Dokument mit einer Zufallszahl am nächsten zu finden rand.

Und natürlich möchten Sie das Zufallsfeld indizieren:

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

Wenn Sie bereits einen Index abfragen, legen Sie ihn einfach ab, hängen random: 1Sie ihn an und fügen Sie ihn erneut hinzu.

Michael
quelle
7
Und hier ist eine einfache Möglichkeit, das Zufallsfeld zu jedem Dokument in der Sammlung hinzuzufügen. Funktion setRandom () {db.topics.find (). forEach (Funktion (obj) {obj.random = Math.random (); db.topics.save (obj);}); } db.eval (setRandom);
Geoffrey
8
Dadurch wird ein Dokument zufällig ausgewählt. Wenn Sie es jedoch mehrmals ausführen, sind die Suchvorgänge nicht unabhängig. Es ist wahrscheinlicher, dass Sie dasselbe Dokument zweimal hintereinander erhalten, als es der Zufall vorschreibt.
Lacker
12
Sieht nach einer schlechten Implementierung von Circular Hashing aus. Es ist noch schlimmer als der Mangel sagt: Selbst eine Suche ist voreingenommen, weil die Zufallszahlen nicht gleichmäßig verteilt sind. Um dies richtig zu machen, benötigen Sie beispielsweise 10 Zufallszahlen pro Dokument. Je mehr Zufallszahlen Sie pro Dokument verwenden, desto gleichmäßiger wird die Ausgabeverteilung.
Thomas
4
Das MongoDB JIRA-Ticket ist noch am Leben: jira.mongodb.org/browse/SERVER-533 Kommentieren Sie und stimmen Sie ab, wenn Sie die Funktion wünschen.
David J.
1
Beachten Sie die Art der genannten Einschränkung. Dies funktioniert bei kleinen Dokumentenmengen nicht effizient. Bei zwei Elementen mit dem Zufallsschlüssel 3 und 63. Das Dokument Nr. 63 wird häufiger ausgewählt, wenn $gtees das erste ist. Alternative Lösung stackoverflow.com/a/9499484/79201 würde in diesem Fall besser funktionieren.
Ryan Schumacher
56

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

Aktivieren Sie zunächst die Geodatenindizierung für eine 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 erhalten:

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

Oder Sie können mehrere Dokumente abrufen, die einem zufälligen 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. Außerdem ist der Code sauber, einfach und flexibel. Sie können sogar die Y-Achse des Geopunkts verwenden, um Ihrer Abfrage eine zweite Zufallsdimension hinzuzufügen.

Nico de Poel
quelle
8
Ich mag diese Antwort. Es ist die effizienteste, die ich je gesehen habe und die keine großen Probleme mit der Serverseite erfordert.
Tony Million
4
Dies ist auch voreingenommen gegenüber Dokumenten, die zufällig nur wenige Punkte in ihrer Nähe haben.
Thomas
6
Das stimmt, und es gibt auch andere Probleme: Dokumente sind stark mit ihren Zufallsschlüsseln korreliert, sodass es sehr vorhersehbar ist, welche Dokumente als Gruppe zurückgegeben werden, wenn Sie mehrere Dokumente auswählen. Es ist auch weniger wahrscheinlich, dass Dokumente in der Nähe der Grenzen (0 und 1) ausgewählt werden. Letzteres könnte durch sphärische Geomapping gelöst werden, die sich an den Rändern umhüllt. Sie sollten diese Antwort jedoch als verbesserte Version des Kochbuchrezepts betrachten, nicht als perfekten Mechanismus zur zufälligen Auswahl. Es ist für die meisten Zwecke zufällig genug.
Nico de Poel
@NicodePoel, ich mag deine Antwort sowie deinen Kommentar! Und ich habe ein paar Fragen an Sie: 1- Woher wissen Sie, dass Punkte in der Nähe der Grenzen 0 und 1 weniger wahrscheinlich ausgewählt werden, basiert dies auf mathematischen Grundlagen? 2- Können Sie mehr über sphärische Geomapping näher erläutern? Wie wird die zufällige Auswahl verbessert und wie wird sie in MongoDB ausgeführt? ... geschätzt!
Securecurve
Bereichern Sie Ihre Idee. Schließlich habe ich einen großartigen Code, der sehr CPU- und RAM-freundlich ist! Vielen Dank
Qais Bsharat
21

Das folgende Rezept ist etwas langsamer als die Mongo-Kochbuchlösung (fügen Sie jedem Dokument einen zufälligen Schlüssel hinzu), gibt jedoch gleichmäßigere zufällige Dokumente zurück. Es ist etwas weniger gleichmäßig verteilt als die skip( random )Lösung, aber viel schneller und ausfallsicherer, wenn 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 zufälliges "zufälliges" Feld hinzufügen. Vergessen Sie also nicht, dieses Feld beim Erstellen 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 generiert gleichmäßigere zufällige Dokumente als die von Michael berichtete "Kochbuch" -Methode:

Für eine Sammlung mit 1.000.000 Elementen:

  • Diese Methode dauert auf meinem Computer weniger als eine Millisekunde

  • Die skip()Methode dauert durchschnittlich 180 ms

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

  • Diese Methode wählt alle Elemente im Laufe der Zeit gleichmäßig aus.

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

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

Dieses Rezept ist nicht perfekt - die perfekte Lösung wäre eine integrierte Funktion, wie andere angemerkt haben.
Es sollte jedoch für viele Zwecke ein guter Kompromiss sein.

spam_eggs
quelle
10

Hier ist eine Möglichkeit, die Standardwerte ObjectIdfür _idund ein wenig Mathematik und Logik zu verwenden.

// Get the "min" and "max" timestamp values from the _id in the collection and the 
// diff between.
// 4-bytes from a hex string is 8 characters

var min = parseInt(db.collection.find()
        .sort({ "_id": 1 }).limit(1).toArray()[0]._id.str.substr(0,8),16)*1000,
    max = parseInt(db.collection.find()
        .sort({ "_id": -1 })limit(1).toArray()[0]._id.str.substr(0,8),16)*1000,
    diff = max - min;

// Get a random value from diff and divide/multiply be 1000 for The "_id" precision:
var random = Math.floor(Math.floor(Math.random(diff)*diff)/1000)*1000;

// Use "random" in the range and pad the hex string to a valid ObjectId
var _id = new ObjectId(((min + random)/1000).toString(16) + "0000000000000000")

// Then query for the single document:
var randomDoc = db.collection.find({ "_id": { "$gte": _id } })
   .sort({ "_id": 1 }).limit(1).toArray()[0];

Das ist die allgemeine Logik in der Shell-Darstellung und leicht anpassbar.

Also in Punkten:

  • Suchen Sie die minimalen und maximalen Primärschlüsselwerte in der Sammlung

  • Generieren Sie eine Zufallszahl, die zwischen den Zeitstempeln dieser Dokumente liegt.

  • Addieren Sie die Zufallszahl zum Mindestwert und suchen Sie das erste Dokument, das größer oder gleich diesem Wert ist.

Dies verwendet "Auffüllen" aus dem Zeitstempelwert in "hex", um einen gültigen ObjectIdWert zu bilden , da dies das ist, wonach wir suchen. Die Verwendung von Ganzzahlen als _idWert ist wesentlich einfacher, aber die gleiche Grundidee in den Punkten.

Blakes Seven
quelle
Ich habe eine Sammlung von 300 000 000 Zeilen. Dies ist die einzige Lösung, die funktioniert und schnell genug ist.
Nikos
8

In Python mit Pymongo:

import random

def get_random_doc():
    count = collection.count()
    return collection.find()[random.randrange(count)]
Jabba
quelle
5
Es ist erwähnenswert, dass dies intern wie bei vielen anderen Antworten das Überspringen und Begrenzen verwendet.
JohnnyHK
Ihre Antwort ist richtig. Allerdings ersetzen Sie bitte count()mit , estimated_document_count()wie count()in Mongdo v4.2 ist veraltet.
user3848207
8

Jetzt können Sie das Aggregat verwenden. Beispiel:

db.users.aggregate(
   [ { $sample: { size: 3 } } ]
)

Siehe das Dokument .

dbam
quelle
3
Hinweis: $ sample kann dasselbe Dokument mehr als einmal erhalten
Saman Shafigh
6

Es ist schwierig, wenn dort keine Daten zum Abschlüsseln vorhanden sind. Was sind die _id Felder? Sind sie Mongodb-Objekt-IDs? Wenn ja, könnten Sie die höchsten und niedrigsten Werte erhalten:

lowest = db.coll.find().sort({_id:1}).limit(1).next()._id;
highest = db.coll.find().sort({_id:-1}).limit(1).next()._id;

Wenn Sie dann annehmen, dass die IDs gleichmäßig verteilt sind (aber nicht, aber zumindest ist es ein Anfang):

unsigned long long L = first_8_bytes_of(lowest)
unsigned long long H = first_8_bytes_of(highest)

V = (H - L) * random_from_0_to_1();
N = L + V;
oid = N concat random_4_bytes();

randomobj = db.coll.find({_id:{$gte:oid}}).limit(1);
dm.
quelle
1
Irgendwelche Ideen, wie das in PHP aussehen würde? oder zumindest welche sprache hast du oben benutzt? ist es Python?
Marcin
6

Mit Python (Pymongo) funktioniert auch die Aggregatfunktion.

collection.aggregate([{'$sample': {'size': sample_size }}])

Dieser Ansatz ist viel schneller als das Ausführen einer Abfrage für eine Zufallszahl (z. B. collection.find ([random_int]). Dies gilt insbesondere für große Sammlungen.

Daniel
quelle
5

Sie können einen zufälligen Zeitstempel auswählen und nach dem ersten Objekt suchen, das anschließend erstellt wurde. Es wird nur ein einzelnes Dokument gescannt, obwohl dies nicht unbedingt eine gleichmäßige Verteilung ergibt.

var randRec = function() {
    // replace with your collection
    var coll = db.collection
    // get unixtime of first and last record
    var min = coll.find().sort({_id: 1}).limit(1)[0]._id.getTimestamp() - 0;
    var max = coll.find().sort({_id: -1}).limit(1)[0]._id.getTimestamp() - 0;

    // allow to pass additional query params
    return function(query) {
        if (typeof query === 'undefined') query = {}
        var randTime = Math.round(Math.random() * (max - min)) + min;
        var hexSeconds = Math.floor(randTime / 1000).toString(16);
        var id = ObjectId(hexSeconds + "0000000000000000");
        query._id = {$gte: id}
        return coll.find(query).limit(1)
    };
}();
Martin Nowak
quelle
Es wäre leicht möglich, das zufällige Datum zu verzerren, um das superlineare Datenbankwachstum zu berücksichtigen.
Martin Nowak
Dies ist die beste Methode für sehr große Sammlungen. Sie funktioniert bei O (1), Unline Skip () oder Count (), die in den anderen Lösungen hier verwendet werden
Marmor
4

Meine Lösung auf PHP:

/**
 * Get random docs from Mongo
 * @param $collection
 * @param $where
 * @param $fields
 * @param $limit
 * @author happy-code
 * @url happy-code.com
 */
private function _mongodb_get_random (MongoCollection $collection, $where = array(), $fields = array(), $limit = false) {

    // Total docs
    $count = $collection->find($where, $fields)->count();

    if (!$limit) {
        // Get all docs
        $limit = $count;
    }

    $data = array();
    for( $i = 0; $i < $limit; $i++ ) {

        // Skip documents
        $skip = rand(0, ($count-1) );
        if ($skip !== 0) {
            $doc = $collection->find($where, $fields)->skip($skip)->limit(1)->getNext();
        } else {
            $doc = $collection->find($where, $fields)->limit(1)->getNext();
        }

        if (is_array($doc)) {
            // Catch document
            $data[ $doc['_id']->{'$id'} ] = $doc;
            // Ignore current document when making the next iteration
            $where['_id']['$nin'][] = $doc['_id'];
        }

        // Every iteration catch document and decrease in the total number of document
        $count--;

    }

    return $data;
}
code_turist
quelle
3

Um eine bestimmte Anzahl zufälliger Dokumente ohne Duplikate zu erhalten:

  1. Holen Sie sich zuerst alle IDs
  2. Größe der Dokumente abrufen
  3. Schleife, die zufälligen Index erhält und dupliziert überspringt

    number_of_docs=7
    db.collection('preguntas').find({},{_id:1}).toArray(function(err, arr) {
    count=arr.length
    idsram=[]
    rans=[]
    while(number_of_docs!=0){
        var R = Math.floor(Math.random() * count);
        if (rans.indexOf(R) > -1) {
         continue
          } else {           
                   ans.push(R)
                   idsram.push(arr[R]._id)
                   number_of_docs--
                    }
        }
    db.collection('preguntas').find({}).toArray(function(err1, doc1) {
                    if (err1) { console.log(err1); return;  }
                   res.send(doc1)
                });
            });
Fabio Guerra
quelle
2

Ich würde vorschlagen, map / redu zu verwenden, wobei Sie die Map-Funktion verwenden, um nur zu emittieren, wenn ein zufälliger Wert über einer bestimmten Wahrscheinlichkeit liegt.

function mapf() {
    if(Math.random() <= probability) {
    emit(1, this);
    }
}

function reducef(key,values) {
    return {"documents": values};
}

res = db.questions.mapReduce(mapf, reducef, {"out": {"inline": 1}, "scope": { "probability": 0.5}});
printjson(res.results);

Die obige Reduktionsfunktion funktioniert, da nur eine Taste ('1') von der Kartenfunktion ausgegeben wird.

Der Wert der "Wahrscheinlichkeit" wird beim Aufrufen von mapRreduce (...) im "Bereich" definiert.

Die Verwendung von mapReduce wie diesem sollte auch für eine Sharded-Datenbank verwendbar sein.

Wenn Sie genau n von m Dokumenten aus der Datenbank auswählen möchten, können Sie dies folgendermaßen tun:

function mapf() {
    if(countSubset == 0) return;
    var prob = countSubset / countTotal;
    if(Math.random() <= prob) {
        emit(1, {"documents": [this]}); 
        countSubset--;
    }
    countTotal--;
}

function reducef(key,values) {
    var newArray = new Array();
for(var i=0; i < values.length; i++) {
    newArray = newArray.concat(values[i].documents);
}

return {"documents": newArray};
}

res = db.questions.mapReduce(mapf, reducef, {"out": {"inline": 1}, "scope": {"countTotal": 4, "countSubset": 2}})
printjson(res.results);

Dabei ist "countTotal" (m) die Anzahl der Dokumente in der Datenbank und "countSubset" (n) die Anzahl der abzurufenden Dokumente.

Dieser Ansatz kann bei Sharded-Datenbanken zu Problemen führen.

torbenl
quelle
4
Durchführen eines vollständigen Sammlungsscans, um 1 Element zurückzugeben ... dies muss die am wenigsten effiziente Technik sein, um dies zu tun.
Thomas
1
Der Trick ist, dass es sich um eine allgemeine Lösung für die Rückgabe einer beliebigen Anzahl zufälliger Elemente handelt. In diesem Fall wäre sie schneller als die anderen Lösungen, wenn> 2 zufällige Elemente abgerufen werden.
Torbenl
2

Sie können zufällige _id auswählen und das entsprechende Objekt zurückgeben:

 db.collection.count( function(err, count){
        db.collection.distinct( "_id" , function( err, result) {
            if (err)
                res.send(err)
            var randomId = result[Math.floor(Math.random() * (count-1))]
            db.collection.findOne( { _id: randomId } , function( err, result) {
                if (err)
                    res.send(err)
                console.log(result)
            })
        })
    })

Hier müssen Sie keinen Platz für das Speichern von Zufallszahlen in der Sammlung aufwenden.

Vijay13
quelle
1

Ich würde vorschlagen, jedem Objekt ein zufälliges int-Feld hinzuzufügen. Dann kannst du einfach eine machen

findOne({random_field: {$gte: rand()}}) 

ein zufälliges Dokument auswählen. Stellen Sie einfach sicher, dass Sie den Index sicherstellen ({random_field: 1}).

mstearn
quelle
2
Wenn der erste Datensatz in Ihrer Sammlung einen relativ hohen random_field-Wert hat, wird er dann nicht fast immer zurückgegeben?
Thehiatus
2
Thehaitus ist richtig, es wird - es ist für keinen Zweck geeignet
Heptic
7
Diese Lösung ist völlig falsch. Das Hinzufügen einer Zufallszahl (stellen wir uns zwischen 0 und 2 ^ 32-1 vor) garantiert keine gute Verteilung, und die Verwendung von $ gte macht sie noch schlimmer, da Ihre zufällige Auswahl nicht einmal nah ist zu einer Pseudozufallszahl. Ich schlage vor, dieses Konzept niemals zu verwenden.
Maximiliano Rios
1

Als ich mit einer ähnlichen Lösung konfrontiert wurde, ging ich zurück und stellte fest, dass die Geschäftsanforderung tatsächlich dazu diente, eine Art Rotation des präsentierten Inventars zu erstellen. In diesem Fall gibt es viel bessere Optionen, die Antworten von Suchmaschinen wie Solr haben, nicht von Datenspeichern wie MongoDB.

Kurz gesagt, mit der Anforderung, Inhalte "intelligent zu drehen", sollten wir anstelle einer Zufallszahl in allen Dokumenten einen persönlichen q-Score-Modifikator einfügen. Um dies selbst zu implementieren, können Sie unter der Annahme einer kleinen Anzahl von Benutzern ein Dokument pro Benutzer speichern, das die Produkt-ID, die Anzahl der Impressionen, die Anzahl der Klicks, das Datum der letzten Anzeige und alle anderen Faktoren enthält, die das Unternehmen für die Berechnung der aq-Bewertung als sinnvoll erachtet Modifikator. Wenn Sie den anzuzeigenden Satz abrufen, fordern Sie normalerweise mehr Dokumente aus dem Datenspeicher an, als vom Endbenutzer angefordert wurden. Wenden Sie dann den Modifikator q score an, nehmen Sie die Anzahl der vom Endbenutzer angeforderten Datensätze und ordnen Sie die Ergebnisseite nach dem Zufallsprinzip zu So sortieren Sie einfach die Dokumente in der Anwendungsschicht (im Speicher).

Wenn das Benutzeruniversum zu groß ist, können Sie Benutzer in Verhaltensgruppen einteilen und nach Verhaltensgruppen und nicht nach Benutzern indizieren.

Wenn das Produktuniversum klein genug ist, können Sie pro Benutzer einen Index erstellen.

Ich habe festgestellt, dass diese Technik viel effizienter, aber vor allem effektiver ist, um eine relevante und lohnende Erfahrung mit der Verwendung der Softwarelösung zu schaffen.

paegun
quelle
1

Keine der Lösungen hat bei mir gut funktioniert. Besonders wenn es viele Lücken gibt und die Menge klein ist. das hat bei mir sehr gut funktioniert (in php):

$count = $collection->count($search);
$skip = mt_rand(0, $count - 1);
$result = $collection->find($search)->skip($skip)->limit(1)->getNext();
Mantas Karanauskas
quelle
Sie geben die Sprache an, aber nicht die Bibliothek, die Sie verwenden?
Benjamin
Zu Ihrer Information, hier gibt es eine Rennbedingung, wenn ein Dokument zwischen der ersten und dritten Zeile entfernt wird. Auch find+ skipist ziemlich schlecht, Sie geben alle Dokumente zurück, nur um eines auszuwählen: S.
Martin Konecny
1

Meine PHP / MongoDB-Sortierung / Bestellung nach RANDOM-Lösung. Hoffe das hilft jedem.

Hinweis: Ich habe numerische IDs in meiner MongoDB-Sammlung, die auf einen MySQL-Datenbankdatensatz verweisen.

Zuerst erstelle ich ein Array mit 10 zufällig generierten Zahlen

    $randomNumbers = [];
    for($i = 0; $i < 10; $i++){
        $randomNumbers[] = rand(0,1000);
    }

In meiner Aggregation verwende ich den Pipeline-Operator $ addField in Kombination mit $ arrayElemAt und $ mod (Modul). Der Moduloperator gibt mir eine Zahl von 0 bis 9, mit der ich dann eine Zahl aus dem Array mit zufällig generierten Zahlen auswähle.

    $aggregate[] = [
        '$addFields' => [
            'random_sort' => [ '$arrayElemAt' => [ $randomNumbers, [ '$mod' => [ '$my_numeric_mysql_id', 10 ] ] ] ],
        ],
    ];

Danach können Sie die Sortierpipeline verwenden.

    $aggregate[] = [
        '$sort' => [
            'random_sort' => 1
        ]
    ];
feskr
quelle
0

Wenn Sie einen einfachen ID-Schlüssel haben, können Sie alle IDs in einem Array speichern und dann eine zufällige ID auswählen. (Ruby Antwort):

ids = @coll.find({},fields:{_id:1}).to_a
@coll.find(ids.sample).first
Herr Demetrius Michael
quelle
0

Mit Map / Reduce können Sie sicherlich einen zufälligen Datensatz erhalten, der jedoch nicht unbedingt sehr effizient ist, abhängig von der Größe der resultierenden gefilterten Sammlung, mit der Sie am Ende arbeiten.

Ich habe diese Methode mit 50.000 Dokumenten getestet (der Filter reduziert sie auf ungefähr 30.000) und sie wird in ungefähr 400 ms auf einem Intel i3 mit 16 GB RAM und einer SATA3-Festplatte ausgeführt ...

db.toc_content.mapReduce(
    /* map function */
    function() { emit( 1, this._id ); },

    /* reduce function */
    function(k,v) {
        var r = Math.floor((Math.random()*v.length));
        return v[r];
    },

    /* options */
    {
        out: { inline: 1 },
        /* Filter the collection to "A"ctive documents */
        query: { status: "A" }
    }
);

Die Map-Funktion erstellt einfach ein Array der IDs aller Dokumente, die der Abfrage entsprechen. In meinem Fall habe ich dies mit ungefähr 30.000 der 50.000 möglichen Dokumente getestet.

Die Reduce-Funktion wählt einfach eine zufällige Ganzzahl zwischen 0 und der Anzahl der Elemente (-1) im Array aus und gibt dann diese _id aus dem Array zurück.

400 ms klingen nach einer langen Zeit, und wenn Sie fünfzig statt fünfzigtausend Datensätze hatten, kann dies den Overhead so weit erhöhen, dass er in Mehrbenutzersituationen unbrauchbar wird.

Es gibt ein offenes Problem für MongoDB, diese Funktion in den Kern aufzunehmen ... https://jira.mongodb.org/browse/SERVER-533

Wenn diese "zufällige" Auswahl in eine Indexsuche integriert würde, anstatt IDs in einem Array zu sammeln und dann eine auszuwählen, würde dies unglaublich helfen. (Geh und stimme ab!)

Doppelhelix
quelle
0

Dies funktioniert gut, ist schnell, funktioniert mit mehreren Dokumenten und erfordert kein Ausfüllen von randFeldern, die sich schließlich selbst ausfüllen:

  1. Fügen Sie dem Feld .rand Ihrer Sammlung einen Index hinzu
  2. Verwenden Sie Suchen und Aktualisieren, so etwas wie:
// Install packages:
//   npm install mongodb async
// Add index in mongo:
//   db.ensureIndex('mycollection', { rand: 1 })

var mongodb = require('mongodb')
var async = require('async')

// Find n random documents by using "rand" field.
function findAndRefreshRand (collection, n, fields, done) {
  var result = []
  var rand = Math.random()

  // Append documents to the result based on criteria and options, if options.limit is 0 skip the call.
  var appender = function (criteria, options, done) {
    return function (done) {
      if (options.limit > 0) {
        collection.find(criteria, fields, options).toArray(
          function (err, docs) {
            if (!err && Array.isArray(docs)) {
              Array.prototype.push.apply(result, docs)
            }
            done(err)
          }
        )
      } else {
        async.nextTick(done)
      }
    }
  }

  async.series([

    // Fetch docs with unitialized .rand.
    // NOTE: You can comment out this step if all docs have initialized .rand = Math.random()
    appender({ rand: { $exists: false } }, { limit: n - result.length }),

    // Fetch on one side of random number.
    appender({ rand: { $gte: rand } }, { sort: { rand: 1 }, limit: n - result.length }),

    // Continue fetch on the other side.
    appender({ rand: { $lt: rand } }, { sort: { rand: -1 }, limit: n - result.length }),

    // Refresh fetched docs, if any.
    function (done) {
      if (result.length > 0) {
        var batch = collection.initializeUnorderedBulkOp({ w: 0 })
        for (var i = 0; i < result.length; ++i) {
          batch.find({ _id: result[i]._id }).updateOne({ rand: Math.random() })
        }
        batch.execute(done)
      } else {
        async.nextTick(done)
      }
    }

  ], function (err) {
    done(err, result)
  })
}

// Example usage
mongodb.MongoClient.connect('mongodb://localhost:27017/core-development', function (err, db) {
  if (!err) {
    findAndRefreshRand(db.collection('profiles'), 1024, { _id: true, rand: true }, function (err, result) {
      if (!err) {
        console.log(result)
      } else {
        console.error(err)
      }
      db.close()
    })
  } else {
    console.error(err)
  }
})

ps. Wie man zufällige Datensätze in der Mongodb- Frage findet, wird als Duplikat dieser Frage markiert. Der Unterschied besteht darin , dass diese Frage ausdrücklich zu einzelnen Datensatz wie der andere fragt explizit über zufälliges Dokument bekommen s .

Mirek Rusin
quelle
-2

Wenn Sie mongoid, den Dokument-zu-Objekt-Wrapper, verwenden, können Sie in Ruby Folgendes tun. (Angenommen, Ihr Modell ist Benutzer)

User.all.to_a[rand(User.count)]

In meinem .irbrc habe ich

def rando klass
    klass.all.to_a[rand(klass.count)]
end

So kann ich in der Rails-Konsole zum Beispiel Folgendes tun:

rando User
rando Article

um Dokumente zufällig aus einer Sammlung zu erhalten.

Zack Xu
quelle
1
Dies ist furchtbar ineffizient, da die gesamte Sammlung in ein Array eingelesen und dann ein Datensatz ausgewählt wird.
JohnnyHK
Ok, vielleicht ineffizient, aber sicherlich praktisch. Versuchen Sie dies, wenn Ihre Datengröße nicht zu groß ist
Zack Xu
3
Sicher, aber die ursprüngliche Frage war für eine Sammlung mit 100 Millionen Dokumenten, also wäre dies eine sehr schlechte Lösung für diesen Fall!
JohnnyHK
-2

Sie können Shuffle-Array auch verwenden, nachdem Sie Ihre Abfrage ausgeführt haben

var shuffle = require ('shuffle-array');

Accounts.find (qry, function (err, results_array) {newIndexArr = shuffle (results_array);

Rabie Jegham
quelle
-7

Was effizient und zuverlässig funktioniert, ist Folgendes:

Fügen Sie jedem Dokument ein Feld mit dem Namen "zufällig" hinzu und weisen Sie ihm einen zufälligen Wert zu. Fügen Sie einen Index für das zufällige Feld hinzu und gehen Sie wie folgt vor:

Nehmen wir an, wir haben eine Sammlung von Weblinks, die als "Links" bezeichnet werden, und wir möchten einen zufälligen Link daraus:

link = db.links.find().sort({random: 1}).limit(1)[0]

Aktualisieren Sie das Zufallsfeld mit einer neuen Zufallszahl, um sicherzustellen, dass derselbe Link nicht ein zweites Mal angezeigt wird:

db.links.update({random: Math.random()}, link)
Zugwrack
quelle
2
Warum die Datenbank aktualisieren, wenn Sie nur einen anderen Zufallsschlüssel auswählen können?
Jason S
Möglicherweise haben Sie keine Liste der Schlüssel, aus denen Sie zufällig auswählen können.
Mike
Sie müssen also jedes Mal die gesamte Sammlung sortieren? Und was ist mit den unglücklichen Aufzeichnungen, die große Zufallszahlen haben? Sie werden niemals ausgewählt.
Fantius
1
Sie müssen dies tun, da die anderen Lösungen, insbesondere die im MongoDB-Buch vorgeschlagene, nicht funktionieren. Wenn die erste Suche fehlschlägt, gibt die zweite Suche immer das Element mit dem kleinsten Zufallswert zurück. Wenn Sie zufällig absteigend indizieren, gibt die erste Abfrage immer das Element mit der größten Zufallszahl zurück.
Zugunglück
In jedem Dokument ein Feld hinzufügen? Ich denke es ist nicht ratsam.
CS_noob