Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Searchkick not searching multiple terms when specify fields

Can anyone provide advice on the following please?

I'm using searchkick / elasticsearch and would like to search for a key term or terms across multiple fields (name, manufacturer). So for example if im looking for a product called "myproduct" made my "somemanufacturer" i'd expect to see this result appear if I search either "myproduct", "somemanufacturer" or "myproduct somemanufacturer" as both these terms are included either in name or manufacturer fields.

My problem is the following:

@products = Product.search query

Allows all the search terms listed above and returns expected result however as soon as I add

@products = Product.search query, fields: [:name, :manufacturer_name]

It will only return a result for "myproduct", or "somecompany", but not "myproduct somecompany".

Now this isn't a big deal as I can remove the fields option entirely BUT I need to utilise searchkicks word_start for the name field. So my final query is something like this:

@products = Product.search query, fields: [{name: :word_start}, :manufacturer_name]

I'd like users to search for the 1st string of the product and be able to enter a manufacturer too eg "myprod somecompany" unfortunately this returns zero results when I was hoping it would return the product named myproduct, made by somecompany.

Am i missing something really obvious here? I can change add

operator: 'or'

but really i want to be able to part search on the name, add additional terms and if both are present for a particular record it gets returned.

heres my model code also

class Product < ActiveRecord::Base
 searchkick word_start: [:name]
end

Thanks

like image 570
drac Avatar asked Sep 23 '15 15:09

drac


2 Answers

If all of your fields share the same analyzer, you can use elasticsearch feature called cross_fields. If it is not the case, you can use query_string. Unfortunately searchkick does not support cross_fields and query_string yet. So, you have to do it by yourself.

Index (different analyzers)

searchkick merge_mappings: true, mappings: {
  product: {
    properties: {
      name: {
        type: 'string',
        analyzer: 'searchkick_word_start_index',
        copy_to: 'grouped'
      },
      manufacturer_name: {
        type: 'string',
        analyzer: 'default_index',
        copy_to: 'grouped'
      },
      grouped: {
        raw: {type: 'string', index: 'not_analyzed'}
      }
    }
  }
}

Search with cross_fields

@products = Product.search(body: {
  query: {
     multi_match: {
                  query: query,
                  type: "cross_fields",
                  operator: "and",
                  fields: [
                      "name",
                      "manufacturer_name",
                      "grouped",
                  ]
              }
         }
}

Search with query_string

@products = Product.search(body: {
  query: {
      query_string: {
                  query: query,
                  default_operator: "AND",
                  fields: [
                      "name",
                      "manufacturer_name",
                      "grouped",
                  ]
              }
         }
}

Update - Changed my answer to the use of different analyzers and multi fields following the this solution.

Unfortunately passing the query to elasticsearch by yourself you lose searchkick features like highlight and conversions, but, you can still do it, adding it to the elasticsearch query.

Adding hightlight to the query

@products = Product.search(body: {
  query: {
    ...
  }, highlight: {
    fields: {
      name: {},
      manufacturer_name: {}
    }
  }

Adding conversions to the query

@products = Product.search(body: {
  query: {
    bool: {
      must: {
        dis_max: {
          queries: {
            query_string: {
              ...
            }
          }
        }
      },
      should: {
        nested: {
          path: 'conversions',
          score_mode: 'sum',
          query: {
            function_score: {
              boost_mode: 'replace',
              query: {
                match: {
                  "conversions.query": query
                }
              },
              field_value_factor: {
                field: 'conversions.count'
              }
            }
          }
        }
      }
    }
  }
like image 61
William Weckl Avatar answered Oct 15 '22 06:10

William Weckl


The easiest way to do this is to combine the fields into one with the search_data method.

class Product
  def search_data
    {
      full_name: "#{manufacturer_name} #{name}"
    }
  end
end

Be sure to reindex afterwards. Then use full name to search. All of Searchkick's features will continue to work.

like image 31
Andrew Kane Avatar answered Oct 15 '22 08:10

Andrew Kane