3

I'm using ElasticSearch and tire.rb to index and search my collection of items.

I want to query on the name field in my index.

If I have a document with the name: Alfa Romeo, I would like to find this document by searching for:

  1. "Alfa"
  2. "Alfa Remeo" (notice spelling mistake)

In ElasticSearch and tire, I know how to set up the two queries separately:

Searching with wildcard:

Model.tire.search do
  query do
    boolean do
      must { string "#{myquerystring}*", default_field: 'name' }
    end
  end
end

Searching with fuzzy (Levenshtein-distance):

Model.tire.search do
  query do
    boolean do
      must { text :name, { query: mysquerystring, operator: 'AND', fuzziness: 0.4 } }
    end
  end
end

How to combine (with or)?

What I want to do is to find all documents where wildcard OR fuzzy search matches. I could do two seperate searches and try to combine them, but that does not make much sense. Can I do this in some logical way?

simonwh
  • 1,017
  • 8
  • 21

2 Answers2

3

As suggested by David, you can use bool query with minimum_number_should_match.

But here are some thoughts about wildcards in query string.

In your case, a prefix query is preferable to query string:

  • wildcards in query strings are slower than prefix query
  • you are manually adding * at the end of the query provided by user - a clear indication you want to use a prefix query (which is is designed for that)

In Tire, here's a complete example:

require 'tire'


class Car
  include Tire::Model::Persistence

  property :name, type: "multi_field",
                    fields: {
                      name:  { type: 'string', analyzer: 'snowball'  },
                      exact: { type: 'string', index:    'not_analyzed' }
                    }

end

Car.index.delete
Car.create_elasticsearch_index

Car.create name: 'Alfa'
Car.create name: 'Alfa Romeo'
Car.index.refresh

queries = [ 'Alfa', 'Alf', 'Alfa Remeo', 'Remeo' ] # Notice the spelling mistake

puts "Searching for: #{queries.join(', ')}", "="*80, ""

queries.each do |q|

  s = Car.search do
    query do
      boolean minimum_number_should_match: 1 do
        should { prefix 'name', q  }
        should { prefix 'name.exact', q, boost: 10 }
        should { match :name, q, operator: 'AND', fuzziness: 0.4 }
      end
    end
  end

  puts "Found #{s.results.size} results for query '#{q}':",
      "-"*80,
       s.map { |d| "#{d.name} (score: #{d._score})" }.join(", "),
       ""
end

with result:

Searching for: Alfa, Alf, Alfa Remeo, Remeo
================================================================================

Found 2 results for query 'Alfa':
--------------------------------------------------------------------------------
Alfa (score: 0.67262733), Alfa Romeo (score: 0.67027444)

Found 2 results for query 'Alf':
--------------------------------------------------------------------------------
Alfa (score: 0.6693944), Alfa Romeo (score: 0.66834825)

Found 1 results for query 'Alfa Remeo':
--------------------------------------------------------------------------------
Alfa Romeo (score: 0.08865173)

Found 1 results for query 'Remeo':
--------------------------------------------------------------------------------
Alfa Romeo (score: 0.06392767)
vhyza
  • 1,030
  • 6
  • 9
  • I applied your example and got error message: NoMethodError: undefined method `text' for #. But when I remove the line "should { text :name, q, operator: 'AND', fuzziness: 0.4 }" , it runs OK. – datnt Apr 01 '14 at 03:32
  • 1
    Text query was renamed to Match query http://www.elasticsearch.org/guide/en/elasticsearch/reference/0.90/query-dsl-text-query.html. I updated the answer. Thank you for pointing this out... – vhyza Apr 01 '14 at 22:05
2

What about using a boolean query with should queries : http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/query-dsl-bool-query.html ?

Does it answer to your use case ?

David

maximus ツ
  • 7,949
  • 3
  • 25
  • 54
dadoonet
  • 14,109
  • 3
  • 42
  • 49