کوئری‌های دسته‌بندی Term level

کوئری‌های دسته‌بندی Term level

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

نکته: برعکس کوئری‌های full-text، در کوئری‌های term level متن مورد جستجو تجزیه و تحلیل (analyze) نمی‌شود و دقیقا طبق همان داده‌ای که در source فیلد موردنظر ذخیره شده است، تطبیق داده می‌شود. البته که با تعیین پارامتر normalizer در mapping فیلد مورد نظر، term مورد جستجو در کوئری term level نیز توسط normalizer تعیین‌شده‌ی فیلد، نرمال‌سازی خواهد شد.

یادآوری: تفاوت normalizer و analyzer در این است که یک analyzer از سه بخش tokenizer، token filter و character filter تشکیل می‌شود اما یک normalizer تنها از بخش‌های token filter و character filter تشکیل شده و به عبارتی فقط مرحله‌ی normalization از فرایند text analysis را انجام می‌دهد و هرگز متن را تجزیه(Tokenize) نخواهد کرد.

با توجه به نکات بالا مثال زیر را در نظر بگیرید. ابتدا فیلدی به نام code در یک ایندکس دلخواه تعریف کرده و پارامتر normalizer آن را برابر lowercase که مقداری built-in است، قرار می‌دهیم (امکان تعریف normalizer دلخواه به همان روشی که analyzer دلخواه تعریف می‌شود، نیز وجود دارد!). سپس مقدار "emergency" را در یک document از این ایندکس ذخیره می‌کنیم:

PUT some_index
{
  "mappings": {
    "properties": {
     "code": {
       "type": "keyword",
       "normalizer": "lowercase"
     }
    }
  }
}

PUT some_index/_doc/1
{
  "code": "emergency"
}

حال برای مثال در کوئری زیر که جزو کوئری‌های term level است (جلوتر با جزییات آن‌ها آشنا خواهیم شد!) اگر مقدار "Emergency" جستجو شود، به دلیل نرمال‌سازی توسط normalizer فیلد document ،code مثال زده شده در نتیجه‌ی کوئری زیر تطبیق پیدا خواهد کرد:

GET some_index/_search
{
  "query": {
    "term": {
     "code": "Emergency"
    }
  }
}

انواع کوئری‌های Term level

  • exists query: این کوئری document هایی را برمی‌گرداند که شامل مقداری ایندکس شده در یک فیلد مشخص باشند. مقادیر مختلفی ممکن است به منزله‌ی عدم ایندکس شدن مقدار در فیلد مدنظر شناخته شود که عبارتند از:
    • مقدار فیلد مدنظر null یا [ ] باشد و یا فیلد مدنظر در source داده وجود نداشته باشد.
    • مقدار پارامتر index در mapping فیلد مدنظر false باشد. (مقدار فیلد ایندکس نشود!)
    • طول مقدار فیلد مدنظر بزرگتر از پارامتر ignore_above در تنظیمات mapping بوده و یا مقدار فیلد بدساختار(malformed) بوده و پارامتر ignore_malformed فعال باشد.

تنها پارامتر این کوئری، پارامتر field است که نام فیلد موردنظر در آن تعیین می‌شود. به طور مثال:

GET some_index/_search
{
  "query": {
    "exists": {
     "field": "username"
    }
  }
}

توجه: مقادیر "" یا [ "null, "something ] به منزله‌ی عدم وجود مقدار درنظر گرفته نخواهند شد.

نکته: چنانچه بخواهیم عدم وجود مقدار در یک فیلد را با این کوئری جستجو کنیم، باید از کوئری exists داخل کوئری bool که از نوع compound query است، استفاده شود (جزییات کوئری bool را در ادامه‌ی این فصل بررسی خواهیم کرد). برای مثال:

