چرا سکان آکادمی؟
کوئری‌های match در دسته بندی full-text

کوئری‌های match در دسته بندی full-text

در این بخش از دوره قصد داریم تا به دنیای کوئری‌ها در Elasticsearch وارد شویم. به طور کلی کوئری‌ها به خانواده‌های زیر دسته‌بندی می‌شوند:

  • Full-text queries: این کوئری‌ها همانطور که از نام آن‌ها مشخص است، مربوط به پیاده سازی انواع جستجو از نوع full-text بوده و برای جستجو در فیلد‌هایی مناسب هستند که توسط analyzer ها تجزیه و تحلیل شده باشند. مقادیر مورد جستجو در این کوئری‌ها نیز توسط analyzer های Elasticsearch تجزیه می‌شوند!
  • Term level queries: کوئری‌های این خانواده برعکس خانواده full-text برای جستجوی مقداری دقیق در فیلد‌هایی با داده‌ی ساختار‌یافته مانند ip، عدد یا آدرس ایمیل و ... مناسب هستند. مقدار مورد جستجو در این کوئری‌ها توسط analyzer تجزیه نشده و تنها امکان نرمال‌سازی آن‌ها وجود دارد!
  • Compound queries: این کوئری‌ها شامل کوئری‌های دیگری می‌شوند تا نوع رفتار آن‌ها و یا شیوه محاسبه‌ی امتیاز را تغییر دهند و به خودی خود هیچ جستجو یا فیلتری در داده‌ها انجام نمی‌دهند.
  • Geo and Shape queries: کوئری‌های مخصوص جستجو در اشکال هندسی و یا موقعیت‌های جغرافیایی
  • Span queries: کوئری‌های تخصصی و سطح پایین برای جستجو بر مبنای ترتیب و مجاورت term ها در داده‌های ایندکس‌شده. این دسته از کوئری‌ها معمولا برای شرایط خاص و بر روی اسناد قانونی یا اختراعات مورد استفاده خواهند بود.
  • Joining queries: این دسته از کوئری‌ها برای جستجو در فیلد‌هایی با نوع داده‌ی join یا nested مناسب بوده و جزو کوئری‌های پرهزینه محسوب می‌شوند.

تمرکز ما در این دوره بر روی دسته‌بندی‌های Full-text، Term level و Compound خواهد بود. برای استفاده از کوئری‌ها، از search API به صورت زیر می‌توان استفاده کرد:

GET <target-index>/_search
{
  "query": {
    …
  }
}

در کلید query ،درخواست مورد نظر قرار گرفته و نام ایندکس یا ایندکس‌های هدف ، در قسمت <target-index> مشخص می‌شود. در صورتی که چندین ایندکس برای جستجو موردنظر است نام آن‌ها با , از هم جدا می‌شوند مانند مثال زیر:

GET index_1,index_2,index_3/_search

بررسی انواع کوئری‌های match در خانواده full-text

در خانواده‎ی کوئری‌های full-text، کوئری‌های match، match phrase و multi-match همگی رویکردی مشابه دارند و ساده‌ترین نوع آن‌ها کوئری match است که در ادامه به آن‌ها خواهیم پرداخت:

  • match:

این کوئری مقدار مورد جستجو را تجزیه کرده و document هایی را در نتیجه برمی‌گرداند که مقادیر فیلد تعیین‌شده‌ی آن‌ها با term های جستجو شده تطبیق داشته باشند.

ساختار آن در search API به صورت زیر است:

GET <target-index>/_search
{
    "query": {
        "match": {
            <field-name>": {
                "query": "این یک مثال از کوئری است"
            }
        }
    }
}

 

پارامتر سطح اول کوئری match، نام فیلد مورد نظر برای جستجو است (<field-name>). داخل آن، پارامتر query جزو پارامتر‌های ضروری بوده و مقدار آن همان مقدار مورد جستجو خواهد بود.

