DanLevy.net

Postgres Text Searching Guide 2026

आपके डेटाबेस में मौजूद सर्च टूल्स, और हर एक कब अपना काम साबित करता है।

ज्यादातर टीमें एक ही Postgres सर्च टूल का इस्तेमाल करती हैं। जो टीमें तीनों जानती हैं, वे कम कॉम्प्लेक्सिटी के साथ बेहतर सर्च डिलीवर करती हैं — और उस महंगे डिटूर से बचती हैं जो एक डेडिकेटेड सर्च सर्विस की ओर जाता है, जिसकी उन्हें ज़रूरत भी नहीं थी।

यह गाइड Postgres-नेटिव विकल्पों की पूरी रेंज कवर करता है: हर एक क्या करता है, कब सही फिट है, और उन्हें कैसे लेयर किया जाए।


तीन टूल्स

फुल-टेक्स्ट सर्च (tsvector / GIN इंडेक्स) लेक्सिकल है। यह टेक्स्ट को लेक्सीम में टोकनाइज़ करता है, उन्हें स्टेम करता है, और इंडेक्स के खिलाफ क्वेरी मैच करता है। “Running” और “runs” एक ही लेक्सीम में ढह जाते हैं। “dog” और “dogs” भी ऐसा ही करते हैं। रैंकिंग फ़ंक्शन (ts_rank) उन डॉक्यूमेंट्स को इनाम देता है जहाँ क्वेरी टर्म्स बार-बार या प्रमुख स्थानों पर आती हैं।

ट्राइग्राम (pg_trgm) स्ट्रिंग्स को ओवरलैपिंग 3-अक्षर वाले स्लाइस में तोड़ता है और मापता है कि दो स्ट्रिंग्स कितने स्लाइस साझा करती हैं। “Dan” → " da", "dan", "an "। “Micheal” और “Michael” अपने ज़्यादातर ट्राइग्राम साझा करते हैं, इसलिए समानता उच्च है। यह pg_trgm को फ़ज़ी नाम मैचिंग, टाइपो टॉलरेंस और ऑटोकम्प्लीट के लिए उत्कृष्ट बनाता है — उस स्पेस में जहाँ FTS खराब प्रदर्शन करता है।

एक्सैक्ट-मैच इंडेक्स (B-tree, hash) प्राइमरी की, ईमेल एड्रेस, ID, SKU, और किसी भी चीज़ को हैंडल करते हैं जहाँ उत्तर बाइनरी है: यह मैच करता है या नहीं। ये “सर्च” जैसे नहीं लगते, लेकिन वे इस चर्चा में शामिल हैं क्योंकि सबसे खराब पैटर्न फ़ज़ी या सेमेंटिक सर्च का इस्तेमाल उन समस्याओं के लिए करना है जिनके सही उत्तर होते हैं।

चुनाव परिष्कार के बारे में नहीं है। यह क्वेरी के आकार से टूल को मैच करने के बारे में है।

Postgres सर्च टूल मैपइनपुट आकार और क्वेरी इंटेंट के आधार पर pg_trgm, फुल-टेक्स्ट सर्च, pgvector और हाइब्रिड सर्च की तुलना।इनपुट आकार के आधार पर सर्च प्रिमिटिव चुनेंएक ही Postgres टेबल चारों को सपोर्ट कर सकती है। तरकीब क्वेरी को टेक्स्ट से मैच करने में है।सटीक शब्द मायने रखते हैंअर्थ मायने रखता हैछोटा / संरचित टेक्स्टलंबा गद्य / चंक्सफ़ज़ीpg_trgmनाम, पते, शीर्षक, टाइपो,ऑटोकम्प्लीट, आंशिक स्ट्रिंग्स।ऑर्थोग्राफ़िक समानता: वर्तनी दूरी।समानpgvectorसंबंधित आइटम, डुप्लिकेट टिकट,छोटे विवरण से सुझाव।एम्बेडिंग समानता: अर्थ दूरी।लेक्सिकलफुल-टेक्स्ट सर्चआर्टिकल, डॉक्स, लॉग, सपोर्ट कंटेंटजहाँ क्वेरी शब्द दिखने चाहिए।लेक्सीम, स्टेमिंग, रैंकिंग, बूलियन फ़िल्टर।हाइब्रिडFTS + pgvectorतकनीकी डॉक्स और RAG जहाँ यूज़रकॉन्सेप्चुअल सवाल और सटीक सिंबल पूछते हैं।दोनों चलाएं, RRF से रैंक मर्ज करें।क्वेरी इंटेंट से शुरू करें, फिर टेक्स्ट आकार चेक करें
चारों Postgres सर्च प्रिमिटिव्स को क्वेरी इंटेंट (सटीक बनाम सेमेंटिक) और टेक्स्ट आकार (संरचित बनाम गद्य) के आधार पर मैप किया गया है। एक ही टेबल चारों इंडेक्स रख सकती है — चुनाव प्रति टेबल नहीं, प्रति क्वेरी है।

