MongoDB
 sql >> Database >  >> NoSQL >> MongoDB

Condizione limite multipla in mongodb

In genere ciò che stai descrivendo è una domanda relativamente comune nella comunità di MongoDB che potremmo descrivere come la "top n problema con i risultati". Questo è quando viene fornito un input che è probabilmente ordinato in qualche modo, come ottenere il primo n risultati senza fare affidamento su valori di indice arbitrari nei dati.

MongoDB ha il $first operatore disponibile per il framework di aggregazione che si occupa della parte "top 1" del problema, poiché in realtà prende il "primo" elemento trovato su un limite di raggruppamento, come il tuo "tipo". Ma ottenere più di "un" risultato ovviamente diventa un po' più complicato. Ci sono alcuni problemi JIRA su questo riguardo alla modifica di altri operatori per gestire n risultati o "restrict" o "slice". In particolare SERVER-6074 . Ma il problema può essere gestito in diversi modi.

Le implementazioni popolari del modello Rails Active Record per l'archiviazione MongoDB sono Mongoid e Mongo Mapper , entrambi consentono l'accesso alle funzioni di raccolta mongodb "native" tramite un .collection accessorio. Questo è ciò di cui hai praticamente bisogno per essere in grado di utilizzare metodi nativi come .aggregato() che supporta più funzionalità rispetto all'aggregazione generale di Active Record.

Ecco un approccio di aggregazione con mongoid, anche se il codice generale non cambia una volta che hai accesso all'oggetto raccolta nativo:

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 denominazione nei documenti in realtà non è utilizzata dal codice e i titoli nei dati mostrati per "Primo", "Secondo" ecc., sono davvero lì solo per illustrare che stai effettivamente ottenendo i "primi 2" documenti dall'elenco come un risultato.

Quindi l'approccio qui è essenzialmente quello di creare una "pila" di documenti "raggruppati" dalla tua chiave, come "tipo". La prima cosa qui è prendere il "primo" documento da quello stack usando $first operatore.

I passaggi successivi corrispondono agli elementi "visti" dallo stack e li filtrano, quindi si rimuove nuovamente il documento "successivo" dallo stack utilizzando $first operatore. I passaggi finali sono davvero solo x restituire i documenti alla forma originale trovata nell'input, che è generalmente ciò che ci si aspetta da una tale query.

Quindi il risultato sono ovviamente solo i primi 2 documenti per ogni tipo:

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

C'è stata una discussione e una versione più lunghe di questo, così come altre soluzioni in questa risposta recente:

Aggregazione Mongodb $group, limita la lunghezza dell'array

Essenzialmente la stessa cosa nonostante il titolo e quel caso cercassero di abbinare fino a 10 voci principali o più. C'è anche del codice di generazione della pipeline per gestire corrispondenze più grandi, nonché alcuni approcci alternativi che possono essere presi in considerazione a seconda dei tuoi dati.