نکته: کلید query اول در سطح بدنه‌ی API را با کلید query در ساختار کوئری match اشتباه نگیرید! کلید اول عمومی بوده و مخصوص search API است اما کلید دوم فقط مخصوص کوئری‌های match است!

 سایر پارامتر‌های مهم در ساختار <field-name> :

analyzer: نام analyzer موردنظر برای تجزیه‌ی مقدار query که به صورت پیش‌فرض از analyzer اعمال شده در فیلد موردنظر و یا مقدار search_analyzer تعیین‌شده در mapping فیلد استفاده می‌کند.

fuzziness: این پارامتر تعیین می‌کند بیشترین تعداد ویرایش کاراکتر‌ها در term های مورد جستجو برای تطبیق چقدر باشد. برای مثال وقتی کلمه‌ی "فریموک" در term های جستجو دیده شد، تا چه میزان ویرایش انجام شود تا با کلمه‌ی "فریمورک" منطبق شود. به عبارتی این پارامتر طبق معنی آن (fuzzy=مبهم) برای مواردی کاربردی است که کاربر غلط املایی داشته اما همچنان کلمات مشابه تطبیق داده شوند. به طور کلی مقدار این پارامتر به صورت یکی از اعداد 0، 1 یا 2 برای بیشترین تعداد ویرایش کاراکتر‌ها جهت تطبیق و یا به صورت AUTO جهت تشخیص تعداد ویرایش بر اساس طول هر term تعیین می‌شود. به عنوان مثال موارد زیر را در نظر بگیرید:

(بیشترین تعداد ویرایش 2 کاراکتر باشد) fuzziness: 2

(بر مبنای طول کلمه) fuzziness: AUTO

در حالت AUTO این امکان وجود دارد تا تعداد ویرایش‌ها بر مبنای طول term تعیین شود. به صورت پیش‌فرض طول term ها روی 3 و 6 تنظیم می‎شود به این معنی که term با طول کمتر از 3، بدون ویرایش، term با طول 3 تا 5 فقط یک ویرایش و term با طول 6 به بالا مجاز است دو ویرایش داشته باشد. در صورت نیاز به تغییر مقادیر پیش‌فرض کافی است مقدار AUTO به صورت زیر تغییر داده شود:

fuzziness: AUTO:[low],[high]

برای مثال 

fuzziness: AUTO:2,7

operator: این پارامتر تعیین می‌کند منطق جستجوی چندین term به صورت AND یا OR باشد. به عنوان مثال در عبارت "زندگی یک توسعه‌دهنده" کوئری‌های مربوط به تطبیق term های "زندگی"، "یک" و "توسعه‌دهنده" با عملگر AND جستجو شوند(تمامی term ها در مقدار فیلد مورد نظر باشد!) و یا با عملگر OR (حداقل یکی از term ها در مقدار فیلد باشد!). مقدار پیش‌فرض OR می‌باشد.

minimum_should_match: تعیین کننده‌ی حداقل تعداد شروط جهت تطبیق می‌باشد. به صورت پیش‌فرض مقدار 1 دارد به این معنی که حداقل یکی از term های مورد جستجو، باید در فیلد مدنظر تطبیق داشته باشد. توجه داشته باشید در صورتی که پارامتر operator مقدار and داشته باشد، این پارامتر کارکردی نخواهد داشت چراکه طبق مقدار and باید تمامی term ها تطبیق داده شوند.

 

نکته: از آنجایی که تنها پارامتر ضروری در کوئری match، پارامتر query است، چنانچه نخواهید هیچ یک از پارامتر‌های دیگر را مقداردهی کنید می‌توانید از ساختار کوتاه‌شده‌ی کوئری match به صورت زیر استفاده کنید:

GET <target-index>/_search
{
    "query": {
        "match": {
            "<field-name>": "این یک مثال از کوئری است"
        }
    }
}

 

  • match phrase:

این کوئری مشابه match بوده با این تفاوت که پس از تجزیه‌ی متن مورد جستجو، آن را به عنوان یک phrase یا عبارت سرهم درنظر گرفته و همان عبارت را (با همان ترتیب کلمات) مورد تطبیق قرار می‌دهد.