फुल-टेक्स्ट सर्च कब जीतता है

कीवर्ड के लिए गद्य खोजना। ब्लॉग पोस्ट, दस्तावेज़ीकरण, प्रोडक्ट डिस्क्रिप्शन, सपोर्ट टिकट, लीगल डॉक्यूमेंट। FTS इस आकार की कंटेंट के लिए बनाया गया है: इंडेक्स की गई, रैंक की गई प्राप्ति नेचुरल-लैंग्वेज टेक्स्ट पर।

कीवर्ड-आधारित यूज़र क्वेरी। यूज़र सर्च टर्म टाइप करते हैं, टैग से फ़िल्टर करते हैं, या कीवर्ड से ब्राउज़ करते हैं। FTS उस इंटेंट को नेटिव रूप से हैंडल करता है — बिना किसी एम्बेडिंग इंफ्रास्ट्रक्चर के।

बाहरी डिपेंडेंसी के बिना रैंक किए गए नतीजे। FTS इंडेक्स तेज़, डिटरमिनिस्टिक हैं, और उन्हें किसी API कॉल की ज़रूरत नहीं होती। रिलेवेंस सिग्नल टर्म फ़्रीक्वेंसी से आता है, जिसे फ़ील्ड पोज़िशन से वेट किया जाता है।

सर्च के साथ बूलियन फ़िल्टरिंग। FTS आपके मौजूदा क्वेरी लॉजिक के साथ नेचुरली कंपोज़ होता है:

SELECT * FROM posts
WHERE search_vector @@ to_tsquery('english', 'postgres & performance')
AND category = 'tutorial'
AND published_at > NOW() - INTERVAL '6 months';

FTS सेटअप

-- जनरेटेड कॉलम इंडेक्स को स्वचालित रूप से करंट रखता है
ALTER TABLE posts ADD COLUMN search_vector tsvector
GENERATED ALWAYS AS (
setweight(to_tsvector('english', coalesce(title, '')), 'A') ||
setweight(to_tsvector('english', coalesce(body, '')), 'B')
) STORED;
CREATE INDEX posts_search_idx ON posts USING GIN (search_vector);
-- क्वेरी
SELECT title, ts_rank(search_vector, query) AS rank
FROM posts, to_tsquery('english', 'postgres & performance') query
WHERE search_vector @@ query
ORDER BY rank DESC
LIMIT 10;

setweight इम्पोर्टेंस असाइन करता है: A (शीर्षक) B (बॉडी) से ऊपर रैंक करता है। ज़्यादातर कंटेंट-सर्च यूज़ केस के लिए यही पूरी रिलेवेंस मॉडल है।

FTS क्या अच्छे से नहीं संभाल पाता


ट्राइग्राम कब जीतता है (pg_trgm)

pg_trgm उस अजीब मध्य को कवर करता है जिसे FTS लगातार चूकता है।

FTS टेक्स्ट को लेक्सीम में टोकनाइज़ करता है और उन्हें स्टेम करता है। गद्य के लिए यह सही है। नामों और छोटे आइडेंटिफ़ायर के लिए अक्सर नहीं:

