Search code examples
solrdjango-haystack

Stop SOLR from indexing same word multiple times (or how to boost one field)


So I'm running a slang dictionary type website and been previously using mysql LIKE for the site search. It has worked okay. Anyway, now I'm updating the site and was thinking of using django-haystack with SOLR (seems to be one of the best search options?)

I got it running, but the search results aren't any good. For example searching for the word "LOL" would give "Flood" as the first result as it also has an example of flooding with "LOL LOL LOL LOL LOL LOL", instead of showing the word LOL first.

So is it possible only to tokenize only one LOL from the Flood example (I'm new to SOLR, so this could be way wrong how I'm thinking). Or can I just boost the value of the word's title (so words where the searchterm matches the title comes first and words where the searchterm matches the example comes second)? I've tried the django-haystack field boost, but it doesn't seem to do much at all.

Thanks in advance!

EDIT: Here's the SOLR scheme (It's a bit large, mostly autogenerated by Django-Haystack):

<?xml version="1.0" ?>
<schema name="default" version="1.1">
  <types>
    <fieldtype name="string"  class="solr.StrField" sortMissingLast="true" omitNorms="true"/>
    <fieldType name="boolean" class="solr.BoolField" sortMissingLast="true" omitNorms="true"/>

    <!-- Numeric field types that manipulate the value into
         a string value that isn't human-readable in its internal form,
         but with a lexicographic ordering the same as the numeric ordering,
         so that range queries work correctly. -->
    <fieldType name="sint" class="solr.SortableIntField" sortMissingLast="true" omitNorms="true"/>
    <fieldType name="slong" class="solr.SortableLongField" sortMissingLast="true" omitNorms="true"/>
    <fieldType name="sfloat" class="solr.SortableFloatField" sortMissingLast="true" omitNorms="true"/>
    <fieldType name="sdouble" class="solr.SortableDoubleField" sortMissingLast="true" omitNorms="true"/>

    <fieldType name="date" class="solr.DateField" sortMissingLast="true" omitNorms="true"/>

    <fieldType name="text" class="solr.TextField" positionIncrementGap="100">
      <analyzer type="index">
        <tokenizer class="solr.WhitespaceTokenizerFactory"/>
        <!-- in this example, we will only use synonyms at query time
        <filter class="solr.SynonymFilterFactory" synonyms="index_synonyms.txt" ignoreCase="true" expand="false"/>
        -->
        <!-- find finnish ones <filter class="solr.StopFilterFactory" ignoreCase="true" words="stopwords.txt"/> -->
        <filter class="solr.WordDelimiterFilterFactory" generateWordParts="1" generateNumberParts="1" catenateWords="1" catenateNumbers="1" catenateAll="0"/>
        <filter class="solr.LowerCaseFilterFactory"/>
        <filter class="solr.RemoveDuplicatesTokenFilterFactory"/>
        <filter class="solr.SnowballPorterFilterFactory" language="Finnish" />
        <filter class="solr.RemoveDuplicatesTokenFilterFactory"/>
        <filter class="solr.EdgeNGramFilterFactory" minGramSize="2" maxGramSize="15" side="front" />
      </analyzer>
      <analyzer type="query">
        <tokenizer class="solr.WhitespaceTokenizerFactory"/>
        <filter class="solr.SnowballPorterFilterFactory" language="Finnish" />
        <!-- <filter class="solr.SynonymFilterFactory" synonyms="synonyms.txt" ignoreCase="true" expand="true"/> -->
        <!-- find finnish ones <filter class="solr.StopFilterFactory" ignoreCase="true" words="stopwords.txt"/> -->
        <filter class="solr.WordDelimiterFilterFactory" generateWordParts="1" generateNumberParts="1" catenateWords="0" catenateNumbers="0" catenateAll="0"/>
        <filter class="solr.LowerCaseFilterFactory"/>
        <filter class="solr.RemoveDuplicatesTokenFilterFactory"/>
      </analyzer>
    </fieldType>

    <fieldType name="text_ws" class="solr.TextField" positionIncrementGap="100">
      <analyzer>
        <tokenizer class="solr.WhitespaceTokenizerFactory"/>
      </analyzer>
    </fieldType>

    <fieldType name="ngram" class="solr.TextField" >
      <analyzer type="index">
        <tokenizer class="solr.KeywordTokenizerFactory"/>
        <filter class="solr.LowerCaseFilterFactory"/>
        <filter class="solr.NGramFilterFactory" minGramSize="3" maxGramSize="15" />
      </analyzer>
      <analyzer type="query">
        <tokenizer class="solr.KeywordTokenizerFactory"/>
        <filter class="solr.LowerCaseFilterFactory"/>
      </analyzer>
    </fieldType>

    <fieldType name="edge_ngram" class="solr.TextField" positionIncrementGap="1">
      <analyzer type="index">
        <tokenizer class="solr.WhitespaceTokenizerFactory" />
        <filter class="solr.LowerCaseFilterFactory" />
        <filter class="solr.WordDelimiterFilterFactory" generateWordParts="1" generateNumberParts="1" catenateWords="0" catenateNumbers="0" catenateAll="0" splitOnCaseChange="1"/>
        <filter class="solr.EdgeNGramFilterFactory" minGramSize="2" maxGramSize="15" side="front" />
      </analyzer>
      <analyzer type="query">
        <tokenizer class="solr.WhitespaceTokenizerFactory" />
        <filter class="solr.LowerCaseFilterFactory" />
        <filter class="solr.WordDelimiterFilterFactory" generateWordParts="1" generateNumberParts="1" catenateWords="0" catenateNumbers="0" catenateAll="0" splitOnCaseChange="1"/>
      </analyzer>
    </fieldType>
  </types>

  <fields>   
    <!-- general -->
    <field name="id" type="string" indexed="true" stored="true" multiValued="false" required="true"/>
    <field name="django_ct" type="string" indexed="true" stored="true" multiValued="false" />
    <field name="django_id" type="string" indexed="true" stored="true" multiValued="false" />

    <dynamicField name="*_i"  type="sint"    indexed="true"  stored="true"/>
    <dynamicField name="*_s"  type="string"  indexed="true"  stored="true"/>
    <dynamicField name="*_l"  type="slong"   indexed="true"  stored="true"/>
    <dynamicField name="*_t"  type="text"    indexed="true"  stored="true"/>
    <dynamicField name="*_b"  type="boolean" indexed="true"  stored="true"/>
    <dynamicField name="*_f"  type="sfloat"  indexed="true"  stored="true"/>
    <dynamicField name="*_d"  type="sdouble" indexed="true"  stored="true"/>
    <dynamicField name="*_dt" type="date"    indexed="true"  stored="true"/>


    <field name="rendered" type="string" indexed="false" stored="true" multiValued="false" />

    <field name="word" type="text" indexed="true" stored="true" multiValued="false" />

    <field name="author" type="text" indexed="true" stored="true" multiValued="false" />

    <field name="text" type="text" indexed="true" stored="true" multiValued="false" />

    <field name="explanation" type="text" indexed="true" stored="true" multiValued="false" />

    <field name="example" type="text" indexed="true" stored="true" multiValued="false" />

  </fields>

  <!-- field to use to determine and enforce document uniqueness. -->
  <uniqueKey>id</uniqueKey>

  <!-- field for the QueryParser to use when an explicit fieldname is absent -->
  <defaultSearchField>text</defaultSearchField>

  <!-- SolrQueryParser configuration: defaultOperator="AND|OR" -->
  <solrQueryParser defaultOperator="AND" />