ساختار آن به شکل زیر است و هیچکدام از پارامتر‌های کوئری match غیر از query برای آن قابل استفاده نمی‌باشد:

GET <target-index>/_search
{
    "query": {
        "match_phrase": {
            "<field-name>": {
                "query": "سکان آکادمی"
            }
        }
    }
}

 

در مثال بالا عبارت "سکان آکادمی" به صورت سرهم مورد تطبیق خواهد بود و نتایج شامل document هایی است که عبارت مورد نظر را به صورت کامل و با همان ترتیب در فیلد موردنظر داشته باشند.

تنها پارامتر این کوئری علاوه بر پارامتر query، پارامتر slop است که تعیین می‌کند چه تعداد فاصله بین کلمات عبارت مجاز است. به صورت پیش‌فرض مقدار آن 0 است (ترتیب و فاصله کلمات باید دقیقا مشابه عبارت جستجو باشد). برای مثال دو term که موقعیت آن‌ها جابجا شده باشد مقدار slop آن‌ها 2 است!

 

  • multi-match:

این کوئری بر مبنای کوئری match کار کرده و برای شرایطی مناسب است که بخواهیم در چندین فیلد مختلف جستجو کنیم. همچنین امکان تقویت امتیازدهی (boost) به ازای هریک از فیلد‌ها در این کوئری وجود دارد. ساختار کوئری مانند نمونه زیر است:

 GET <target-index>/_search
 {
     "query": {
         "multi_match" : {
             "query": "محمد مهدی معظمی",
             "fields": [ "title^2", "*_name" ]
         }
     }
}

در کلید fields به صورت یک آرایه نام فیلد‌های مورد نظر را تعیین می‌کنیم. همانطور که در نمونه بالا می‌بینید، امکان استفاده از wildcard برای نام فیلد‌ها وجود دارد (*_name) و با استفاده از کاراکتر ^ می‌توان ضریب تقویت برای امتیازدهی به ازای هر فیلد تعیین کرد (title^2). در مثال بالا امتیاز هر تطبیق در فیلد title در عدد 2 ضرب خواهد شد (اهمیت title دو برابر سایر فیلد‌ها است!).

