کوئری Boolean در دسته‌بندی Compound

کوئری Boolean در دسته‌بندی Compound

پس از اینکه انواع کوئری‌های full-text و term level را بررسی کردیم، به سراغ کوئری‌های compound می‌آییم تا با استفاده از آن‌ها بتوانیم کوئری‌های مختلفی را باهم ترکیب کرده و جستجو‌هایی خلاقانه و پیشرفته‌تر‌ را پیاده‌سازی کنیم.

در این قسمت می‌خواهیم کوئری Boolean در دسته‌بندی کوئری‌های compound را بررسی کنیم. این کوئری امکان ترکیب چند کوئری با منطق بولین (boolean logic) را برای ما فراهم می‌کند. در این کوئری طبق منطق بولین می‌توان 4 نوع قید برای کوئری‌ها تعریف کرد.

انواع قید‌ها در کوئری boolean

  • must: کوئری تعریف‌شده در این قید حتما باید در نتایج ظاهر شده باشد (تطبیق داشته باشد) و همچنین در فرایند امتیازدهی شرکت داده خواهد شد.
  • filter: کوئری تعریف‌شده در این قید حتما باید در نتایج ظاهر شده باشد (تطبیق داشته باشد) اما برخلاف قید must، در فرایند امتیازدهی شرکت داده نمی‌شود. در حقیقت کوئری‌های این قید در filter context اجرا شده به این معنا که امتیاز برای آن‌ها نادیده گرفته می‌شود و همچنین cache خواهند شد.
  • should: کوئری تعریف‌شده در این قید بهتر است (!) که در نتایج ظاهر شده باشد. نتایج تطبیق داده شده در این قید در امتیازدهی مشارکت داده خواهند شد.
  • must_not: کوئری تعریف‌شده در این قید نباید در نتایج ظاهر شده باشد. این کوئری‌ها در filter context اجرا شده بنابراین امتیاز برای آن‌ها نادیده گرفته شده و cache خواهند شد.

نکته مهم: کوئری boolean از رویکرد "هرچه تطبیق بیشتر، کیفیت بالاتر" استفاده می‌کند، به این معنا که امتیاز کوئری‌هایی در حالات should و must که تطبیق پیدا کرده باشند، باهم تجمیع شده و امتیاز نهایی مجموع امتیاز‌ این کوئری‌ها خواهد بود. (شاید بگید اهمیت مساله کجاست! جلوتر مثال‌هایی را بررسی خواهیم کرد که این رویکرد ممکن است اثر سوء داشته و توجه به این نکته اهمیت دارد.)

ساختار کلی یک کوئری boolean به صورت زیر است:

POST index_1/_search
{
  "query": {
    "bool": {
      "must": [
        …
      ],
      "filter": [
        …
      ],
      "must_not": [
        …
      ],
      "should": [
        …
      ]
    }
  }
}

همانطور که در مثال بالا مشخص است، بعد از تعیین نوع کوئری (کلید bool داخل کلید query)، برای هریک از قید‌هایی که می‌توان در کوئری bool تعریف کرد، از کلید متناظر (همنام) آن استفاده کرده و مقدار آن آرایه‌ای از کوئری‌های موردنظر خواهد بود. به عنوان مثال در کوئری زیر از قید‌های should و must_not استفاده شده است:

POST index_1/_search
{
  "query": {
    "bool": {
      "must_not": [
        {
           "range": {
             "age": {"gte": 10, "lte": 20}
           }
        }
      ],
      "should": [
        {"term": {"tags": "نخبه"}},
        {"match": {"title": "سکان آکادمی"}}
      ],
      "minimum_should_match": 1,
      "boost": 1
    }
  }
}

نکته: در صورتی که بخواهیم فقط یک کوئری در قید مدنظر نظر قرار دهیم، می‌توانیم به جای استفاده از آرایه، شی کوئری را مستقیم به عنوان مقدار درنظر بگیریم. برای مثال کوئری بالا به شکل زیر نیز صحیح است (به مقدار قید must_not توجه کنید!):

POST index_1/_search
{
  "query": {
    "bool": {
      "must_not": {
        "range": {
          "age": {"gte": 10, "lte": 20}
        }
      },
      "should": [
        {"term": {"tags": "نخبه"}},
        {"match": {"title": "سکان آکادمی"}}
      ],
      "minimum_should_match": 1,
      "boost": 1
    }
  }
}
}

پارامتر‌های کوئری boolean

  • minimum_should_match: این پارامتر حداقل تعداد تطبیق مورد نیاز در کوئری‌های قید should را تعیین می‌کند. اگر کوئری boolean شامل حداقل یک کوئری در قید should باشد و هیچ یک از قید‌های must و filter تعیین نشده باشند، به صورت پیش‌فرض مقدار این پارامتر 1 خواهد بود و در غیر این صورت مقدار پیش‌فرض آن 0 درنظر گرفته خواهد شد.
  • boost: تعیین ضریب امتیاز در نتایج کوئری boolean

استفاده از نام برای کوئری‌ها (named queries)

در اولین سطح از کوئری‌های تعریف شده در قید‌های کوئری boolean، امکان استفاده از پارامتر name_ برای نامگذاری کوئری‌ها وجود دارد. با این کار در نتیجه‌ی کوئری boolean، کلیدی به نام matched_queries  وجود خواهد داشت که مشخص می‌کند هر document با کدام کوئری‌ها تطبیق داشته است. به عنوان مثال کوئری زیر را درنظر بگیرید:

