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

Vis kun matchende felter til MongoDB-tekstsøgning

Efter at have tænkt over dette længe, ​​tror jeg, det er muligt at implementere det, man ønsker. Det er dog ikke egnet til meget store databaser, og jeg har endnu ikke udarbejdet en inkrementel tilgang. Det mangler stammeord, og stopord skal defineres manuelt.

Ideen er at bruge mapReduce til at lave en samling søgeord med referencer til oprindelsesdokumentet og det felt, hvor søgeordet stammer fra. Derefter udføres selve forespørgslen om autofuldførelse ved hjælp af en simpel aggregering, som bruger et indeks og derfor burde være ret hurtig.

Så vi vil arbejde med følgende tre dokumenter

{
  "name" : "John F. Kennedy",
  "address" : "Kenson Street 1, 12345 Footown, TX, USA",
  "note" : "loves Kendo and Sushi"
}

og

{
  "name" : "Robert F. Kennedy",
  "address" : "High Street 1, 54321 Bartown, FL, USA",
  "note" : "loves Ethel and cigars"
}

og

{
  "name" : "Robert F. Sushi",
  "address" : "Sushi Street 1, 54321 Bartown, FL, USA",
  "note" : "loves Sushi and more Sushi"
}

i en samling kaldet textsearch .

Kort-/reducer-stadiet

Det, vi grundlæggende gør, er, at vi behandler hvert eneste ord i et af de tre felter, fjerner stopord og tal og gemmer hvert eneste ord med dokumentets _id og feltet for forekomsten i en mellemtabel.

Den kommenterede kode:

db.textsearch.mapReduce(
  function() {

    // We need to save this in a local var as per scoping problems
    var document = this;

    // You need to expand this according to your needs
    var stopwords = ["the","this","and","or"];

    // This denotes the fields which should be processed
    var fields = ["name","address","note"];

    // For each field...
    fields.forEach(

      function(field){

        // ... we split the field into single words...
        var words = (document[field]).split(" ");

        words.forEach(

          function(word){
            // ...and remove unwanted characters.
            // Please note that this regex may well need to be enhanced
            var cleaned = word.replace(/[;,.]/g,"")

            // Next we check...
            if(
              // ...wether the current word is in the stopwords list,...
              (stopwords.indexOf(word)>-1) ||

              // ...is either a float or an integer... 
              !(isNaN(parseInt(cleaned))) ||
              !(isNaN(parseFloat(cleaned))) ||

              // or is only one character.
              cleaned.length < 2
            )
            {
              // In any of those cases, we do not want to have the current word in our list.
              return
            }
              // Otherwise, we want to have the current word processed.
              // Note that we have to use a multikey id and a static field in order
              // to overcome one of MongoDB's mapReduce limitations:
              // it can not have multiple values assigned to a key.
              emit({'word':cleaned,'doc':document._id,'field':field},1)

          }
        )
      }
    )
  },
  function(key,values) {

    // We sum up each occurence of each word
    // in each field in every document...
    return Array.sum(values);
  },
    // ..and write the result to a collection
  {out: "searchtst" }
)

At køre dette vil resultere i oprettelsen af ​​samlingen searchtst . Hvis det allerede eksisterede, vil alt dets indhold blive erstattet.

Det vil se nogenlunde sådan her ud:

{ "_id" : { "word" : "Bartown", "doc" : ObjectId("544b9811fd9270c1492f5835"), "field" : "address" }, "value" : 1 }
{ "_id" : { "word" : "Bartown", "doc" : ObjectId("544bb320fd9270c1492f583c"), "field" : "address" }, "value" : 1 }
{ "_id" : { "word" : "Ethel", "doc" : ObjectId("544b9811fd9270c1492f5835"), "field" : "note" }, "value" : 1 }
{ "_id" : { "word" : "FL", "doc" : ObjectId("544b9811fd9270c1492f5835"), "field" : "address" }, "value" : 1 }
{ "_id" : { "word" : "FL", "doc" : ObjectId("544bb320fd9270c1492f583c"), "field" : "address" }, "value" : 1 }
{ "_id" : { "word" : "Footown", "doc" : ObjectId("544b7e44fd9270c1492f5834"), "field" : "address" }, "value" : 1 }
[...]
{ "_id" : { "word" : "Sushi", "doc" : ObjectId("544bb320fd9270c1492f583c"), "field" : "name" }, "value" : 1 }
{ "_id" : { "word" : "Sushi", "doc" : ObjectId("544bb320fd9270c1492f583c"), "field" : "note" }, "value" : 2 }
[...]

