MongoDB
 sql >> Base de données >  >> NoSQL >> MongoDB

Condition limite multiple dans mongodb

Généralement, ce que vous décrivez est une question relativement courante dans la communauté MongoDB que nous pourrions décrire comme le "top n problème de résultats". C'est lorsqu'on leur donne une entrée qui est probablement triée d'une manière ou d'une autre, comment obtenir le top n résultats sans s'appuyer sur des valeurs d'index arbitraires dans les données.

MongoDB a le $first opérateur disponible pour le framework d'agrégation qui traite de la partie "top 1" du problème, car cela prend en fait le "premier" élément trouvé sur une limite de regroupement, comme votre "type". Mais obtenir plus d'"un" résultat est bien sûr un peu plus compliqué. Il y a quelques problèmes JIRA à ce sujet concernant la modification d'autres opérateurs pour traiter n résultats ou "restreindre" ou "trancher". Notamment SERVER-6074 . Mais le problème peut être traité de plusieurs manières.

Les implémentations populaires du modèle Rails Active Record pour le stockage MongoDB sont Mongoid et Mongo Mapper , les deux permettent l'accès aux fonctions de collection mongodb "natives" via un .collection accesseur. C'est ce dont vous avez essentiellement besoin pour pouvoir utiliser des méthodes natives telles que .aggregate() qui prend en charge plus de fonctionnalités que l'agrégation générale des enregistrements actifs.

Voici une approche d'agrégation avec mongoid, bien que le code général ne change pas une fois que vous avez accès à l'objet de collection natif :

require "mongoid"
require "pp";

Mongoid.configure.connect_to("test");

class Item
  include Mongoid::Document
  store_in collection: "item"

  field :type, type: String
  field :pos, type: String
end

Item.collection.drop

Item.collection.insert( :type => "A", :pos => "First" )
Item.collection.insert( :type => "A", :pos => "Second"  )
Item.collection.insert( :type => "A", :pos => "Third" )
Item.collection.insert( :type => "A", :pos => "Forth" )
Item.collection.insert( :type => "B", :pos => "First" )
Item.collection.insert( :type => "B", :pos => "Second" )
Item.collection.insert( :type => "B", :pos => "Third" )
Item.collection.insert( :type => "B", :pos => "Forth" )

res = Item.collection.aggregate([
  { "$group" => {
      "_id" => "$type",
      "docs" => {
        "$push" => {
          "pos" => "$pos", "type" => "$type"
        }
      },
      "one" => {
        "$first" => {
          "pos" => "$pos", "type" => "$type"
        }
      }
  }},
  { "$unwind" =>  "$docs" },
  { "$project" => {
    "docs" => {
      "pos" => "$docs.pos",
      "type" => "$docs.type",
      "seen" => {
        "$eq" => [ "$one", "$docs" ]
      },
    },
    "one" => 1
  }},
  { "$match" => {
    "docs.seen" => false
  }},
  { "$group" => {
    "_id" => "$_id",
    "one" => { "$first" => "$one" },
    "two" => {
      "$first" => {
        "pos" => "$docs.pos",
        "type" => "$docs.type"
      }
    },
    "splitter" => {
      "$first" => {
        "$literal" => ["one","two"]
      }
    }
  }},
  { "$unwind" => "$splitter" },
  { "$project" => {
    "_id" => 0,
    "type" => {
      "$cond" => [
        { "$eq" => [ "$splitter", "one" ] },
        "$one.type",
        "$two.type"
      ]
    },
    "pos" => {
      "$cond" => [
        { "$eq" => [ "$splitter", "one" ] },
        "$one.pos",
        "$two.pos"
      ]
    }
  }}
])

pp res

La dénomination dans les documents n'est en fait pas utilisée par le code, et les titres dans les données affichées pour "Premier", "Second", etc., sont vraiment juste là pour illustrer que vous obtenez en effet les "2 meilleurs" documents de la liste comme un résultat.

L'approche ici consiste donc essentiellement à créer une "pile" des documents "regroupés" par votre clé, telle que "type". La toute première chose ici est de prendre le "premier" document de cette pile en utilisant le $first opérateur.

Les étapes suivantes correspondent aux éléments "vus" de la pile et les filtrent, puis vous retirez à nouveau le document "suivant" de la pile en utilisant le $first opérateur. Les étapes finales sont vraiment justes pour remettre les documents dans le formulaire d'origine tel qu'il se trouve dans l'entrée, ce qui est généralement ce qui est attendu d'une telle requête.

Donc le résultat est bien sûr, juste les 2 premiers documents pour chaque type :

{ "type"=>"A", "pos"=>"First" }
{ "type"=>"A", "pos"=>"Second" }
{ "type"=>"B", "pos"=>"First" }
{ "type"=>"B", "pos"=>"Second" }

Il y avait une discussion plus longue et une version de ceci ainsi que d'autres solutions dans cette réponse récente :

Agrégation Mongodb $group, restreindre la longueur du tableau

Essentiellement la même chose malgré le titre et cette affaire cherchait à faire correspondre jusqu'à 10 meilleures entrées ou plus. Il existe également un code de génération de pipeline pour traiter les correspondances plus importantes, ainsi que des approches alternatives qui peuvent être envisagées en fonction de vos données.