GET index_1/_search
{
  "query": {
    "bool": {
      "should": [
        {
          "match": {
            "name": { 
              "query": "mahdi",
              "_name": "firstname_match"
            } 
          }
        },
        {
          "match": {
            "last_name": { 
              "query": "moazami",
              "_name": "lastname_match"
            } 
          }
        }
      ],
      "filter": {
        "terms": {
          "position": ["developer", "searchEngineer"],
          "_name": "position_filter"
        }
      }
    }
  }
}

برای کوئری اول در قید should نام "firstname_match" و برای کوئری دوم نام "lastname_match" درنظر گرفته شده است و در نتیجه‌ی کوئری نیز برای هر نتیجه مشخص خواهد شد با کدام یک از کوئری‌ها تطبیق داشته است.

مراقب رویکرد کوئری boolean باشید!

جلوتر گفته شد که رویکرد کوئری boolean به صورت "هرچه تطبیق بیشتر، کیفیت بالاتر" است. برای یک کوئری مشابه مثال زیر این رویکرد کاملا مناسب خواهد بود:

GET contents/_search
{
  "query": {
    "bool": {
      "should": [
        {
         "terms_set": {
           "tags": {
             "terms": [
                "دیسک",
                "سخت‌افزار",
               "disk",
               "I/O"
              ],
             "minimum_should_match_script": {
               "source": "(int)(params.num_terms / 2)"
              }
            }
          }
        },
        {
         "multi_match": {
            "query": "array disk RAID I/O ورودی خروجی",
           "fields": [
             "title^2",
             "description",
             "text"
            ],
           "type": "most_fields",
           "minimum_should_match": "50%"
          }
        }
      ],
     "must_not": [
        {
         "exists": {
           "field": "hidden"
          }
        }
      ]
    }
  }
}

کوئری بالا در تلاش است تا مطالبی را مرتبط با مبحث دیسک‌های raid و سخت‌افزار‌های ورودی و خروجی پیدا کند. این کوئری حداقل نیمی از کلمات را در فیلد tags جستجو می‌کند (کوئری terms_set) و در عین حال نیز نیمی از یک سری واژگان را در فیلد‌های title ،description و text تطبیق خواهد داد (کوئری multi_match). همچنین دقت کنید چون قید‌های filter و  must در کوئری استفاده نشده است، پس مقدار پیش‌فرض minimum_should_match نیز برابر 1 است و این یعنی حداقل یکی از کوئری‌های قید should باید تطبیق داشته باشد. با این ترتیب مطالبی که هم در کوئری terms_set و هم کوئری multi_match تطبیق پیدا کنند امتیازی بیشتری نسبت به مواردی خواهند داشت که تنها با یکی از کوئری‌های قید should تطبیق پیدا کنند. در این سناریو این رویکرد برای ما کاملا مناسب بوده و مطابق خواسته ما خواهد بود! (مطلبی که هم تگ مربوط هم کلمات کلیدی مرتبط دارد قطعا امتیاز بالاتری باید داشته باشد!)

حال کوئری دیگری را بررسی می‌کنیم. مثال زیر را درنظر بگیرید:

GET contents/_search
{
  "query": {
    "bool": {
      "should": [
        {
          "terms_set": {
           "tags": {
             "terms": [
                "دیسک",
                "سخت‌افزار",
               "disk",
               "I/O"
              ],
             "minimum_should_match_script": {
               "source": "(int)(params.num_terms / 2)"
              }
            }
          }
        },
        {
         "match": {
           "title": {
             "query": "RAID disk",
              "minimum_should_match": "2",
             "boost": 3
            }
          }
        },
        {
         "multi_match": {
            "query": "array disk RAID I/O ورودی خروجی",
           "fields": [
             "title^2",
             "description",
             "text"
            ],
           "type": "most_fields",
           "minimum_should_match": "70%"
          }
        }
      ],
     "must_not": [
        {
         "exists": {
            "field": "hidden"
          }
        }
      ]
    }
  }
}

در مثال بالا در کنار کوئری multi_match، یک کوئری match روی فیلد title با دو کلمه‌ی "disk" و "RAID" نیز اضافه کردیم و مقدار boost این کوئری را برابر 3 گذاشتیم. همچنین در کوئری multi_match مقدار minimum_should_match را به 70% تغییر دادیم تا سخت‌گیرانه‌تر در خصوص تطبیق فیلد‌های title ،description و text عمل کرده باشیم. در واقع هدف این تغییرات این بوده است که اگر document ای در فیلد title هر دو کلمه‌ی "disk" و "RAID" را داشت امتیازی 3 برابر نسبت به document ای بگیرد که تطبیق 70% در گستره‌ی بزرگتری از واژگان در فیلد‌های title ،description و text  دارد. (اهمیت title به صورت تکی در کلمات "disk" و "RAID"  بالاتر از ترکیب تمامی فیلد‌ها است!)

این روش با توجه به رویکرد کوئری boolean، باعث خواهد شد تا document ای که کلمات "RAID" و "disk" را در فیلد title خود داراست، در هر دو کوئری مذکور امتیاز کسب کند و در نتیجه مجموع امتیاز هر دو کوئری در امتیاز نهایی آن اثر داشته باشد (در حالیکه امتیاز کوئری multi_match برای این document کافی بوده و تجمیع با امتیاز کوئری match باعث افزایش نامعقول (تکراری) امتیاز است _ گویی به تطبیق کلمات "RAID" و "disk" دو بار امتیاز داده باشیم _ که می‌تواند در ترتیب نمایش نتایج اثر نامناسب داشته باشد!)

برای سناریو مطرح شده‌ی دوم باید از روشی دیگر استفاده شود که در جلسه آینده ضمن معرفی کوئری disjunction_max به آن خواهیم پرداخت.