GET some_index/_search
{
  "query": {
    "bool": {
     "must_not": {
       "exists": {
         "field": "user.id"
       }
     }
    }
  }
}
  • document :fuzzy query هایی را تطبیق می‌دهد که شامل یک term مشابه با term جستجو‌شده باشند. کارکرد این کوئری شبیه به کارکرد پارامتر fuzziness در کوئری‌های match می‌باشد. این کوئری ویرایش‌هایی از جمله حذف یا اضافه کردن یک کاراکتر، جابجایی دو کاراکتر مجاور هم و یا تغییر یک کاراکتر را روی term جستجو‌شده انجام داده و حالات ممکن را با term های اسناد مطابقت می‌دهد. پارامتر‌های این کوئری عبارتند از:
    • value: مقدار مورد جستجو
    • fuzziness: تنظیمات میزان مجاز ویرایش کاراکتر‌ها. جزییات آن مشابه پارامتر fuzziness در کوئری‌های match است و می‌توانید از اینجا نیز روش آن را مطالعه کنید.
    • prefix_length: تعداد کاراکتر‌های ابتدایی term مورد جستجو که بدون تغییر باقی بمانند. مقدار پیش‌فرض آن 0 است.
    • transpositions: تعیین می‌کند آیا جابجایی حروف مجاور جزو ویرایش‌ها باشد یا خیر. پیش‌فرض مقدار true دارد.

مثالی از کوئری fuzzy روی فیلد tag (به غلط املایی "سکان آکاذمی" توجه کنید!):

GET some_index/_search
{
  "query": {
    "fuzzy": {
     "tag": {
       "value": "سکان آکاذمی",
       "fuzziness": "AUTO:3,5"
     }
    }
  }
}
  • document :ids query ها را بر اساس شناسه یا id آن‌ها جستجو می‌کند. شناسه‌ی اسناد در فیلد id_ آن‌ها ثبت می‌شود. تنها پارامتر این کوئری، پارامتر values است که آرایه‌ای از شناسه‌ها می‌باشد:
GET some_index/_search
{
  "query": {
    "ids": {
     "values": ["100", "4", " ZS7YkH4BiU0C6-4H5brq"]
    }
  }
}
  • document :prefix query هایی را برمی‌گرداند که شامل مقدار پیشوند تعیین شده در term های فیلد مدنظر باشند. پارامتر‌های این کوئری عبارتند از:
    • value: مقدار پیشوند موردنظر
    • case_insensitive: این پارامتر از نسخه‌ی 7.10 به بعد اضافه شده است و تعیین می‌کند تطبیق پیشوند به بزرگی و کوچکی حروف حساس نباشد. پیش‌فرض مقدار آن false بوده و در این صورت حساسیت به بزرگی و کوچکی حروف وابسته به mapping فیلد و normalizer آن خواهد بود!

به طور مثال کوئری زیر، تمامی فیلد‌های username را تطبیق می‌دهد که با عبارت "moaz" شروع شده باشند:

