Dizine alınmış bir sütunda MongoDB seçme sayısı (farklı x) - büyük veri kümeleri için benzersiz sonuçları sayın


82

Birkaç makale ve örneği inceledim ve bu SQL sorgusunu MongoDB'de yapmanın etkili bir yolunu henüz bulamadım (milyonlarca satırlar belgeler)

İlk girişim

(örneğin bu neredeyse yinelenen sorudan - SQL'deki SELECT DISTINCT'in Mongo eşdeğeri? )

db.myCollection.distinct("myIndexedNonUniqueField").length

Veri kümem çok büyük olduğu için açıkçası bu hatayı aldım

Thu Aug 02 12:55:24 uncaught exception: distinct failed: {
        "errmsg" : "exception: distinct too big, 16mb cap",
        "code" : 10044,
        "ok" : 0
}

İkinci deneme

Bir grup denemeye karar verdim

db.myCollection.group({key: {myIndexedNonUniqueField: 1},
                initial: {count: 0}, 
                 reduce: function (obj, prev) { prev.count++;} } );

Ama bunun yerine şu hata mesajını aldım:

exception: group() can't handle more than 20000 unique keys

Üçüncü deneme

Henüz denemedim, ancak içeren birkaç öneri var mapReduce

Örneğin

Ayrıca

Görünüşe göre GitHub'da .distinct, yalnızca bir sayı döndürmesi gerektiğinden bahsetmek için yöntemi düzelten bir çekme isteği var , ancak hala açık: https://github.com/mongodb/mongo/pull/34

Ama bu noktada burada sormaya değer olduğunu düşündüm, konuyla ilgili son gelişmeler neler? Farklı sayılar için SQL veya başka bir NoSQL DB'ye geçmeli miyim? yoksa verimli bir yol var mı?

Güncelleme:

MongoDB resmi dokümanları hakkındaki bu yorum cesaret verici değil, bu doğru mu?

http://www.mongodb.org/display/DOCS/Aggregation#comment-430445808

Güncelleme2:

Görünüşe göre yeni Toplama Çerçevesi yukarıdaki yorumu yanıtlıyor ... (MongoDB 2.1 / 2.2 ve üstü, geliştirme önizlemesi mevcut, üretim için değil)

http://docs.mongodb.org/manual/applications/aggregation/


Bunu sık sık yapmanız gerektiğini varsayıyorum, yoksa performans o kadar önemli olmaz. Bu durumda, farklı değerleri, bu kadar büyük bir koleksiyonda farklı bir şey yapmaya çalışmak yerine, yeni bir belge eklediğinizde güncellenen ayrı bir koleksiyonda saklarım. Ya öyle ya da MongoDb kullanımımı yeniden değerlendirir ve muhtemelen başka bir şeye geçerim. Bulduğunuz gibi, MongoDb şu anda yapmaya çalıştığınız şeyde iyi değil.
Tim Gautier

@TimGautier teşekkürler, korkmuştum, tüm bu değerleri eklemek saatler sürdü ve bunu daha önce düşünmeliydim :) Sanırım şimdi bu istatistikler için MySQL'e eklemek için zaman harcayacağım ...
Eran Medan

Ayrıca, toplu verilerin delta indekslemesini temelde taklit eden artımlı bir MR da yapabilirsiniz. Demek istediğim, ne kullandığınızla ilgili sonuçlara ne zaman ihtiyaç duyduğunuza bağlı. MySQL'in büyük olasılıkla çok fazla IO elde edeceğini ve bunu yapmadan ne yapacağını hayal edebiliyorum (bir dizinde yalnızca 100k dokümanı satır içi olarak ayırarak küçük bir sunucuyu öldürebilirim) ama yine de bu tür şeyleri sorgulamada daha esnek olduğunu düşünüyorum. .
Sammaye

Mongo'nun bu tür şeylerde iyi olmadığına katılmıyorum. Mongo'nun üstün olduğu şey bu türse.
süper parlak

1
Maalesef moderatör, yinelenen soruya göndermiş olduğum cevabımı sildi. Orada silemiyorum ve burada yeniden yayınlayamıyorum, bu yüzden bağlantı: stackoverflow.com/a/33418582/226895
uzman

Yanıtlar:


75

1) Bunu yapmanın en kolay yolu, toplama çerçevesi kullanmaktır. Bu, iki "$ group" komutunu alır: birincisi farklı değerlere göre gruplandırılır, ikincisi farklı tüm değerleri sayar

