ما در جلسه قبل در رابطه با multi-key index ها صحبت کردیم اما نوع خاصی از multi-key index ها به نام text index (ایندکس رشته ای) وجود دارد که هنوز آن را بررسی نکرده ایم. فرض کنید یک رشته تصادفی را داشته باشیم. مثلا:
This product is a must-buy for all fans of modern fiction!
اگر یادتان باشد من در چند جلسه قبل گفته بودم که برای جست و جوی یک رشته در پایگاه داده می توانیم از یکی از اپراتور های evaluation به نام regex$ استفاده کنیم اما این اپراتور بسیار کُند عمل می کند و بهترین راه حل ممکن نیست. من در همان جلسه توضیح دادم که regex مخفف regular expression (به معنی «عبارات با قاعده») است و یک بحث کاملا جداگانه از زبان برنامه نویسی شما است. ما در زبان های مختلف PHP و Javascript و Python و تقریبا تمام زبان های دنیا regex ها را داریم و از آن ها استفاده می کنیم. مسئله اینجاست که regex ها می توانند بسیار پیچیده شوند و به دوره خودشان نیاز دارند اما ما برای ساده نگه داشتن بحث، از ساده ترین حالت regex ها استفاده می کنیم که یک کلمه بین دو علامت / است. مثالی که در آن جلسه زدم به شکل زیر بود:
db.movies.find({summary: {$regex: /remote island in the Pacific/}}).pretty()
ایندکس های رشته ای دو کار مهم را برای ما انجام می دهند:
بنابراین رشته ای که اول برایتان نوشتم به شکل زیر در می آید:
product must buy fans modern fiction
بهتر است این نوع از ایندکس ها را در عمل ببینیم بنابراین من یک کالکشن جدید به نام products را می سازم و دو سند جدید را در آن قرار می دهم:
db.products.insertMany([{title: "A Book", description: "This is an awesome book about a young artist!"}, {title: "Red T-Shirt", description: "This T-Shirt is red and it's pretty awesome!"}])
برای اینکه این کالکشن را بهتر ببینیم از دستور find استفاده می کنم:
db.products.find().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!"
همه چیز مرتب است بنابراین اولین قدم ما ساخت یک index برای description است اما نباید مثل همیشه به شیوه عادی خودمان 1 یا 1- بگذارید:
db.products.createIndex({description: 1})
اگر این کوئری را اجرا کنیم، یک ایندکس از نوع single field index خواهیم داشت که یعنی فقط می توانیم در جست و جوی دقیق رشته از آن استفاده کنیم. یعنی کوئری زیر از index scan استفاده خواهد کرد:
db.products.explain().find({description: "This T-Shirt is red and it's pretty awesome!"})
یعنی همه چیز دقیقا باید با کل رشته برابر باشد اما ما نمی خواهیم چنین کاری را انجام بدهیم. اگر شما چنین ایندکسی را برای کالکشن خود تعریف کرده اید می توانید با کوئری زیر آن را حذف کنید:
db.products.dropIndex({description: 1})
برای ایجاد ایندکس های رشته ای باید از کلیدواژه text استفاده کنیم:
db.products.createIndex({description: "text"})
نتیجه ای که این کوئری برمی گرداند کاملا عادی است و با اینکدس های دیگر تفاوتی ندارد:
"createdCollectionAutomatically" : false, "numIndexesBefore" : 1, "numIndexesAfter" : 2, "ok" : 1
از این به بعد برای کوئری زدن و جست و جو برای قسمتی از متن بالا می توانیم از اپراتور text$ استفاده کنیم:
db.products.find({$text: {$search: "awesome"}}).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!"
در description هر دو سند، کلمه awesome وجود دارد بنابراین هر دو برگردانده شده اند. در ضمن احتمالا خودتان حدس زده اید که این کوئری case-insensitive است بنابراین بزرگی و کوچکی حروف انگلیسی برایش اهمیتی ندارد.
سوال: چرا هیچ جایی از کوئری بالا، فیلد description را مشخص نکرده ایم؟ MongoDB از کجا می داند که درون کدام فیلد را جست و جو کند؟
پاسخ: ایندکس های رشته ای از نظر سرعت، هزینه بر هستند. تصور کنید که یک جمله 10 کلمه ای به حدود 10 ایندکس مختلف تبدیل می شود! به همین دلیل MongoDB به شما اجازه می دهد که در هر collection فقط به یک فیلد text index بدهید و اجازه ندارید برای چندین فیلد مختلف ایندکس های رشته ای تعیین کنید. ما آن را برای description فعال کردیم بنابراین تا آخر برای این فیلد (منظور تمام فیلد های Description است) فعال خواهد بود.
همچنین باید توجه شما را به مسئله ای خاص جلب کنم. به کوئری زیر توجه کنید:
db.products.find({$text: {$search: "red book"}}).pretty()
ما در یک سند کلمه red و در سند دیگر کلمه book را داریم اما هر دو کلمه red و book در یک سند وجود ندارند. به نظر شما با اجرای کوئری بالا چه اتفاقی می افتد؟ درست است! هر دو سند ما برگردانده می شوند. در چنین حالتی red book به عنوان یک رشته خاص در نظر گرفته نمی شود بلکه مجموعه ای از کلید واژه ها است. اگر بخواهید واقعا به دنبال رشته خاصی باشید باید آن رشته را درون double quotation بگذارید:
db.products.find({$text: {$search: "\"red book\""}}).pretty()
ما برای پاس دادن مقدار به اپراتور search$ باید double quotation داشته باشیم و حالا یک جفت double quotation دیگر نیز اضافه می شوند بنابراین باید آن ها را escape کنیم. یعنی علامت \ را قبل از آن ها قرار بدهیم تا به عنوان کد به آن ها نگاه نشود. با اجرای کوئری بالا هیچ نتیجه ای نمی گیریم چرا که هیچ کدام از اسناد ما رشته red book را درون خود ندارد.
این روش بسیار سریع تر از regex است بنابراین سعی کنید تا حد امکان از این روش به جای regex استفاده کنید.
در این قسمت، به پرسشهای تخصصی شما دربارهی محتوای مقاله پاسخ داده نمیشود. سوالات خود را اینجا بپرسید.