Reputation: 168
I'm using elasticsearch in a Rails application. I want my search results can be sorted by price, so i add the price attribute to mapping. Before this i was able to search with success for the other fields that i had specify. Now i'm getting an error like this:
Elasticsearch::Transport::Transport::Errors::BadRequest in Search#search: [400] {"error":"SearchPhaseExecutionException[Failed to execute phase [query], all shards failed; shardFailures {[7aIAvW_pSlCg7HDBXwNvXA][products][0]: SearchParseException[[products][0]: from[-1],size[-1]: Parse Failure [Failed to parse source [{\"query\":{\"bool\":{\"should\":[{\"multi_match\":{\"query\":\"electronics\",\"fuzziness\":2,\"fields\":[\"name^2\",\"description\",\"category.name\",\"price\"],\"prefix_length\":2,\"operator\":\"and\"}}]}}}]]]; nested: NumberFormatException[For input string: \"electronics\"]; }
If i remove price attribute from the mapping i get search results but the order is not correct. It seems that the results are sorted by the first digit, treat like a string i think, e.g. 1111 appeared to be smaller than 200 because first digit '1' is smaller than '2'.
Any ideas?
Search controller:
class SearchController < ApplicationController
def search
options = { sort: params[:s] }
@products = Product.search(params[:q], options).paginate(page: params[:page], per_page: 5).records
end
end
Product model:
require "elasticsearch/model"
class Product < ActiveRecord::Base
include Elasticsearch::Model
include Elasticsearch::Model::Callbacks
def self.search(query, options={})
@search_definition = {
query: {} }
unless query.blank?
@search_definition[:query] = {
bool: {
should: [
{ multi_match: {
query: query,
fuzziness: 2,
fields: ['name^2', 'description','category.name', 'price'],
prefix_length: 2,
operator: 'and'}}]}}
else
@search_definition[:query] = { match_all: {} }
@search_definition[:sort] = [{ created_at: { order: "desc" }}]
end
if options[:sort] == 'Newest'
@search_definition[:sort] = [{ created_at: { order: "desc" }}]
@search_definition[:track_scores] = true
elsif options[:sort] == 'Price - Descending'
@search_definition[:sort] = [{ price: { order: "desc" }}]
@search_definition[:track_scores] = true
elsif options[:sort] == 'Price - Ascending'
@search_definition[:sort] = [{ price: { order: "asc" }}]
@search_definition[:track_scores] = true
end
__elasticsearch__.search @search_definition
end
settings analysis: {
analyzer: {
my_index_analyzer: {
type: "custom",
tokenizer: "standard",
filter: ["standard", "lowercase", "translation"] },
my_search_analyzer: {
type: "custom",
tokenizer: "standard",
filter: ["standard", "lowercase"] }
},
filter: {
translation: {
type: "nGram",
min_gram: 2,
max_gram: 20 }}
}
mapping do
indexes :name, type: 'string', index_analyzer: 'my_index_analyzer', search_analyzer: 'my_search_analyzer'
indexes :description, type: 'string', index_analyzer: 'my_index_analyzer', search_analyzer: 'my_search_analyzer'
indexes :created_at, type: 'date'
indexes :price, type: 'double', index: "not_analyzed"
indexes :category do
indexes :name, type: 'string', index_analyzer: 'my_index_analyzer', search_analyzer: 'my_search_analyzer'
end
end
def as_indexed_json(options={})
as_json(
only: [:name, :description, :price, :created_at],
include: { category: { only: :name } }
)
end
search.html.erb:
<div class="container main-body">
<h2>Search results</h2>
<div class="clearfix">
<ul class="list-inline pull-right">
<li><h5>Show search results by:</h5></li>
<li>
<div class="btn-group">
<button class="btn btn-default btn-md dropdown-toggle" type="button" data-toggle="dropdown">
<% sort = case
when params[:s] then params[:s]
when params[:q].blank? then 'Newest'
else 'Relevancy'
end
%>
<%= sort.humanize %> <span class="caret"></span>
</button>
<ul class="dropdown-menu" role="menu">
<li><%= link_to "Relevancy", search_path(params.except(:controller, :action).merge(s: nil)), class: 'btn-xs' %></li>
<li><%= link_to "Newest", search_path(params.except(:controller, :action).merge(s: 'Newest')), class: 'btn-xs' %></li>
<li><%= link_to "Price - Descending", search_path(params.except(:controller, :action).merge(s: 'Price - Descending')), class: 'btn-xs' %></li>
<li><%= link_to "Price - Ascending", search_path(params.except(:controller, :action).merge(s: 'Price - Ascending')), class: 'btn-xs' %></li>
</ul>
</div></li>
</ul>
</div>
<div class="clearfix">
<%= render partial: 'products/products_form' %>
</div>
<div class="centered"><%= will_paginate @products %></div>
</div>
Upvotes: 0
Views: 351
Reputation: 6426
The issue and the solution is listed at https://github.com/elastic/elasticsearch/issues/3975
The problem is the multi_match query. It works only with strings.
EDIT:
@search_definition[:query] = {
bool: {
should: [
{ multi_match: {
query: query,
fuzziness: 2,
fields: ['name^2', 'description','category.name', 'price'],
lenient: true
prefix_length: 2,
operator: 'and'}}]}}
Upvotes: 1