pg_trgm भाषा-एग्नोस्टिक भी है, जो विविध भाषाई पृष्ठभूमि के नामों के लिए मायने रखता है। FTS को प्रति भाषा डिक्शनरी कॉन्फ़िगरेशन की ज़रूरत होती है।

फ़ज़ी नाम सर्च

CREATE EXTENSION IF NOT EXISTS pg_trgm;
CREATE INDEX users_name_trgm_idx ON users USING GIN (name gin_trgm_ops);
-- "Michael Jordan" खोजने पर "Micheal Jordan" मिलता है
SELECT id, name, similarity(name, $1) AS score
FROM users
WHERE name % $1 -- % ऑपरेटर = समानता थ्रेशोल्ड (डिफ़ॉल्ट 0.3)
ORDER BY score DESC
LIMIT 10;

% ऑपरेटर pg_trgm.similarity_threshold (डिफ़ॉल्ट 0.3, रेंज 0–1) का उपयोग करता है। नाम सर्च के लिए, 0.3–0.4 टाइपो को पकड़ता है जबकि नॉइज़ कम रखता है।

ऑटोकम्प्लीट, प्रीफ़िक्स और कंटेंस सर्च

-- ऑटोकम्प्लीट के लिए प्रीफ़िक्स मैचिंग। ट्राइग्राम GIN इंडेक्स मदद कर सकता है,
-- लेकिन शुद्ध बाएं-एंकर्ड प्रीफ़िक्स के लिए B-tree पैटर्न इंडेक्स बेहतर हो सकता है।
SELECT name FROM users
WHERE name ILIKE $1 || '%'
ORDER BY name
LIMIT 10;
-- लंबी स्ट्रिंग्स के भीतर आंशिक मैच के लिए word_similarity
-- ("Andrew Johnson III" के अंदर "Johnson")
SELECT id, name, word_similarity($1, name) AS score
FROM users
WHERE $1 <% name
ORDER BY score DESC
LIMIT 10;

ट्राइग्राम GIN इंडेक्स ILIKE '%pattern%' कंटेंस क्वेरी और टाइपो-टॉलरेंट मैचिंग के लिए विशेष रूप से उपयोगी है — ऐसे पैटर्न जो ट्राइग्राम इंडेक्स के बिना आमतौर पर फुल-टेबल स्कैन होते हैं।

FTS की जगह pg_trgm कब चुनें

परिदृश्यउपयोग
टाइपो के साथ व्यक्ति/कंपनी नाम सर्चpg_trgm
ऑटोकम्प्लीट / प्रीफ़िक्स सर्चpg_trgm (या प्रीफ़िक्स क्वेरी के साथ FTS)
छोटी स्ट्रिंग्स, आइडेंटिफ़ायर, कोडpg_trgm
गद्य आर्टिकल, दस्तावेज़ीकरण, टिकटFTS
कीवर्ड के लिए लॉग मैसेजFTS
बहुभाषी नाम सर्चpg_trgm (भाषा-एग्नोस्टिक)

एक्सैक्ट-मैच SQL कब जीतता है

कुछ “सर्च” समस्याएं बिल्कुल सर्च नहीं हैं।

“ईमेल dan@example.com वाला यूज़र खोजें” एक इक्वैलिटी चेक है। “ऑर्डर ORD-12345 खोजें” एक प्राइमरी की लुकअप है। “तारीख़ के हिसाब से सॉर्ट की गई tutorial कैटेगरी की पोस्ट सूची” एक फ़िल्टर्ड क्वेरी है। ये B-tree या hash इंडेक्स पर belong करते हैं।

यहाँ FTS या ट्राइग्राम का उपयोग करने से कॉम्प्लेक्सिटी बढ़ती है बिना करेक्टनेस में सुधार के — और सटीक आइडेंटिफ़ायर के लिए, लगभग-मैच कोई मैच नहीं होने से बदतर है।

CREATE INDEX users_email_idx ON users (email);
-- सटीक लुकअप: तेज़ और अस्पष्ट नहीं
SELECT id, name FROM users WHERE email = $1;

व्यापक सबक: सही उत्तर वाली समस्याओं के लिए अनुमानित सर्च एक कैटेगरी एरर है। यह कुछ लौटाता है — जो आत्मविश्वास से गलत हो सकता है।