</schema>

Solution

  • Your last comment cleared it up for me. What you need to look at is Relevance in general and in your case Field Boosting in special.

    In order to use that query-time boost on fields you are required to use Solr's DisMax Handler or its' extension the eDisMax Handler. You can tell that handler via its' qf parameter which fields to search in and how the boost for each field shall be.

    e.g.

    qf="word^10.0 title^5.0 exmaple^0.5"
    
    • if the document is matched by a hit in word, increment that score by a boost of 10
    • if the document is matched by a hit in title, increment that score by a boost of 5
    • if the document is matched by a hit in example, increment that score by a boost of 0.5, which equates to a decrement

    You can add that qf parameter either with every search query you send to Solr or you can configure it in your solrconfig.xml.

    <requestHandler name="standard" 
        class="solr.StandardRequestHandler" default="true">
        <!-- default values for query parameters -->
        <lst name="defaults">
            <str name="defType">edismax</str>
            <str name="q.alt">*:*</str>
            <str name="qf">word^10.0 title^5.0 exmaple^0.5</str>
            <str name="fl">*,score</str>
            <str name="mm">100%</str>
        </lst>
    </requestHandler>
    
    <queryParser name="edismax" 
        class="org.apache.solr.search.ExtendedDismaxQParserPlugin" />
    

    Some further reading