کوئری multi-match علاوه بر پارامتر‌های کوئری match که قبل‌تر آن‌ها را بررسی کردیم، یک پارامتر به نام type دارد که نوع جستجو و امتیازدهی بین چندین فیلد را تعیین می‌کند. مقادیر مختلف آن به شرح زیر است:

  • best_fields: این حالت که مقدار پیش‌فرض است، امتیاز تطبیق term ها در تمامی فیلد‌ها را محاسبه کرده و در نهایت برای امتیازدهی به document در نتایج خروجی، از امتیاز فیلدی که بیشترین مقدار را کسب کرده است، استفاده می‌کند. این حالت برای شرایطی مناسب است که در جستجوی عبارتی باشیم که در یکی از فیلد‌ها بهترین تطبیق (امتیاز) را داشته باشد. برای مثال تطبیق "پلتفرم سکان آکادمی" در فیلد title امتیاز بیشتری نسبت به تطبیق "پلتفرم" در فیلد text و "سکان آکادمی" در فیلد title به صورت جداگانه دارد. بنابراین امتیاز فیلد title برای تطبیق "پلتفرم سکان آکادمی" به عنوان امتیاز نهایی document مربوطه درنظر گرفته می‌شود.
  • most_fields: این حالت برای شرایطی مناسب است که مقداری را در فیلد‌های ایندکس‌شده‌ی مختلف از یک فیلد بخواهیم جستجو کنیم. مشابه مثالی که در فصل قبل در استفاده از analyzer های مختلف برای یک فیلد دیده بودیم، فرض کنید بخواهیم عبارت "ویژگی های جدید لاراول" را در فیلد‌های title.ngram و title.standard جستجو کنیم (title.ngram شامل فیلتر edge_ngram بوده و title.standard فقط از فیلتر stop_word استفاده کرده است!). در این سناریو استفاده از نوع most_fields باعث خواهد شد تا امتیاز نهایی document ها بر اساس مجموع امتیاز‌ها در هر فیلد محاسبه شده و مواردی که بیشترین تطبیق از حالات مختلف یک فیلد را دارند در نتایج خروجی بالاتر ظاهر شوند.
  • phrase: این حالت مشابه best_fields عمل کرده با این تفاوت که از کوئری match phrase به جای match استفاده خواهد کرد.
  • cross_fields: در حالات best_fields و most_fields که تا الان بررسی کردیم، term های عبارت جستجو در تک تک فیلد‌ها به صورت جداگانه جستجو خواهند شد. حتی پارامتر‌های minimum_should_match و operator نیز به ازای هر فیلد اعمال خواهد شد (به عبارتی این دو حالت field-centric یا فیلد-محور هستند! اما اگر بخواهیم term های عبارت جستجو‌شده در فیلد‌های مختلف به صورت ترکیبی تطبیق داده شوند، باید از حالت cross_fields استفاده کرد. این حالت برای فیلد‌هایی که از یک نوع analyzer استفاده کنند قابل اعمال است و مقادیر فیلد‌ها را باهم ترکیب کرده و جستجو را داده‌های ترکیب‌شده از تمامی فیلد‌ها انجام می‌دهد. به عنوان مثال فرض کنید بخواهیم عبارت "احسان علیخانی" را در فیلد‌های first_name و last_name جستجو کرده و انتظار داریم "احسان" در فیلد first_name و "علیخانی" در فیلد last_name تطبیق داده شود. کوئری زیر را درنظر بگیرید:
GET actors/_search
{
  "query": {
    "multi_match" : {
      "query":      "احسان علیخانی",
      "type":       "cross_fields",
      "fields":     [ "first_name", "last_name" ],
      "operator":   "and"
    }
  }
}

شروط کوئری بالا به صورت زیر تفسیر می‌شوند:

به دلیل استفاده از مقدار and برای پارامتر operator، هر دو term باید در حداقل یکی از فیلد‌های first_name یا last_name ظاهر شده باشند( به عبارتی باید هر دو در مقدار ترکیب‌شده‌ی آن‌ها ظاهر شده باشند).

 در حالیکه اگر این کوئری با حالت best_fields یا most_fields و با مقدار and برای پارامتر operator اجرا شود، شروط آن به صورت زیر تفسیر خواهد شد:

 به عبارتی باید هر دو term همزمان در یکی از دو فیلد باهم ظاهر شده باشند (که خلاف انتظار ما برای تطبیق است چون "احسان" در last_name نبوده و "علیخانی" نیز در first_name ظاهر نشده است!)

 

تمرین

ابتدا فایل نمونه داده‌های بخش بلاگ سایت سکان‌آکادمی را از اینجا دانلود کنید. در این فایل اسکریپت دو API وجود دارد که می‌توانید آن‌ها را در محیط dev tools کیبانا کپی کرده و اجرا کنید. اولین API مربوط به ساخت یک ایندکس به نام sokanacademy_sample_data است (فایل create_index.txt) و API دوم یک bulk API است که تعدادی داده در این ایندکس ثبت می‌کند (فایل index_some_data.txt). 

پس از اجرای این دو API شروع به اجرای API های جستجو با کوئری‌های مختلف match کنید. در نتایج خروجی به کلید score_ دقت کرده و سعی کنید به صورت کلی دلیل تفاوت امتیاز نتایج را بررسی کنید. به عنوان پیشنهاد می‌توانید موارد زیر را بررسی کنید:

  • دو کوئری زیر را از جهت عملکرد پارامتر operator مقایسه کنید (همچنین به استفاده از پارامتر fuzziness و غلط املایی در کلمه‌ی laravle دقت کنید!!):
GET sokanacademy_sample_data/_search
{
  "query": {
    "match": {
       "title": {
         "query": "php laravle",
         "fuzziness": "AUTO"
      }
    }
  }
}
GET sokanacademy_sample_data/_search
{
  "query": {
    "match": {
      "title": {
        "query": "php laravle",
        "fuzziness": "AUTO"
        "operator": "and"
      }
    }
  }
}

 

کوئری دوم هیچ نتیجه‌ای نخواهد داشت چراکه در هیچ یک از document ها هر دو کلمه‌ی php و laravel همزمان ظاهر نشده است.

  • کوئری multi_match زیر را اجرا کنید و ضمن درنظر داشتن این نکته که type پیش‌فرض در این کوئری best_fields است، به ترتیبِ نتایج و امتیازی که به آن‌ها داده شده است دقت کنید:
GET sokanacademy_sample_data/_search
{
  "query": {
   "multi_match": {
      "fields": [
        "title",
        "description"
      ],
      "query": "لینوس توروالدز لینوکس"
    }
  }
}

در این کوئری اهمیت بالاتر با document هایی است که هریک از term های جستجو‌شده در یکی از فیلد‌های مشخص شده، بهتر ظاهر شده باشد (تکرار در هر دو فیلد منجر به امتیاز بالاتر نمی‌شود بلکه کیفیت در هریک از فیلد‌ها اهمیت دارد!)

  • دو کوئری multi_match زیر را از نظر عملکرد پارامتر type در آن‌ها و ترتیب و امتیازدهی نتایج بازگشتی با هم مقایسه کنید:
GET sokanacademy_sample_data/_search
{
  "query": {
   "multi_match": {
      "fields": [
        "title",
        "description"
      ],
      "query": "هوش مصنوعی",
      "type": "best_fields"
    }
  }
}
GET sokanacademy_sample_data/_search
{
  "query": {
   "multi_match": {
      "fields": [
        "title",
        "description"
      ],
      "query": "هوش مصنوعی",
      "type": "most_fields"
    }
  }
}

                                                                

در کوئری دوم اهمیت بالاتر با تکرار بیشتر در هریک از فیلد‌ها است (امتیاز نهایی مجموع امتیاز هریک از فیلد‌ها خواهد بود!)

  • دو کوئری match_phrase زیر را اجرا کرده و عملکرد پارامتر slop که بیانگر تعداد کلمات قرار گرفته بین کلماتِ عبارت مورد جستجو است را بررسی کنید (پارامتر slop عملکردی مشابه fuzziness در کوئری match دارد و به طور کلی تعیین کننده‌ی میزان انعطاف‌پذیری انطباق عبارت مورد جستجو است!):
GET sokanacademy_sample_data/_search
{
  "query": {
    "match_phrase": {
      "description": {
        "query": "تحقیقات هوش مصنوعی ",
        "slop": 4
      }
    }
  }
}

GET sokanacademy_sample_data/_search
{
  "query": {
    "match_phrase": {
      "description": {
        "query": "تفاوت vpn و proxy",
        "slop": 1
      }
    }
  }
}
GET sokanacademy_sample_data/_search
{
  "query": {
    "match_phrase": {
       "title": {
         "query": "دولوپر موفق",
         "slop": 0
       }
    }
  }
}

 

در کوئری‌های اول و دوم اگر مقدار slop را کمتر از مقدار تعیین شده در مثال قرار دهید، هیچ document ای تطبیق نخواهد داشت (چراکه در این کوئری‌ها عبارت جستجو شده به صورت تقریبی در داده‌ها ظاهر شده و لازم است تا انعطاف‌پذیری لازم برای تطبیق توسط پارامتر slop بوجود آید اما در کوئری آخر مقدار 0 برای slop منجر به تطبیق یک document خواهد شد چون عبارت "دولوپر موفق" دقیقا به همین شکل در داده‌ها وجود دارد (امتحان کنید!)

اینکه مقدار slop در کوئری match_phrase چه مقداری باشد کاملا متناسب با نیازمندی شما و دقت جستجوی مدنظر تعیین می‌شود. slop بالاتر به معنی امکان وجود فاصله‌های بیشتر بین کلمات عبارت جستجوشده می‌باشد.

دوره در حال تکمیل است ... rocket