Anshad Vattapoyil
Anshad Vattapoyil

Reputation: 23483

Laravel elastic search not giving contains or like match

I wanted to have elasticsearch implemented for all my laravel search queries. I have the latest Laravel and latest elasticsearch installed using brew.

curl http://localhost:9200/ gives,

{
  "name" : "_SFvSGk",
  "cluster_name" : "elasticsearch_an398690",
  "cluster_uuid" : "xBi3aTDaTkmA6dtzhpOrwg",
  "version" : {
    "number" : "6.5.4",
    "build_flavor" : "oss",
    "build_type" : "tar",
    "build_hash" : "d2ef93d",
    "build_date" : "2018-12-17T21:17:40.758843Z",
    "build_snapshot" : false,
    "lucene_version" : "7.5.0",
    "minimum_wire_compatibility_version" : "5.6.0",
    "minimum_index_compatibility_version" : "5.0.0"
  },
  "tagline" : "You Know, for Search"
}

Here I am using the driver babenkoivan/scout-elasticsearch-driver.

Model,

namespace App;

use ScoutElastic\Searchable;
use Illuminate\Database\Eloquent\Model;

class Customer extends Model
{
    use Searchable;

    /**
     * @var string
     */
    protected $indexConfigurator = CustomerIndexConfigurator::class;

    /**
     * @var array
     */
    protected $searchRules = [
        CustomerSearchRule::class
    ];

    /**
     * @var array
     */
    protected $mapping = [
        'properties' => [
            'text' => [
                'type' => 'text',
                'fields' => [
                    'ref_num' => [
                        'type' => 'keyword',
                    ]
                ]
            ],
        ]
    ];
}

SearchRule,

namespace App;

use ScoutElastic\SearchRule;

class CustomerSearchRule extends SearchRule
{
    /**
     * @inheritdoc
     */
    public function buildHighlightPayload()
    {
        return [
            'fields' => [
                'ref_num' => [
                    'type' => 'plain'
                ]
            ]
        ];
    }

    /**
     * @inheritdoc
     */
    public function buildQueryPayload()
    {
        $query = $this->builder->query;

        return [
                [
                    'match' => [
                        'ref_num' => [
                            'query' => $query,
                            'boost' => 2
                        ]
                    ]
                ]
        ];
    }
}

Configurator,

namespace App;

use ScoutElastic\IndexConfigurator;
use ScoutElastic\Migratable;

class CustomerIndexConfigurator extends IndexConfigurator
{
    use Migratable;

    /**
     * @var array
     */
    protected $settings = [
        //
    ];
}

I have a record with ref_num as I50263. So I should get this record when I search I50 same like like query. I tried all the below search but I am getting the result only with the complete word I50263.

return Customer::search('I50')->get();
// no record
return Customer::search('I50263')->get();
// got record
return Customer::searchRaw([
  'query' => [
     'bool' => [
        'must' => [
            'match' => [
                'ref_num' => 'I502'
            ]
        ]
     ]
  ]
]);
// no record
return Customer::searchRaw([
  'query' => [
      'bool' => [
          'must' => [
             "match_phrase" => [
                "ref_num" => [
                   "query" => "I50",
                   "boost" => 1
                 ]
              ]
           ]
        ]
     ]
 ]);
 // no record

Tried field type as text also.

Upvotes: 0

Views: 2676

Answers (1)

Piotr Pradzynski
Piotr Pradzynski

Reputation: 4535

As I see, your ref_num field is of keyword type. Performing full-text queries (like match or match_phrase) does not give you any results. For keyword-s you should use term level queries. Perhaps prefix query would be helpful for you here.

Example

Mapping

PUT /so54176561
{
  "mappings": {
    "_doc": {
      "properties": {
        "ref_num": {
          "type": "text",
          "fields": {
            "raw": {
              "type": "keyword"
            }
          }
        }
      }
    }
  }
}

Adding sample document

POST /so54176561/_doc/1
{
  "ref_num": "I50263"
}

Full-text match search on text type field

by entire value

POST /so54176561/_search
{
  "query": {
    "match": {
      "ref_num": "I50263"
    }
  }
}

Result: document found

by prefix of the value

POST /so54176561/_search
{
  "query": {
    "match": {
      "ref_num": "I50"
    }
  }
}

Result: document not found

Term level prefix search on keyword type field

POST /so54176561/_search
{
  "query": {
    "prefix": {
      "ref_num.raw": "I50"
    }
  }
}

Result: document found

As you can see, in the example I used so kind of subfields (raw is a subfield of ref_num but with a different type). In Elasticsearch it is called fields and more about that you can read in the documentation.

You can simply use the query with any other query on any other field using bool query properly.

If you would like to achieve the same result on the text type field you have to prepare your index properly. For example, you could use your custom analyzer with an NGram tokenizer which splits the words into n-gram tokens.

By default, any of the analyzers do not split words so in your case you had only one token in the index:

POST /_analyze
{
  "analyzer": "standard",
  "text": "I50263"
}

Result:

{
  "tokens": [
    {
      "token": "i50263",
      "start_offset": 0,
      "end_offset": 6,
      "type": "<ALPHANUM>",
      "position": 0
    }
  ]
}

For full-text search, Elasticsearch is basing on the tokens it has in the index. If tokens do not match the tokens from the search term, then there is no match.

Upvotes: 1

Related Questions