MongoDB
 sql >> Base de Dados >  >> NoSQL >> MongoDB

Condição de limite múltiplo no mongodb


Geralmente, o que você está descrevendo é uma pergunta relativamente comum na comunidade MongoDB, que poderíamos descrever como a "principal n problemas de resultados". Isto é quando é dada alguma entrada que provavelmente é classificada de alguma forma, como obter o n principal resultados sem depender de valores de índice arbitrários nos dados.

O MongoDB tem o $first operador que está disponível para a estrutura de agregação que lida com a parte "top 1" do problema, pois isso realmente leva o "primeiro" item encontrado em um limite de agrupamento, como seu "tipo". Mas obter mais de "um" resultado, é claro, envolve um pouco mais. Existem alguns problemas do JIRA sobre como modificar outros operadores para lidar com n resultados ou "restringir" ou "fatiar". Notavelmente SERVER-6074 . Mas o problema pode ser tratado de algumas maneiras.

As implementações populares do padrão Rails Active Record para armazenamento MongoDB são Mongoid e Mongo Mapper , ambos permitem o acesso às funções de coleção "nativas" do mongodb por meio de um .collection acessador. Isso é o que você basicamente precisa para poder usar métodos nativos, como .agregar() que suporta mais funcionalidades do que a agregação geral do Active Record.

Aqui está uma abordagem de agregação com mongoid, embora o código geral não seja alterado quando você tiver acesso ao objeto de coleção 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

A nomenclatura nos documentos na verdade não é usada pelo código, e os títulos nos dados mostrados para "Primeiro", "Segundo" etc. um resultado.

Portanto, a abordagem aqui é essencialmente criar uma "pilha" de documentos "agrupados" por sua chave, como "tipo". A primeira coisa aqui é pegar o "primeiro" documento dessa pilha usando o $first operador.

As etapas subsequentes correspondem aos elementos "vistos" da pilha e os filtram, então você retira o documento "próximo" da pilha novamente usando o $first operador. As etapas finais são realmente apenas para retornar os documentos ao formato original, conforme encontrado na entrada, que geralmente é o que se espera de uma consulta desse tipo.

Portanto, o resultado é, obviamente, apenas os 2 principais documentos para cada tipo:
{ "type"=>"A", "pos"=>"First" }
{ "type"=>"A", "pos"=>"Second" }
{ "type"=>"B", "pos"=>"First" }
{ "type"=>"B", "pos"=>"Second" }

Houve uma discussão e versão mais longa disso, bem como outras soluções nesta resposta recente:

Agregação Mongodb $grupo, tamanho restrito da matriz

Essencialmente a mesma coisa, apesar do título e desse caso, estava procurando corresponder até 10 entradas principais ou mais. Há algum código de geração de pipeline também para lidar com correspondências maiores, bem como algumas abordagens alternativas que podem ser consideradas dependendo de seus dados.