pipeline = [ 
    { $group: { _id: "$myIndexedNonUniqueField"}  },
    { $group: { _id: 1, count: { $sum: 1 } } }
];

//
// Run the aggregation command
//
R = db.runCommand( 
    {
    "aggregate": "myCollection" , 
    "pipeline": pipeline
    }
);
printjson(R);

2) Bunu Harita / Küçült ile yapmak isterseniz yapabilirsiniz. Bu aynı zamanda iki aşamalı bir süreçtir: ilk aşamada, anahtar için her farklı değerin bir listesiyle yeni bir koleksiyon oluşturuyoruz. İkinci olarak, yeni koleksiyona bir sayarız ().

var SOURCE = db.myCollection;
var DEST = db.distinct
DEST.drop();


map = function() {
  emit( this.myIndexedNonUniqueField , {count: 1});
}

reduce = function(key, values) {
  var count = 0;

  values.forEach(function(v) {
    count += v['count'];        // count each distinct value for lagniappe
  });

  return {count: count};
};

//
// run map/reduce
//
res = SOURCE.mapReduce( map, reduce, 
    { out: 'distinct', 
     verbose: true
    }
    );

print( "distinct count= " + res.counts.output );
print( "distinct count=", DEST.count() );

Haritanın sonucunu döndüremeyeceğinizi / satır içi küçültemeyeceğinizi unutmayın, çünkü bu 16MB belge boyutu sınırını aşabilir. Sen edebilir bir koleksiyonundaki hesaplama kaydetmek ve daha sonra (sayım) koleksiyonun boyutunu veya mapreduce dönüş değerinden sonuç sayısını elde edebilirsiniz ().


5
Mongo 2.2 RC0'ı indirdim ve 1. önerinizi kullandım ve işe yarıyor! ve hızlı! teşekkür ederim (aferin 10gen ...) Burada bir özet oluşturdu (kısayol toplama komutunu kullandı ve bir satıra koydu) gist.github.com/3241616
Eran Medan

@EranMedan Yine de sizi uyarmalıyım, toplama çerçevesini önermedim çünkü 2.2 rc0 hala tam dağıtım için gerçekten hazır değil, sadece akılda tutulması gereken bir şey, toplamanın dağıtımını önermeden önce 2.2'nin tam sürümüne kadar beklerdim çerçeve.
Sammaye

@Sammaye evet, teşekkürler, bunun farkındayım, henüz üretime geçmeyecek, buna dahili istatistikler için ihtiyacım vardı ve mümkünse verileri SQL'e taşımaktan kaçınmak istedim (ve merakımı
giderin

Mongo neden kabul etmiyor: this.plugins.X-Powered-By.string? Bundan nasıl kaçabilirim?
EarlyPoster

Bu cevabın parçalanmış bir ortam için güvenilir olup olmadığını merak ediyorum. Anladığım kadarıyla, kırıkların her biri kendi toplanmasını yapacak ve ardından sonuçların daha sonra toplanacağı sonucu döndürecektir. Öyleyse bu senaryoda, $groupmongolara geri gönderilmeden önce ikinci ifadede farklı değerler kaybedildiğinden, yinelenenlerin var olma fırsatımız olmaz mıydı ?
Verran

37
db.myCollection.aggregate( 
   {$group : {_id : "$myIndexedNonUniqueField"} }, 
   {$group: {_id:1, count: {$sum : 1 }}});

doğrudan sonuca:

db.myCollection.aggregate( 
   {$group : {_id : "$myIndexedNonUniqueField"} }, 
   {$group: {_id:1, count: {$sum : 1 }}})
   .result[0].count;

1
Doğru, bu daha iyi. Ama bu William'ın verdiği cevapla aynı değil mi?
JohnnyHK

2
Benzer, ancak tek satırda olması hoşuma gidiyor. Yine de bir hata aldım: "Tanımsız '0' özelliği okunamıyor" Son satırı kaldırın ve güzel çalışıyor.
Nico

ve gerçekten devasa bir veritabanı hakkında konuşursak, unutma {allowDiskUse: true} yani, db.myCollection.aggregate ([{$ group ..}, {$ group:}], {allowDiskUse: true}) sonuç [ 0] .count;
hi_artem

3

Aşağıdaki çözüm benim için çalıştı

db.test.distinct ('kullanıcı'); ["alax", "İngiltere", "Fransa", "Avustralya"]

db.countries.distinct ('ülke'). uzunluk 4

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.