इन टूल्स को मिलाना

ये टूल्स साफ़ तरीके से कंपोज़ होते हैं। आपको सिर्फ़ एक चुनने की ज़रूरत नहीं है।

टाइपो टॉलरेंट सर्च बॉक्स के लिए FTS + pg_trgm:

-- शीर्षक पर ट्राइग्राम समानता टाइपो पकड़ती है; ts_rank बॉडी रिलेवेंस हैंडल करता है
SELECT id, title,
ts_rank(search_vector, to_tsquery('simple', $1)) AS fts_rank,
similarity(title, $1) AS trgm_score
FROM posts
WHERE search_vector @@ to_tsquery('simple', $1)
OR title % $1
ORDER BY (ts_rank(search_vector, to_tsquery('simple', $1)) + similarity(title, $1)) DESC
LIMIT 10;

इंटरनेशनल कंटेंट के लिए FTS + unaccent:

-- डायक्रिटिकल मार्क्स हटाएं ताकि "José", "Jose" से मैच हो
CREATE EXTENSION IF NOT EXISTS unaccent;
CREATE TEXT SEARCH CONFIGURATION public.simple_unaccent (COPY = pg_catalog.simple);
ALTER TEXT SEARCH CONFIGURATION public.simple_unaccent
ALTER MAPPING FOR hword, hword_part, word
WITH unaccent, simple;
ALTER TABLE posts ADD COLUMN search_vector tsvector;
CREATE TRIGGER posts_search_vector_refresh
BEFORE INSERT OR UPDATE OF title, body ON posts
FOR EACH ROW EXECUTE FUNCTION
tsvector_update_trigger(search_vector, 'public.simple_unaccent', title, body);

इंटरनेशनल नाम सर्च के लिए unaccent + pg_trgm:

ALTER TABLE users ADD COLUMN name_search text;
CREATE FUNCTION users_name_search_refresh()
RETURNS trigger LANGUAGE plpgsql AS $$
BEGIN
NEW.name_search := unaccent(coalesce(NEW.name, ''));
RETURN NEW;
END;
$$;
CREATE TRIGGER users_name_search_refresh
BEFORE INSERT OR UPDATE OF name ON users
FOR EACH ROW EXECUTE FUNCTION users_name_search_refresh();
CREATE INDEX users_name_search_trgm_idx
ON users USING GIN (name_search gin_trgm_ops);
SELECT id, name
FROM users
WHERE name_search % unaccent($1)
ORDER BY similarity(name_search, unaccent($1)) DESC
LIMIT 10;

ट्रिगर उदाहरण जनरेटेड-कॉलम या इंडेक्स एक्सप्रेशन के अंदर unaccent() का उपयोग करने से बचते हैं, जहाँ PostgreSQL के इम्यूटेबिलिटी नियम मायने रखते हैं। यदि आप unaccent() को अपने इम्यूटेबल फ़ंक्शन में रैप करते हैं, तो दस्तावेज़ करें कि आप अपग्रेड/कॉन्फ़िगरेशन जोखिम स्वीकार कर रहे हैं।


उल्लेखनीय एक्सटेंशन

pg_trgm ज़्यादातर Postgres डिस्ट्रीब्यूशन के साथ बंडल है लेकिन स्पष्ट रूप से इनेबल करने की ज़रूरत होती है। Postgres में फ़ज़ी स्ट्रिंग मैचिंग की नींव।

unaccent इंडेक्सिंग और क्वेरी से पहले डायक्रिटिकल मार्क्स हटाता है। यूरोपीय-भाषा कंटेंट के लिए pg_trgm और FTS दोनों के साथ अच्छी जोड़ी बनाता है। Postgres के साथ बंडल।

pg_bigm ट्राइग्राम अप्रोच को बाइग्राम (2-अक्षर स्लाइस) तक बढ़ाता है, जो CJK (चीनी, जापानी, कोरियाई) भाषाओं के लिए परिणामों को काफ़ी बेहतर बनाता है जहाँ pg_trgm कमज़ोर प्रदर्शन करता है। अलग से इंस्टॉल करना होता है; बंडल नहीं है।

