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, और किसी भी चीज़ को हैंडल करते हैं जहाँ उत्तर बाइनरी है: यह मैच करता है या नहीं। ये “सर्च” जैसे नहीं लगते, लेकिन वे इस चर्चा में शामिल हैं क्योंकि सबसे खराब पैटर्न फ़ज़ी या सेमेंटिक सर्च का इस्तेमाल उन समस्याओं के लिए करना है जिनके सही उत्तर होते हैं।
चुनाव परिष्कार के बारे में नहीं है। यह क्वेरी के आकार से टूल को मैच करने के बारे में है।
फुल-टेक्स्ट सर्च कब जीतता है
कीवर्ड के लिए गद्य खोजना। ब्लॉग पोस्ट, दस्तावेज़ीकरण, प्रोडक्ट डिस्क्रिप्शन, सपोर्ट टिकट, लीगल डॉक्यूमेंट। FTS इस आकार की कंटेंट के लिए बनाया गया है: इंडेक्स की गई, रैंक की गई प्राप्ति नेचुरल-लैंग्वेज टेक्स्ट पर।
कीवर्ड-आधारित यूज़र क्वेरी। यूज़र सर्च टर्म टाइप करते हैं, टैग से फ़िल्टर करते हैं, या कीवर्ड से ब्राउज़ करते हैं। FTS उस इंटेंट को नेटिव रूप से हैंडल करता है — बिना किसी एम्बेडिंग इंफ्रास्ट्रक्चर के।
बाहरी डिपेंडेंसी के बिना रैंक किए गए नतीजे। FTS इंडेक्स तेज़, डिटरमिनिस्टिक हैं, और उन्हें किसी API कॉल की ज़रूरत नहीं होती। रिलेवेंस सिग्नल टर्म फ़्रीक्वेंसी से आता है, जिसे फ़ील्ड पोज़िशन से वेट किया जाता है।
सर्च के साथ बूलियन फ़िल्टरिंग। FTS आपके मौजूदा क्वेरी लॉजिक के साथ नेचुरली कंपोज़ होता है:
SELECT * FROM postsWHERE 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 rankFROM posts, to_tsquery('english', 'postgres & performance') queryWHERE search_vector @@ queryORDER BY rank DESCLIMIT 10;setweight इम्पोर्टेंस असाइन करता है: A (शीर्षक) B (बॉडी) से ऊपर रैंक करता है। ज़्यादातर कंटेंट-सर्च यूज़ केस के लिए यही पूरी रिलेवेंस मॉडल है।
FTS क्या अच्छे से नहीं संभाल पाता
- क्वेरी में टाइपो — “javascipt” “javascript” से मैच नहीं करेगा
- व्यक्ति के नाम, पते, उचित संज्ञा जो अनुमानित रूप से स्टेम नहीं होते
- बिना विशेष कॉन्फ़िगरेशन के प्रीफ़िक्स/ऑटोकम्प्लीट
- वे क्वेरी जहाँ यूज़र किसी चीज़ का नाम नहीं बल्कि कॉन्सेप्ट बता रहा है
ट्राइग्राम कब जीतता है (pg_trgm)
pg_trgm उस अजीब मध्य को कवर करता है जिसे FTS लगातार चूकता है।
FTS टेक्स्ट को लेक्सीम में टोकनाइज़ करता है और उन्हें स्टेम करता है। गद्य के लिए यह सही है। नामों और छोटे आइडेंटिफ़ायर के लिए अक्सर नहीं:
- व्यक्ति के नाम (“Dan Levy” → डिक्शनरी और लैंग्वेज कॉन्फ़िग के आधार पर अलग तरह से स्टेम)
- कंपनी के नाम, पते, प्रोडक्ट टाइटल जहाँ सटीक वर्तनी मायने रखती है
- टाइपो वाली क्वेरी — “Micheal Jordan”, “Amaon”, “javascipt”
- ऑटोकम्प्लीट / प्रीफ़िक्स सर्च
- आंशिक स्ट्रिंग मैचिंग (“Johnson” के अंदर “son” का मैच होना, “Anderson”)
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 scoreFROM usersWHERE name % $1 -- % ऑपरेटर = समानता थ्रेशोल्ड (डिफ़ॉल्ट 0.3)ORDER BY score DESCLIMIT 10;% ऑपरेटर pg_trgm.similarity_threshold (डिफ़ॉल्ट 0.3, रेंज 0–1) का उपयोग करता है। नाम सर्च के लिए, 0.3–0.4 टाइपो को पकड़ता है जबकि नॉइज़ कम रखता है।
ऑटोकम्प्लीट, प्रीफ़िक्स और कंटेंस सर्च
-- ऑटोकम्प्लीट के लिए प्रीफ़िक्स मैचिंग। ट्राइग्राम GIN इंडेक्स मदद कर सकता है,-- लेकिन शुद्ध बाएं-एंकर्ड प्रीफ़िक्स के लिए B-tree पैटर्न इंडेक्स बेहतर हो सकता है।SELECT name FROM usersWHERE name ILIKE $1 || '%'ORDER BY nameLIMIT 10;
-- लंबी स्ट्रिंग्स के भीतर आंशिक मैच के लिए word_similarity-- ("Andrew Johnson III" के अंदर "Johnson")SELECT id, name, word_similarity($1, name) AS scoreFROM usersWHERE $1 <% nameORDER BY score DESCLIMIT 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_scoreFROM postsWHERE search_vector @@ to_tsquery('simple', $1) OR title % $1ORDER BY (ts_rank(search_vector, to_tsquery('simple', $1)) + similarity(title, $1)) DESCLIMIT 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_refreshBEFORE INSERT OR UPDATE OF title, body ON postsFOR 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_refreshBEFORE INSERT OR UPDATE OF name ON usersFOR 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, nameFROM usersWHERE name_search % unaccent($1)ORDER BY similarity(name_search, unaccent($1)) DESCLIMIT 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 rankFROM postsWHERE posts @@@ paradedb.fuzzy_phrase(field => 'title', value => 'postgres performnce')ORDER BY rank DESCLIMIT 10;pgvector डेंस वेक्टर स्टोरेज और समानता सर्च जोड़ता है। यह सही टूल है जब यूज़र किसी चीज़ का नाम नहीं बल्कि उसे डिस्क्राइब करते हैं — सेमेंटिक सर्च, RAG, संबंधित-कंटेंट सुझाव, बहुभाषी क्वेरी। Semantic Vector Search and Hybrid Strategies में विस्तार से कवर किया गया है।
निर्णय तालिका
| आप क्या खोज रहे हैं | अनुशंसित |
|---|---|
| गद्य आर्टिकल, डॉक्स, टिकट | FTS |
| टाइपो के साथ व्यक्ति/कंपनी नाम | pg_trgm |
| ऑटोकम्प्लीट, प्रीफ़िक्स सर्च | pg_trgm |
| छोटे कोड, आइडेंटिफ़ायर | pg_trgm |
| कीवर्ड के लिए लॉग मैसेज | FTS |
| इंटरनेशनल नाम | pg_trgm + unaccent |
| बड़ा कंटेंट, बेहतर रैंकिंग | pg_search (ParadeDB BM25) |
| प्राइमरी की, सटीक ईमेल, ID | B-tree इंडेक्स |
| तारीख़, रेंज, सॉर्ट की गई सूची | B-tree इंडेक्स |
| परमिशन, कैटेगरी, फ़िल्टर | सामान्य WHERE क्लॉज़ |
| सवाल, पैराफ़्रेज़, कॉन्सेप्ट | pgvector (अगला आर्टिकल देखें) |
संदेह होने पर: वर्तनी भिन्नता के साथ छोटी स्ट्रिंग्स → ट्राइग्राम। कीवर्ड क्वेरी के लिए लंबा गद्य → FTS। संरचित आइडेंटिफ़ायर → रेगुलर इंडेक्स। कॉन्सेप्चुअल या नेचुरल-लैंग्वेज क्वेरी → pgvector।
हाइब्रिड सर्च: दो सिग्नल, एक रैंक
जब "withRetry timeout errors" जैसी क्वेरी सर्च बॉक्स में हिट करती है, तो वह दो तरह के इंटेंट ले जाती है: सटीक सिंबल नाम जो यूज़र जानता है (withRetry) और एक कॉन्सेप्चुअल डिस्क्रिप्शन (timeout errors)। कोई एक प्रिमिटिव दोनों को कवर नहीं करता। FTS और वेक्टर सर्च को समानांतर में चलाना — फिर Reciprocal Rank Fusion से उनकी रैंक की गई सूचियों को मर्ज करना — यह काम करता है।
RRF हर नतीजे को हर सूची में 1 / (60 + rank) के रूप में स्कोर करता है और सूचियों में जोड़ता है। स्थिरांक 60 टॉप रैंक के फ़ायदे को कम करता है, ताकि एक नतीजा जो दोनों सूचियों में दूसरे स्थान पर आए, वह उस नतीजे को हरा सके जो एक सूची जीतता है और दूसरी में पूरी तरह चूक जाता है। महत्वपूर्ण रूप से, RRF कभी भी कच्चे स्कोर को मेथड्स के बीच औसत नहीं करता — FTS रैंक और कोसाइन डिस्टेंस अलग करेंसी हैं और उन्हें अंकगणितीय रूप से नहीं जोड़ा जा सकता।
-- हाइब्रिड सर्च: 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_scoreFROM fts FULL JOIN vec ON fts.id = vec.idORDER BY rrf_score DESCLIMIT 10;प्रति ब्रांच 60-डॉक्यूमेंट कैंडिडेट पूल (LIMIT 60) एक आम शुरुआती बिंदु है। यदि रिकॉल कम है तो इसे बढ़ाएं; स्पीड के लिए घटाएं।
आगे क्या है
Postgres टेक्स्ट सर्च काफ़ी ज़मीन कवर करता है, लेकिन इसकी एक सीमा है। जब यूज़र किसी चीज़ का नाम नहीं बल्कि उसे डिस्क्राइब करते हैं — “कुछ ऐसा जो मुझे फ़्लाइट में सोने में मदद करे,” “नए इंजीनियर के रूप में डिबगिंग कॉन्फ़िडेंस पर आर्टिकल” — लेक्सिकल और ट्राइग्राम सर्च दोनों फ़ेल हो जाते हैं।
यह वेक्टर एम्बेडिंग्स, सेमेंटिक सर्च और हाइब्रिड आर्किटेक्चर का इलाका है। Semantic Vector Search and Hybrid Strategies में कवर किया गया है।