sql >> Database >  >> NoSQL >> MongoDB

Meerdere limietcondities in mongodb

Over het algemeen is wat u beschrijft een relatief veel voorkomende vraag in de MongoDB-gemeenschap die we zouden kunnen omschrijven als de "top n resultatenprobleem". Dit is wanneer u invoer krijgt die waarschijnlijk op de een of andere manier is gesorteerd, hoe u de beste n krijgt resultaten zonder te vertrouwen op willekeurige indexwaarden in de gegevens.

MongoDB heeft de $first operator die beschikbaar is voor het aggregatieraamwerk die zich bezighoudt met het "top 1" deel van het probleem, omdat dit eigenlijk het "eerste" item op een groeperingsgrens nodig heeft, zoals uw "type". Maar om meer dan "één" resultaat te krijgen, komt er natuurlijk iets meer bij kijken. Er zijn enkele JIRA-problemen met betrekking tot het aanpassen van andere operators om met n om te gaan resultaten of "beperken" of "slice". Met name SERVER-6074 . Maar het probleem kan op een paar manieren worden opgelost.

Populaire implementaties van het rails Active Record-patroon voor MongoDB-opslag zijn Mongoid en Mongo Mapper , beide geven toegang tot de "native" mongodb-verzamelingsfuncties via een .collection accessoire. Dit is wat je in principe nodig hebt om native methoden te kunnen gebruiken, zoals .aggregate() die meer functionaliteit ondersteunt dan algemene Active Record-aggregatie.

Hier is een aggregatiebenadering met mongoïde, hoewel de algemene code niet verandert zodra u toegang heeft tot het oorspronkelijke verzamelingsobject:

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

De naamgeving in de documenten wordt eigenlijk niet gebruikt door de code, en titels in de gegevens die worden weergegeven voor "Eerste", "Tweede" enz., zijn er eigenlijk alleen maar om te illustreren dat u inderdaad de "top 2" -documenten uit de lijst krijgt als een resultaat.

Dus de benadering hier is in wezen om een ​​"stapel" van de documenten te maken die "gegroepeerd" zijn op uw sleutel, zoals "type". Het allereerste wat hier is om het "eerste" document van die stapel te nemen met behulp van de $first telefoniste.

De volgende stappen komen overeen met de "geziene" elementen uit de stapel en filteren ze, dan haal je het "volgende" document weer van de stapel met behulp van de $first exploitant. De laatste stappen daarin zijn eigenlijk gewoonx om de documenten terug te brengen naar de originele vorm zoals gevonden in de invoer, wat over het algemeen is wat van een dergelijke zoekopdracht wordt verwacht.

Dus het resultaat is natuurlijk alleen de top 2 documenten voor elk type:

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

Er was een langere discussie en versie hiervan, evenals andere oplossingen in dit recente antwoord:

Mongodb-aggregatie $group, beperk de lengte van de array

In wezen hetzelfde ondanks de titel en die zaak wilde overeenkomen met maximaal 10 topitems of meer. Er is daar ook een code voor het genereren van pijplijnen voor het omgaan met grotere overeenkomsten, evenals enkele alternatieve benaderingen die kunnen worden overwogen, afhankelijk van uw gegevens.



  1. VersionError:Geen overeenkomend document gevonden fout op Node.js/Mongoose

  2. Hoe meerdere velden van een array-object bij te werken met één verzoek?

  3. Een PDF-bestand opslaan in DB met Flask-admin

  4. Next Generation Data Warehousing bij Santander UK