pg_search (ParadeDB से) स्टैंडर्ड GIN / tsvector स्टैक को Tantivy-आधारित BM25 इंडेक्स से बदल देता है। यह आपको BM25 स्कोरिंग (अक्सर ts_rank से बेहतर), FTS क्वेरी के भीतर फ़ज़ी मैचिंग, फ़ैक्टेड सर्च, और बड़ी टेबल्स पर नाटकीय रूप से तेज़ इंडेक्सिंग देता है। यह एक ड्रॉप-इन अपग्रेड पाथ है जब स्टैंडर्ड FTS रैंकिंग या परफ़ॉर्मेंस लिमिट दिखाने लगता है।

-- pg_search: फ़ज़ी मैचिंग के साथ BM25 फुल-टेक्स्ट सर्च
CREATE INDEX posts_bm25_idx ON posts
USING bm25 (id, title, body)
WITH (key_field = 'id', text_fields = '{"title": {}, "body": {}}');
-- BM25 स्कोरिंग + फ़ज़ी मैचिंग के साथ क्वेरी ("javascipt" पकड़ता है)
SELECT id, title, paradedb.score(id) AS rank
FROM posts
WHERE posts @@@ paradedb.fuzzy_phrase(field => 'title', value => 'postgres performnce')
ORDER BY rank DESC
LIMIT 10;

pgvector डेंस वेक्टर स्टोरेज और समानता सर्च जोड़ता है। यह सही टूल है जब यूज़र किसी चीज़ का नाम नहीं बल्कि उसे डिस्क्राइब करते हैं — सेमेंटिक सर्च, RAG, संबंधित-कंटेंट सुझाव, बहुभाषी क्वेरी। Semantic Vector Search and Hybrid Strategies में विस्तार से कवर किया गया है।


निर्णय तालिका

आप क्या खोज रहे हैंअनुशंसित
गद्य आर्टिकल, डॉक्स, टिकटFTS
टाइपो के साथ व्यक्ति/कंपनी नामpg_trgm
ऑटोकम्प्लीट, प्रीफ़िक्स सर्चpg_trgm
छोटे कोड, आइडेंटिफ़ायरpg_trgm
कीवर्ड के लिए लॉग मैसेजFTS
इंटरनेशनल नामpg_trgm + unaccent
बड़ा कंटेंट, बेहतर रैंकिंगpg_search (ParadeDB BM25)
प्राइमरी की, सटीक ईमेल, IDB-tree इंडेक्स
तारीख़, रेंज, सॉर्ट की गई सूचीB-tree इंडेक्स
परमिशन, कैटेगरी, फ़िल्टरसामान्य WHERE क्लॉज़
सवाल, पैराफ़्रेज़, कॉन्सेप्टpgvector (अगला आर्टिकल देखें)

संदेह होने पर: वर्तनी भिन्नता के साथ छोटी स्ट्रिंग्स → ट्राइग्राम। कीवर्ड क्वेरी के लिए लंबा गद्य → FTS। संरचित आइडेंटिफ़ायर → रेगुलर इंडेक्स। कॉन्सेप्चुअल या नेचुरल-लैंग्वेज क्वेरी → pgvector।


हाइब्रिड सर्च: दो सिग्नल, एक रैंक

जब "withRetry timeout errors" जैसी क्वेरी सर्च बॉक्स में हिट करती है, तो वह दो तरह के इंटेंट ले जाती है: सटीक सिंबल नाम जो यूज़र जानता है (withRetry) और एक कॉन्सेप्चुअल डिस्क्रिप्शन (timeout errors)। कोई एक प्रिमिटिव दोनों को कवर नहीं करता। FTS और वेक्टर सर्च को समानांतर में चलाना — फिर Reciprocal Rank Fusion से उनकी रैंक की गई सूचियों को मर्ज करना — यह काम करता है।