GET some_index/_search
{
  "query": {
    "prefix": {
     "username": {
       "value": "moaz"
     }
    }
  }
}
  • document :range query هایی را برمی‌گرداند که فیلدی از آن شامل مقداری در یک بازه‌ی تعیین شده باشند. از این کوئری برای مقایسه مقادیر تاریخ و عددی استفاده می‌شود و استفاده از آن برای سایر انواع داده توصیه نمی‌شود! پارامتر‌های این کوئری عبارتند از :
    • gt: مقدار موردنظر برای عملگر "بزرگتر از"
    • gte: مقدار موردنظر برای عملگر "بزرگتر یا مساوی با"
    • lt: مقدار موردنظر برای عملگر "کوچکتر از"
    • lte: مقدار موردنظر برای عملگر "کوچکتر یا مساوی با"
    • format: فرمت موردنظر برای تبدیل مقادیر تاریخ در کوئری. به صورت پیش‌فرض Elasticsearch از پارامتر fromat تعیین شده در mapping فیلد مورد جستجو استفاده خواهد کرد.
    • relation: تعیین می‌کند شیوه‌ی تطبیق کوئری برای فیلد‌های از نوع داده‌ی range چگونه باشد. مقادیر قابل قبول برای آن عبارتند از:
      • INTERSECTS: مقدار پیش‌فرض بوده و document هایی را تطبیق می‌دهد که مقدار فیلد آن‌ها با بازه‌ی تعیین شده در کوئری یکدیگر را قطع کنند. برای مثال اگر مقدار فیلد [ gte:10, lt:20 ]  بوده و بازه‌ی کوئری [ gt:5, lt:15 ] باشد، تطبیق خواهد داشت.
      • document  :CONTAINS هایی را تطبیق می‌دهد که مقدار فیلد آن‌ها کاملا بازه‌ی تعیین شده در کوئری را شامل شود برای مثال اگر مقدار فیلد [ gt:2022-01-20, lt:2022-01-30 ] بوده و بازه‌ی کوئری [ gt:2022-01-24, lt:2022-01-26 ] باشد، تطبیق خواهد داشت.
      • WITHIN: برعکس CONTAINS عمل کرده و document هایی را تطبیق می‌دهد که مقدار فیلد آن‌ها کاملا داخل بازه‌ی تعیین شده در کوئری قرار گیرد.
    • time_zone: مقدار دلخواه برای تبدیل مقادیر تاریخ در کوئری به فرمت UTC. برای مثال اگر تاریخ نوشته شده در کوئری بر اساس time zone ایران باشد، چون Elasticsearch مقادیر تاریخ را در فرمت UTC ذخیره می‌کند، مقدار پارامتر time_zone باید "03:30+" باشد تا مقایسه طبق ساعت ایران به درستی انجام شود (ساعت ایران نسبت به UTC، سه ساعت و سی دقیقه جلوتر است!).
    • boost: ضریب امتیازدهی نتایج کوئری.

نکته: برای مقایسه مقادیر از نوع تاریخ می‌توان از عملگر‌های ریاضیاتی برای تاریخ بهره برد. برای مثال مقدار "now-2M" توسط Elasticsearch به تاریخ 2 ماه گذشته نسبت به تاریخ روز تفسیر می‌شود. برای مثال کوئری زیر document هایی از 7 روز گذشته تا 1 ساعت قبل را بر اساس مقدار فیلد timestamp جستجو می‌کند:

GET some_index/_search
{
  "query": {
    "range": {
      "timestamp": {
        "gte": "now-7d",
        "lt": "now-1h"
      }
    }
  }
}
  • document :regexp query هایی را برمی‌گرداند که شامل مقداری تطبیق داده شده با الگوی regular expression تعیین شده در کوئری باشند. برای مشاهده‌ی قواعد و عملگر‌های معتبر در الگوی regular expression در Elasticsearch می‌توانید اینجا را مطالعه کنید. پارامتر‌های کوئری عبارتند از:
    • value: مقدار الگوی regular expression برای تطبیق term ها.
    • flags: برای تعیین عملگرهای خاص جهت استفاده در الگو. جزییات مقادیر قابل قبول در لینک مرجع بالا توضیح داده شده است.
    • case_insensitive: این پارامتر از نسخه‌ی 7.10 به بعد اضافه شده است و تعیین می‌کند تطبیق پیشوند به بزرگی و کوچکی حروف حساس نباشد. پیش‌فرض مقدار آن false بوده و در این صورت حساسیت به بزرگی و کوچکی حروف وابسته به mapping فیلد و normalizer آن خواهد بود!
  • document :term query هایی را برمی‌گرداند که شامل یک term با مقدار دقیق تعیین شده در کوئری باشند. پارامتر‌های این کوئری boost ،value و case_insensitive می‌باشد. به عنوان مثال برای جستجوی کلمه‌ی “sokan academy” در فیلد title با ضریب 1.5 برای امتیاز، کوئری زیر را درنظر بگیرید:
GET some_index/_search
{
  "query": {
    "term": {
      "title": {
        "value": "sokan academy",
        "boost": 1.5
      }
    }
  }
}
  • terms query: این کوئری مشابه کوئری term بوده اما برای تطبیق چندین مقدار استفاده می‌شود. برای مثال:
