پستگرسکیوال (PostgreSQL): فراتر از یک پایگاه داده رابطهای، NoSQL و موتور جستجوی قدرتمند (Full-Text Search)
پستگرسکیوال، که اغلب به آن “postgres” گفته میشود، یک سیستم مدیریت پایگاه داده رابطهای شیء-گرا (ORDBMS) قدرتمند و منبعباز است. اگرچه در درجه اول به خاطر قابلیتهای رابطهای پیشرفتهاش شناخته میشود، اما PostgreSQL مدتهاست که از طریق افزونهها و ویژگیهای داخلی خود از قابلیتهای NoSQL و جستجوی متن کامل (Full-Text Search) نیز پشتیبانی میکند. در این مقاله به بررسی چگونگی استفاده از PostgreSQL به عنوان یک پایگاه داده NoSQL و یک موتور جستجوی کارآمد میپردازیم. با تمرکز بر نوع داده `JSONB` و قابلیتهای جستجوی متن کامل داخلی، نشان میدهیم که چگونه میتوان از PostgreSQL برای سناریوهایی فراتر از پایگاه دادههای رابطهای سنتی بهره برد.
قابلیتهای NoSQL پستگرسکیوال: نوع داده JSONB
یکی از قدرتمندترین ویژگیهایی که PostgreSQL را به یک انتخاب عالی برای ذخیرهسازی NoSQL تبدیل میکند، نوع داده `JSONB` است. برخلاف نوع داده `JSON` که فقط متن را ذخیره میکند، `JSONB` دادههای `JSON` را به صورت باینری ذخیره میکند. این فرمت باینری چندین مزیت کلیدی دارد:
* **عملکرد:** دسترسی و پردازش دادهها سریعتر است، زیرا نیازی به تجزیه مجدد رشته `JSON` در هر بار استفاده نیست.
* **فشردهسازی:** `JSONB` فشردهتر است و فضای کمتری اشغال میکند.
* **اعتبارسنجی:** دادهها در زمان ورود به پایگاه داده از نظر صحت ساختار `JSON` اعتبارسنجی میشوند.
برای مثال، فرض کنید میخواهیم اطلاعات کاربران را که ساختار انعطافپذیری دارند، ذخیره کنیم. میتوانیم یک جدول با ستون `JSONB` ایجاد کنیم:
CREATE TABLE users (
id SERIAL PRIMARY KEY,
user_data JSONB
);
حالا میتوانیم دادههای `JSON` را به راحتی در این ستون وارد کنیم:
INSERT INTO users (user_data) VALUES
('{ "name": "علی", "email": "ali@example.com", "preferences": { "newsletter": true, "theme": "dark" } }'),
('{ "name": "سارا", "email": "sara@example.com", "preferences": { "newsletter": false }, "address": { "city": "تهران", "zip": "12345" } }');
پرسوجو (Query) بر روی JSONB تو در تو
یکی از نقاط قوت `JSONB`، قابلیت پرسوجو عمیق بر روی ساختارهای تو در تو است. PostgreSQL عملگرهای قدرتمندی برای استخراج و فیلتر کردن دادهها از درون `JSONB` ارائه میدهد.
برای مثال، برای انتخاب کاربرانی که در خبرنامه عضو شدهاند:
SELECT user_data->>'name' AS user_name, user_data->>'email' AS user_email
FROM users
WHERE user_data->'preferences'->>'newsletter' = 'true';
در این پرسوجو:
* `->` برای استخراج یک شیء `JSON` یا آرایه `JSON` استفاده میشود.
* `->>` برای استخراج یک فیلد `JSON` به عنوان متن استفاده میشود.
اگر بخواهیم کاربرانی را که شهرشان “تهران” است پیدا کنیم:
SELECT user_data->>'name' AS user_name
FROM users
WHERE user_data->'address'->>'city' = 'تهران';
نمایهگذاری (Indexing) JSONB برای عملکرد بهتر
برای بهبود عملکرد پرسوجوها بر روی ستونهای `JSONB`، میتوانیم نمایههای `GIN` (Generalized Inverted Index) ایجاد کنیم. این نمایهها به PostgreSQL اجازه میدهند تا به سرعت به فیلدهای درون `JSONB` دسترسی پیدا کند.
نمایهگذاری کل ستون `JSONB` برای جستجوی کلیدها و مقادیر:
CREATE INDEX idx_users_jsonb_data ON users USING GIN (user_data);
این نوع فهرست برای اپراتورهای `?`, `?|`, `?&`, `@>`, `<@` مفید است.
اگر میدانید که اغلب بر اساس یک فیلد خاص درون `JSONB` پرسوجو میکنید، میتوانید یک نمایه عبارتی (expression index) ایجاد کنید:
CREATE INDEX idx_users_email ON users USING GIN ((user_data->>'email'));
این نمایه به طور خاص پرسوجوهایی مانند `WHERE user_data->>’email’ = ‘…’` را سرعت میبخشد.
جستجوی متن کامل (Full-Text Search) در PostgreSQL
PostgreSQL شامل قابلیتهای جستجوی متن کامل قدرتمندی است که به شما امکان میدهد متون را با دقت و سرعت بالا جستجو کنید. این قابلیتها از ترکیب دو نوع داده اصلی استفاده میکنند: `TSVECTOR` و `TSQUERY`.
* `TSVECTOR`: یک “بردار توکن” است که نمایشی پردازششده از یک سند متنی را در خود جای میدهد. شامل کلمات نرمالشده (lexemes)، اطلاعات موقعیت (positional information) و وزنها (weights) است.
* `TSQUERY`: یک “پرسوجوی توکن” است که برای جستجو در `TSVECTOR` استفاده میشود. میتواند شامل عملگرهای `AND`, `OR`, `NOT` و جستجوی عبارات باشد.
برای مثال، فرض کنید جدولی برای مقالات داریم:
CREATE TABLE articles (
id SERIAL PRIMARY KEY,
title TEXT,
content TEXT,
search_vector TSVECTOR
);
ابتدا، باید ستون `search_vector` را از `title` و `content` پر کنیم. از تابع `to_tsvector` استفاده میکنیم که متن را به `TSVECTOR` تبدیل میکند. میتوانید یک “پیکربندی دیکشنری” (dictionary configuration) را برای زبان مشخص کنید (مثلاً `pg_catalog.persian` برای فارسی، در صورت نصب افزونههای مربوطه، یا `english` برای انگلیسی).
UPDATE articles
SET search_vector = to_tsvector('pg_catalog.english', title || ' ' || content);
برای جستجو، از تابع `to_tsquery` برای تبدیل رشته جستجو به `TSQUERY` و سپس از عملگر `@@` برای مقایسه `TSVECTOR` با `TSQUERY` استفاده میکنیم:
SELECT title, content
FROM articles
WHERE search_vector @@ to_tsquery('pg_catalog.english', 'database & NoSQL');
این پرسوجو مقالاتی را پیدا میکند که هم کلمه “database” و هم کلمه “NoSQL” را (به فرمهای نرمالشدهشان) شامل میشوند.
برای بهبود عملکرد جستجوی متن کامل، باید روی ستون `TSVECTOR` نمایه `GIN` ایجاد کنیم:
CREATE INDEX idx_articles_search_vector ON articles USING GIN (search_vector);
رتبهبندی نتایج جستجوی متن کامل
قابلیتهای جستجوی متن کامل PostgreSQL همچنین شامل توابعی برای رتبهبندی نتایج است، که به شما امکان میدهد مرتبطترین اسناد را در بالای لیست نمایش دهید. تابع `ts_rank` یا `ts_rank_cd` برای این منظور استفاده میشوند.
مثال برای رتبهبندی نتایج:
SELECT title, ts_rank(search_vector, to_tsquery('pg_catalog.english', 'database & PostgreSQL')) AS rank
FROM articles
WHERE search_vector @@ to_tsquery('pg_catalog.english', 'database & PostgreSQL')
ORDER BY rank DESC;
این پرسوجو مقالاتی را که حاوی “database” و “PostgreSQL” هستند بر اساس میزان ارتباطشان با پرسوجو (ranking) مرتب میکند.
ترکیب جستجوی متن کامل با JSONB
قدرت واقعی PostgreSQL زمانی آشکار میشود که قابلیتهای `JSONB` و جستجوی متن کامل را با هم ترکیب کنید. میتوانید متن را از داخل اسناد `JSONB` استخراج کرده و سپس آن را برای جستجوی متن کامل نمایهسازی کنید.
فرض کنید در ستون `user_data` از مثال قبلی، علاوه بر اطلاعات ساختاریافته، توضیحات متنی (مثلاً “bio”) نیز داشته باشیم:
ALTER TABLE users ADD COLUMN search_vector TSVECTOR;
سپس `search_vector` را با استخراج متن از `JSONB` پر میکنیم:
UPDATE users
SET search_vector = to_tsvector('pg_catalog.english', COALESCE(user_data->>'name', '') || ' ' || COALESCE(user_data->>'bio', ''))
WHERE user_data IS NOT NULL;
حالا میتوانیم جستجوهای ترکیبی انجام دهیم: کاربران را بر اساس متن در “bio” آنها جستجو کنیم و همزمان بر اساس یک ویژگی `JSONB` فیلتر کنیم:
SELECT user_data->>'name' AS user_name, user_data->>'email' AS user_email
FROM users
WHERE search_vector @@ to_tsquery('pg_catalog.english', 'developer')
AND user_data->'preferences'->>'newsletter' = 'true';
با این روش، میتوانیم کاربرانی را پیدا کنیم که در بخش “bio” خود کلمه “developer” را دارند و همچنین عضو خبرنامه هستند. برای بهبود عملکرد این جستجو، ایجاد یک نمایه `GIN` بر روی `search_vector` در جدول `users` ضروری است:
CREATE INDEX idx_users_search_vector ON users USING GIN (search_vector);
نتیجهگیری
پستگرسکیوال فراتر از یک پایگاه داده رابطهای صرف است و قابلیتهای NoSQL و جستجوی متن کامل پیشرفتهای را ارائه میدهد. با استفاده از نوع داده `JSONB`، میتوانید دادههای نیمهساختاریافته و بدون ساختار را به طور مؤثر مدیریت کنید و با `TSVECTOR` و `TSQUERY`، موتورهای جستجوی قدرتمندی را مستقیماً در پایگاه داده خود بسازید. این ویژگیها PostgreSQL را به یک راهکار همهکاره و مقیاسپذیر برای طیف وسیعی از برنامهها تبدیل میکنند و نیاز به پایگاههای داده جداگانه NoSQL یا موتورهای جستجوی اختصاصی را در بسیاری از موارد از بین میبرند. با درک و بهکارگیری این قابلیتها، توسعهدهندگان میتوانند از تمام پتانسیل PostgreSQL در پروژههای خود بهرهمند شوند.