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

Multipel grænsetilstand i mongodb

Generelt er det, du beskriver, et relativt almindeligt spørgsmål omkring MongoDB-fællesskabet, som vi kunne beskrive som "top n resultater problem". Dette er, når der gives noget input, der sandsynligvis er sorteret på en eller anden måde, hvordan man får den øverste n resultater uden at stole på vilkårlige indeksværdier i dataene.

MongoDB har $first operatør, som er tilgængelig for aggregationsramme som omhandler "top 1"-delen af ​​problemet, da dette faktisk tager det "første" element fundet på en grupperingsgrænse, såsom din "type". Men at få mere end "et" resultat bliver selvfølgelig lidt mere involveret. Der er nogle JIRA-problemer om dette om at ændre andre operatører til at håndtere n resultater eller "begræns" eller "slice". Især SERVER-6074 . Men problemet kan håndteres på flere måder.

Populære implementeringer af skinnernes Active Record-mønster til MongoDB-lagring er Mongoid og Mongo Mapper , giver begge adgang til de "native" mongodb-samlingsfunktioner via en .collection tilbehør. Dette er, hvad du grundlæggende har brug for for at kunne bruge native metoder såsom .aggregate() som understøtter mere funktionalitet end generel Active Record-aggregering.

Her er en aggregeringstilgang med mongoid, selvom den generelle kode ikke ændres, når du først har adgang til det oprindelige samlingsobjekt:

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

Navngivningen i dokumenterne bruges faktisk ikke af koden, og titler i dataene vist for "First", "Second" osv. er egentlig kun til for at illustrere, at du faktisk får "top 2" dokumenterne fra listen som et resultat.

Så fremgangsmåden her er i det væsentlige at skabe en "stak" af dokumenterne "grupperet" efter din nøgle, såsom "type". Den allerførste ting her er at tage det "første" dokument fra den stak ved hjælp af $first operatør.

De efterfølgende trin matcher de "sete" elementer fra stakken og filtrerer dem, så tager du det "næste" dokument ud af stakken igen ved hjælp af $first operatør. De sidste trin derinde er egentlig bare at returnere dokumenterne til den originale form, som findes i inputtet, hvilket generelt er, hvad der forventes af en sådan forespørgsel.

Så resultatet er selvfølgelig kun de øverste 2 dokumenter for hver type:

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

Der var en længere diskussion og version af dette samt andre løsninger i dette seneste svar:

Mongodb aggregering $group, begræns længden af ​​array

Grundlæggende det samme på trods af titlen, og den sag søgte at matche op til 10 topposter eller flere. Der er også noget pipeline-genereringskode til håndtering af større matches samt nogle alternative tilgange, der kan overvejes afhængigt af dine data.



  1. fejl TS2688:Kan ikke finde typedefinitionsfil for 'bson'

  2. MongoDB dropper hver database

  3. Sådan installeres NodeJS med MongoDB på Ubuntu

  4. Redis køarbejder går ned i utcparse