Der er et par ting at bemærke her. Først og fremmest kan et ord have flere forekomster, for eksempel med "FL". Det kan dog være i forskellige dokumenter, som det er tilfældet her. Et ord kan på den anden side også have flere forekomster i et enkelt felt i et enkelt dokument. Vi vil bruge dette til vores fordel senere.

For det andet har vi alle felterne, især word felt i et sammensat indeks for _id , hvilket burde gøre de kommende forespørgsler ret hurtige. Dette betyder dog også, at indekset vil være ret stort og – som for alle indekser – har en tendens til at æde RAM.

Aggregeringsstadiet

Så vi har reduceret listen over ord. Nu forespørger vi efter en (under)streng. Det, vi skal gøre, er at finde alle ord, der begynder med den streng, som brugeren har indtastet indtil videre, og returnere en liste over ord, der matcher den streng. For at kunne gøre dette og få resultaterne i en form, der passer til os, bruger vi en aggregering.

Denne aggregering burde være ret hurtig, da alle nødvendige felter til forespørgsel er en del af et sammensat indeks.

Her er den kommenterede aggregering for sagen, hvor brugeren indtastede bogstavet S :

db.searchtst.aggregate(
  // We match case insensitive ("i") as we want to prevent
  // typos to reduce our search results
  { $match:{"_id.word":/^S/i} },
  { $group:{
      // Here is where the magic happens:
      // we create a list of distinct words...
      _id:"$_id.word",
      occurrences:{
        // ...add each occurrence to an array...
        $push:{
          doc:"$_id.doc",
          field:"$_id.field"
        } 
      },
      // ...and add up all occurrences to a score
      // Note that this is optional and might be skipped
      // to speed up things, as we should have a covered query
      // when not accessing $value, though I am not too sure about that
      score:{$sum:"$value"}
    }
  },
  {
    // Optional. See above
    $sort:{_id:-1,score:1}
  }
)

Resultatet af denne forespørgsel ser nogenlunde sådan ud og burde være ret selvforklarende:

{
  "_id" : "Sushi",
  "occurences" : [
    { "doc" : ObjectId("544b7e44fd9270c1492f5834"), "field" : "note" },
    { "doc" : ObjectId("544bb320fd9270c1492f583c"), "field" : "address" },
    { "doc" : ObjectId("544bb320fd9270c1492f583c"), "field" : "name" },
    { "doc" : ObjectId("544bb320fd9270c1492f583c"), "field" : "note" }
  ],
  "score" : 5
}
{
  "_id" : "Street",
  "occurences" : [
    { "doc" : ObjectId("544b7e44fd9270c1492f5834"), "field" : "address" },
    { "doc" : ObjectId("544b9811fd9270c1492f5835"), "field" : "address" },
    { "doc" : ObjectId("544bb320fd9270c1492f583c"), "field" : "address" }
  ],
  "score" : 3
}

Karakteren 5 for Sushi kommer af, at ordet Sushi forekommer to gange i notefeltet i et af dokumenterne. Dette er tilsigtet adfærd.

Selvom dette kan være en fattigmandsløsning, skal optimeres til de myriader af tænkelige brugssager og ville have brug for en inkrementel mapReduce, der skal implementeres for at være halvvejs nyttig i produktionsmiljøer, fungerer det som forventet. hth.

Rediger

Selvfølgelig kunne man droppe $match fase og tilføje en $out trin i aggregeringsfasen for at få resultaterne forbehandlet:

db.searchtst.aggregate(
  {
    $group:{
      _id:"$_id.word",
      occurences:{ $push:{doc:"$_id.doc",field:"$_id.field"}},
      score:{$sum:"$value"}
     }
   },{
     $out:"search"
   })

Nu kan vi forespørge på den resulterende search indsamling for at fremskynde tingene. Grundlæggende bytter du resultater i realtid for hastighed.

Rediger 2 :I tilfælde af at forbehandlingsmetoden tages, skal searchtst samlingen af ​​eksemplet skal slettes efter aggregeringen er færdig for at spare både diskplads og – endnu vigtigere – dyrebar RAM.




  1. Hvordan kan vi sikre dataintegritet i mongoDb?

  2. oprette objektforælder, som indlejrede børn i mongoose

  3. Opdeling af poster i en samling i MongoDB

  4. MongoDB $sum og $avg af underdokumenter