sql >> Base de Datos >  >> NoSQL >> MongoDB

Condición de límite múltiple en mongodb

En general, lo que está describiendo es una pregunta relativamente común en la comunidad de MongoDB que podríamos describir como el "principal n problema de resultados ". Esto es cuando se le da alguna entrada que probablemente esté ordenada de alguna manera, cómo obtener el n superior resultados sin depender de valores de índice arbitrarios en los datos.

MongoDB tiene el $first que está disponible para el marco de agregación que se ocupa de la parte "top 1" del problema, ya que en realidad toma el "primer" elemento que se encuentra en un límite de agrupación, como su "tipo". Pero obtener más de "un" resultado, por supuesto, involucra un poco más. Hay algunos problemas de JIRA sobre la modificación de otros operadores para tratar con n resultados o "restringir" o "rebanar". En particular, SERVER-6074 . Pero el problema se puede manejar de varias maneras.

Las implementaciones populares del patrón Rails Active Record para almacenamiento MongoDB son Mongoid y Mongo Mapper , ambos permiten el acceso a las funciones de colección de mongodb "nativas" a través de un .collection accesorio Esto es lo que básicamente necesita para poder usar métodos nativos como .agregar() que admite más funciones que la agregación general de Active Record.

Aquí hay un enfoque de agregación con mongoid, aunque el código general no se altera una vez que tiene acceso al objeto de colección 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

El nombre en los documentos en realidad no es utilizado por el código, y los títulos en los datos que se muestran para "Primero", "Segundo", etc., realmente están ahí para ilustrar que de hecho está obteniendo los "2 mejores" documentos de la lista como un resultado.

Entonces, el enfoque aquí es esencialmente crear una "pila" de los documentos "agrupados" por su clave, como "tipo". Lo primero aquí es tomar el "primer" documento de esa pila usando $first operador.

Los pasos subsiguientes hacen coincidir los elementos "vistos" de la pila y los filtran, luego quita el documento "siguiente" de la pila nuevamente usando $first operador. Los pasos finales allí son realmente justx para devolver los documentos a la forma original tal como se encuentran en la entrada, que generalmente es lo que se espera de una consulta de este tipo.

Entonces, el resultado es, por supuesto, solo los 2 documentos principales para cada tipo:

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

Hubo una discusión más larga y una versión de esto, así como otras soluciones en esta respuesta reciente:

Grupo $ de agregación de Mongodb, restringe la longitud de la matriz

Esencialmente lo mismo a pesar del título y ese caso buscaba hacer coincidir hasta 10 entradas principales o más. También hay un código de generación de canalización para lidiar con coincidencias más grandes, así como algunos enfoques alternativos que pueden considerarse según sus datos.