RRF हर नतीजे को हर सूची में 1 / (60 + rank) के रूप में स्कोर करता है और सूचियों में जोड़ता है। स्थिरांक 60 टॉप रैंक के फ़ायदे को कम करता है, ताकि एक नतीजा जो दोनों सूचियों में दूसरे स्थान पर आए, वह उस नतीजे को हरा सके जो एक सूची जीतता है और दूसरी में पूरी तरह चूक जाता है। महत्वपूर्ण रूप से, RRF कभी भी कच्चे स्कोर को मेथड्स के बीच औसत नहीं करता — FTS रैंक और कोसाइन डिस्टेंस अलग करेंसी हैं और उन्हें अंकगणितीय रूप से नहीं जोड़ा जा सकता।

Reciprocal Rank Fusion के साथ हाइब्रिड सर्चएक क्वेरी फुल-टेक्स्ट सर्च और वेक्टर सर्च दोनों में जाती है, हर एक रैंक बनाता है, और Reciprocal Rank Fusion उन्हें एक नतीजा सूची में मर्ज करता है।हाइब्रिड सर्च दो ईमानदार सिग्नल है, फिर एक मर्ज की गई रैंककच्चे स्कोर का औसत न लें। FTS रैंक और कोसाइन डिस्टेंस अलग करेंसी हैं।यूज़र क्वेरी”withRetrytimeout errors”FTS / BM25सटीक सिंबल और शब्द1. API रेफ़रंस2. रिट्राई गाइडpgvectorकॉन्सेप्चुअल नेबर1. नेटवर्क फ़ेल्योर2. रिट्राई गाइडRRF मर्जहर नतीजे को क्रेडिट देंकि वह हर सूची में कहाँ रैंक किया।1 / (60 + rank)अंतिम नतीजेटॉप हिट वह है जहाँ सटीक टर्म्सऔर सेमेंटिक अर्थ सहमत हैं।
एक क्वेरी समानांतर में FTS और pgvector दोनों में जाती है। हर एक अपनी रैंक की गई सूची बनाता है। RRF हर डॉक्यूमेंट को हर सूची में अपनी पोज़िशन से स्कोर करता है और स्कोर जोड़ता है — नतीजा उन डॉक्यूमेंट्स को सतह पर लाता है जिन पर दोनों सिग्नल सहमत हैं।
-- हाइब्रिड सर्च: FTS + pgvector को RRF से मर्ज किया गया
WITH fts AS (
SELECT id, ts_rank(search_vector, query) AS score,
ROW_NUMBER() OVER (ORDER BY ts_rank(search_vector, query) DESC) AS rank
FROM docs, to_tsquery('english', 'withRetry & timeout') query
WHERE search_vector @@ query
LIMIT 60
),
vec AS (
SELECT id,
ROW_NUMBER() OVER (ORDER BY embedding <=> $embedding) AS rank
FROM docs
ORDER BY embedding <=> $embedding
LIMIT 60
)
SELECT COALESCE(fts.id, vec.id) AS id,
(COALESCE(1.0 / (60 + fts.rank), 0) +
COALESCE(1.0 / (60 + vec.rank), 0)) AS rrf_score
FROM fts FULL JOIN vec ON fts.id = vec.id
ORDER BY rrf_score DESC
LIMIT 10;

प्रति ब्रांच 60-डॉक्यूमेंट कैंडिडेट पूल (LIMIT 60) एक आम शुरुआती बिंदु है। यदि रिकॉल कम है तो इसे बढ़ाएं; स्पीड के लिए घटाएं।


आगे क्या है

Postgres टेक्स्ट सर्च काफ़ी ज़मीन कवर करता है, लेकिन इसकी एक सीमा है। जब यूज़र किसी चीज़ का नाम नहीं बल्कि उसे डिस्क्राइब करते हैं — “कुछ ऐसा जो मुझे फ़्लाइट में सोने में मदद करे,” “नए इंजीनियर के रूप में डिबगिंग कॉन्फ़िडेंस पर आर्टिकल” — लेक्सिकल और ट्राइग्राम सर्च दोनों फ़ेल हो जाते हैं।

यह वेक्टर एम्बेडिंग्स, सेमेंटिक सर्च और हाइब्रिड आर्किटेक्चर का इलाका है। Semantic Vector Search and Hybrid Strategies में कवर किया गया है।