sql >> Databasteknik >  >> NoSQL >> MongoDB

Flera gränsvillkor i mongodb

Generellt sett är det du beskriver en relativt vanlig fråga runt MongoDB-communityt som vi skulle kunna beskriva som "top n resultatproblem". Detta är när det ges någon indata som troligen är sorterad på något sätt, hur man får den översta n resultat utan att förlita sig på godtyckliga indexvärden i data.

MongoDB har $first operatör som är tillgänglig för aggregationsramverket som behandlar "topp 1"-delen av problemet, eftersom detta faktiskt tar det "första" objektet som finns på en grupperingsgräns, till exempel din "typ". Men att få mer än "ett" resultat blir förstås lite mer involverat. Det finns några JIRA-problem om detta om att modifiera andra operatörer för att hantera n resultat eller "begränsa" eller "skiva". Särskilt SERVER-6074 . Men problemet kan hanteras på flera sätt.

Populära implementeringar av skenornas Active Record-mönster för MongoDB-lagring är Mongoid och Mongo Mapper , båda tillåter åtkomst till de "native" mongodb-samlingsfunktionerna via en .collection tillbehör. Detta är vad du i princip behöver för att kunna använda inbyggda metoder som .aggregate() som stöder mer funktionalitet än allmän Active Record-aggregering.

Här är en aggregeringsmetod med mongoid, även om den allmänna koden inte ändras när du har tillgång till det ursprungliga samlingsobjektet:

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

Namnet i dokumenten används faktiskt inte av koden, och titlar i data som visas för "Första", "Andra" etc, är egentligen bara där för att illustrera att du verkligen får "topp 2" dokumenten från listningen som ett resultat.

Så tillvägagångssättet här är i huvudsak att skapa en "stack" av dokumenten "grupperade" efter din nyckel, till exempel "typ". Det allra första här är att ta det "första" dokumentet från den stacken med $first operatör.

De efterföljande stegen matchar de "sedda" elementen från stacken och filtrerar dem, sedan tar du bort "nästa" dokument från stacken igen med $first operatör. De sista stegen där är egentligen bara att återställa dokumenten till den ursprungliga formen som finns i inmatningen, vilket i allmänhet är vad som förväntas av en sådan fråga.

Så resultatet är naturligtvis bara de två bästa dokumenten för varje typ:

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

Det fanns en längre diskussion och version av detta samt andra lösningar i det här senaste svaret:

Mongodb aggregering $group, begränsa längden på arrayen

I huvudsak samma sak trots titeln och det fallet var ute efter att matcha upp till 10 toppposter eller fler. Det finns viss pipelinegenereringskod där också för att hantera större matchningar samt några alternativa tillvägagångssätt som kan övervägas beroende på dina data.



  1. Mongoose unikt index på underdokument

  2. Batchuppsättningsdata från Dictionary till Redis

  3. Rate-Limit an API (spring MVC)

  4. Mongodb Försöker få utvalda fält att returnera från aggregat