ما در قسمت قبل با مفهوم text indexes آشنا شدیم اما نکاتی از آن ها هنوز باقی مانده است که باید مرور کنیم. من این جلسه را با یک کوئری ساده شروع می کنم:
db.products.find({$text: {$search: "awesome t-shirt"}}).pretty()
ما می دانیم که با اجرای این کوئری، هر دو سند موجود در کالکشن برگردانده می شوند:
"_id" : ObjectId("5ebe3d68c8ead79df676bcfe"), "title" : "A Book", "description" : "This is an awesome book about a young artist!" "_id" : ObjectId("5ebe3d68c8ead79df676bcff"), "title" : "Red T-Shirt", "description" : "This T-Shirt is red and it's pretty awesome!"
اگر به این نتایج نگاه کنیم، کلمه t-shirt را در نتیجه دوم برگردانده شده پیدا می کنیم. در همان نتیجه کلمه awesome را نیز داریم. بنابراین هم t-shirt و هم awesome در نتیجه دوم وجود دارد در حالی که نتیجه اول فقط awesome را دارد. با یک نگاه ساده برای هر انسانی مشخص است که نتیجه دوم باید به جای نتیجه اول باشد چرا که کلمات بیشتری را دارد که مطابق کوئری ما است اما MongoDB به صورت پیش فرض به این مسئله اهمیتی نمی دهد.
راه حل چیست؟ MongoDB در برخورد با ایندکس های رشته ای و جست و جوی آن ها یک مقدار خاص به نام textScore دارد (به معنی «نمره متن») که شدت مطابقت رشته پیدا شده با کوئری را نشان می دهد. بنابراین اولین مرحله دسترسی به این خصوصیت است که من با projection انجام می دهم:
db.products.find({$text: {$search: "awesome t-shirt"}}, {score: {$meta: "textScore"}}).pretty()
در اینجا با استفاده از اپراتور meta$ می توانیم به metadata (اطلاعاتی در مورد کوئری و داده های برگردانده شده) دست پیدا کنیم که یکی از آن ها textScore است. با اجرای کوئری بالا نتیجه زیر را می گیریم:
"_id" : ObjectId("5ebe3d68c8ead79df676bcfe"), "title" : "A Book", "description" : "This is an awesome book about a young artist!", "score" : 0.625 "_id" : ObjectId("5ebe3d68c8ead79df676bcff"), "title" : "Red T-Shirt", "description" : "This T-Shirt is red and it's pretty awesome!", "score" : 1.7999999999999998
همانطور که می بینید امتیاز (score) هر نتیجه را نیز برگردانده ایم و همچنین جای نتیجه ها عوض شده است (نتیجه ای که امتیاز بالاتری داشته است، بالاتر قرار گرفته است). در ضمن برای اینکه مطمئن بشویم که نتیجه دارای Score بالاتر، به عنوان نتیجه اول برگردانده می شود، می توانیم از دستور Sort نیز استفاده کنیم:
db.products.find({$text: {$search: "awesome t-shirt"}}, {score: {$meta: "textScore"}}).sort({score: {$meta: "textScore"}}).pretty()
با این کار مطمئن می شویم که داده ها به ترتیب textScore مرتب می شوند.
همانطور که قبلا هم گفتم، ما فقط می توانیم به ازای هر کالکشن یک text index داشته باشیم. بیایید ایندکس های کالکشن خود را نگاه کنیم:
db.products.getIndexes()
با اجرای کد بالا پیام زیر را می گیریم:
{ "v" : 2, "key" : { "_id" : 1 }, "name" : "_id_", "ns" : "contactData.products" }, { "v" : 2, "key" : { "_fts" : "text", "_ftsx" : 1 }, "name" : "description_text", "ns" : "contactData.products", "weights" : { "description" : 1 }, "default_language" : "english", "language_override" : "language", "textIndexVersion" : 3 }
بنابراین دو ایندکس داریم که یکی از آنها textIndex است. البته default_language (زبان پیش فرض) روی انگلیسی است اما می توانیم بعدا آن را تغییر دهیم. مسئله اینجاست که مقدار title برای هر سند نیز یک رشته یا text است اما آیا می توانیم برای آن هم textIndex تعریف کنیم؟ من کوئری زیر را امتحان می کنم:
db.products.createIndex({title: "text"})
اجرای این کوئری باعث یک خطا از نوع "IndexOptionsConflict" می شود که به طور خلاصه می گوید، MongoDB قصد ساختن یک text index داشته است اما یک text index از قبل پیدا شده است بنابراین این دو با هم تضاد دارند چرا که برای هر کالکشن فقط یک text index مجاز است. سوال ما این است که چطور می توانیم title را نیز درون text index خود قرار بدهیم؟
اگر به خوبی به مشکل ما دقت کنید، راه حل به ذهنتان می رسد. ما نمی توانیم 2 ایندکس داشته باشیم اما می توانیم دو مقدار را در یک ایندکس ترکیب کنیم! برای اینکه این کار را انجام بدهیم، باید text index قبلی را حذف کنیم اما حذف text index ها مانند موارد قبل نیست و نمی توانیم به شکل زیر عمل کنیم:
db.products.dropIndex({description: "text"})
برای حذف آن ها حتما باید از نام ایندکس استفاده کنید. نام ایندکس با دستور getIndexes نمایش داده می شود. من بالاتر این دستور را اجرا کرده ام و می دانم که مقدار آن به شکل زیر است:
"name" : "description_text
بنابراین برای حذف آن می گوییم:
db.products.dropIndex("description_text")
با این کار ایندکس ما حذف می شود (می توانید با دستور getIndexes چک کنید). در مرحله بعد به جای پاس دادن یک مقدار به createIndex می توانیم چندین مقدار را به آن بدهیم. به طور مثال:
db.products.createIndex({title: "text", description: "text"})
با این کار هر دو فیلد های description و title در یک text index ترکیب می شوند. برای تست کردن این ایندکس بهتر است یک محصول جدید را به کالکشن خود اضافه کنیم:
db.products.insertOne({title: "A Ship", description: "Floats perfectly!"})
در این سند یک title ساده داریم که کلمه Ship را دارد اما این کلمه در Description موجود نیست بنابراین اگر من ship را جست و جو کنم، و این سند بیاید مطمئن می شویم که مقدار title نیز در text index ما موجود است.
db.products.find({$text: {$search: "ship"}}).pretty()
با اجرای کوئری بالا نتیجه زیر را می گیریم:
"_id" : ObjectId("5ebe80dac8ead79df676bd00"), "title" : "A Ship", "description" : "Floats perfectly!"
بنابراین مطمئن هستیم که ایندکس ما به درستی کار می کند!
در این قسمت، به پرسشهای تخصصی شما دربارهی محتوای مقاله پاسخ داده نمیشود. سوالات خود را اینجا بپرسید.