GET some_index/_search
{
  "query": {
    "terms": {
      "title": {
        "value": ["sokan academy", "RadioFullStack"],
        "boost": 1.5
      }
    }
  }
}
  • terms_set query: مشابه کوئری terms برای تطبیق چندین مقدار عمل کرده و امکان تعیین حداقل تعداد مورد نیاز برای تطبیق نیز در این کوئری ممکن است. پارامتر‌های آن عبارتند از:
    • terms: آرایه‌ای از مقادیر مورد جستجو
    • minimum_should_match_field: نام فیلدی از نوع عددی که تعداد حداقل تطبیق مورد نیاز در آن ذخیره شده باشد. به عنوان مثال فرض کنید document زیر در یک ایندکس ثبت شود:
PUT /job-candidates/_doc/1
{
  "name": "Jane Smith",
  "programming_languages": ["c#", "java", "php"],
  "required_count": 2
}

در فیلد required_count، تعداد مورد نیاز برای تطبیق مقادیر فیلد programming_languages ذخیره شده است (به این منظور که در این document حداقل 2 مورد از مقادیر فیلد programming_languages باید با مقادیر کوئری موردنظر تطبیق داشته باشند!). بنابراین می‌توان از کوئری terms_set به صورت زیر استفاده کرد:

GET job-candidates/_search
{
  "query": {
    "terms_set": {
      "programming_languages": {
        "terms": ["c++", "java", "php"],
        "minimum_should_match_field": "required_count"
      }
    }
  }
}
  • minimum_should_match_script: پارامتر دیگری که به وسیله‌ی آن می‌توان حداقل تعداد تطبیق مورد نیاز را با استفاده از اسکریپت painless تعیین کرد. به عنوان مثال در کوئری مثال قبل:
GET job-candidates/_search
{
  "query": {
    "terms_set": {
      "programming_languages": {
        "terms": ["c++", "java", "php"],
        "minimum_should_match_script": {
          "source": "Math.min(params.num_terms, doc['required_matches'].value)"
        }
      }
    }
  }
}

در کوئری بالا، از اسکریپت زیر برای تعیین تعداد مورد نیاز تطبیق استفاده شده است:

Math.min(params.num_terms, doc['required_count'].value)

طبق این اسکریپت حداقل مقدار بین تعداد term ها در کوئری و مقدار فیلد required_count به عنوان حداقل تعداد مورد نیاز تعیین خواهد شد.

  • wildcard query: این کوئری document هایی را برمی‌گرداند که مقادیر term های آن‌ها با مقدار تعیین‌شده توسط الگوی wildcard تطبیق داشته باشند. تنها عملگر‌های قابل استفاده در الگوی wildcard، عملگر *، برای تطبیق هیچ یا چند کاراکتر و عملگر ? برای تطبیق یک کاراکتر می‌باشند. پارامتر‌های این کوئری boost ،value و case_insensitive هستند که قبل‌تر نیز توضیح داده شده‌اند. به عنوان مثالی برای جستجوی مقادیری در فیلد userId که با عبارت "mo" شروع شده و با عبارت "mi" تمام شوند، کوئری زیر را درنظر بگیرید:
GET some_index/_search
{
  "query": {
    "wildcard": {
      "userId": {
        "value": "mo*mi",
        "boost": 1.0
      }
    }
  }
}

توجه: برخی از کوئری‌های معرفی شده در دسته‌بندی terms level، به دلیل نوع عملکردی که دارند از جمله کوئری‌های هزینه‌بر یا expensive به حساب می‌آیند که در صورتی که پارامتر search.allow_expensive_queries در ایندکس موردنظر غیرفعال باشد، امکان اجرای این کوئری‌ها وجود ندارد. مقدار پیش‌فرض این تنظیم true است و کوئری‌های هزینه‌بر شامل موارد زیر هستند:

  • fuzzy query
  • regexp query
  • prefix query
  • wildcard query
  • range query روی فیلد‌های از نوع text یا keyword