सबसे लंबे समय तक उपसर्ग खोजने के लिए एल्गोरिथम


11

मेरे पास दो टेबल हैं।

पहले एक उपसर्गों के साथ एक तालिका है

code name price
343  ek1   10
3435 nt     4
3432 ek2    2

दूसरा फोन नंबर के साथ कॉल रिकॉर्ड है

number        time
834353212     10
834321242     20
834312345     30

मुझे एक स्क्रिप्ट लिखने की ज़रूरत है, जो प्रत्येक रिकॉर्ड के लिए उपसर्गों में से सबसे लंबे समय तक उपसर्ग ढूंढे, और यह सब डेटा इस तरह से तीसरे तालिका में लिखें:

 number        code   ....
 834353212     3435
 834321242     3432
 834312345     343

संख्या 834353212 के लिए हमें '8' को ट्रिम करना होगा, और फिर उपसर्ग तालिका से सबसे लंबे कोड को खोजना होगा, इसके 3435.
हमें हमेशा पहले '8' को छोड़ना चाहिए और उपसर्ग को शुरुआत में होना चाहिए।

मैंने इस कार्य को बहुत समय पहले, बहुत बुरे तरीके से हल किया था। इसकी भयानक पर्ल स्क्रिप्ट थी जो प्रत्येक रिकॉर्ड के लिए बहुत सारे प्रश्न करती है। यह स्क्रिप्ट:

  1. कॉल टेबल से एक नंबर लें, लूप में लंबाई (संख्या) से 1 => $ उपसर्ग तक सबस्टिट्यूट करें

  2. क्वेरी करें: उपसर्गों से गिनती (*) चुनें जहां कोड '$ प्रीफिक्स' की तरह है

  3. यदि गिनती> 0 तो पहले उपसर्ग लें और तालिका में लिखें

पहली समस्या क्वेरी काउंट - यह है call_records * length(number)। दूसरी समस्या LIKEअभिव्यक्ति की है। मुझे डर है कि वे धीमे हैं।

मैंने दूसरी समस्या को हल करने की कोशिश की:

CREATE EXTENSION pg_trgm;
CREATE INDEX prefix_idx ON prefix USING gist (code gist_trgm_ops);

यह प्रत्येक क्वेरी को गति देता है, लेकिन सामान्य रूप से समस्या को हल नहीं करता है।

मेरे पास अब 20k उपसर्ग और 170k संख्या हैं, और मेरा पुराना समाधान खराब है। लगता है कि मुझे बिना लूप के कुछ नए समाधान की आवश्यकता है।

प्रत्येक कॉल रिकॉर्ड के लिए केवल एक क्वेरी या ऐसा कुछ।


2
मुझे यकीन नहीं है कि अगर codeपहली तालिका में बाद में उपसर्ग के समान है। क्या आप कृपया इसे स्पष्ट कर सकते हैं? और उदाहरण डेटा और वांछित आउटपुट के कुछ फिक्सिंग (ताकि आपकी समस्या का पालन करना आसान हो) भी स्वागत योग्य होगा।
dezso

हां। आप सही। मैं '8' के बारे में लिखना भूल गया था। धन्यवाद।
कोराजविन इवान

2
उपसर्ग शुरुआत में है, है ना?
dezso

हाँ। दूसरे स्थान से। 8 $ उपसर्ग $ संख्या
कोरजाविन इवान

आपके टेबल की कार्डिनैलिटी क्या है? 100k नंबर? कितने उपसर्ग हैं?
एरविन ब्रान्डेसटेटर

जवाबों:


21

मैं textसंबंधित स्तंभों के लिए डेटा प्रकार मान रहा हूं ।

CREATE TABLE prefix (code text, name text, price int);
CREATE TABLE num (number text, time int);

"सरल" समाधान

SELECT DISTINCT ON (1)
       n.number, p.code
FROM   num n
JOIN   prefix p ON right(n.number, -1) LIKE (p.code || '%')
ORDER  BY n.number, p.code DESC;

महत्वपूर्ण तत्व:

DISTINCT ONSQL मानक का एक Postgres एक्सटेंशन है DISTINCTSO पर इस संबंधित उत्तर में प्रयुक्त क्वेरी तकनीक के लिए एक विस्तृत विवरण प्राप्त करें ।
ORDER BY p.code DESCसबसे लंबे समय तक मैच चुनता है, क्योंकि '1234'हर तरह के बाद '123'(आरोही क्रम में)।

सरल एसक्यूएल फिडल

इंडेक्स के बिना, क्वेरी बहुत लंबे समय तक चलेगी (इसे पूरा देखने के लिए इंतजार न करें)। इस उपवास को बनाने के लिए, आपको सूचकांक समर्थन की आवश्यकता है। अतिरिक्त मॉड्यूल द्वारा आपूर्ति किए गए आपके द्वारा उल्लिखित ट्रिग्राम इंडेक्स pg_trgmएक अच्छे उम्मीदवार हैं। आपको GIN और GiST इंडेक्स में से किसी एक को चुनना होगा। संख्याओं का पहला वर्ण सिर्फ शोर है और इसे सूचकांक से बाहर रखा जा सकता है, जिससे यह एक कार्यात्मक सूचकांक बन जाता है।
मेरे परीक्षणों में, एक कार्यात्मक ट्रिग्राम जीआईएन इंडेक्स ने ट्रिग्राम जीईएसटी इंडेक्स (उम्मीद के मुताबिक) पर रेस जीती:

CREATE INDEX num_trgm_gin_idx ON num USING gin (right(number, -1) gin_trgm_ops);

उन्नत dbfiddle यहाँ

सभी परीक्षा परिणाम एक स्थानीय पोस्टग्रैड से होते हैं। एक कम सेटअप के साथ 9.1 टेस्ट इंस्टॉलेशन: 17k नंबर और 2k कोड:

  • कुल रनटाइम: 1719.552 एमएस (ट्राइग्राम जीएसटी)
  • कुल रनटाइम: 912.329 एमएस ( ट्रिम जीआईएन )

बहुत तेजी से अभी तक

के साथ विफल प्रयास text_pattern_ops

एक बार जब हम विचलित करने वाले पहले शोर वाले चरित्र को अनदेखा कर देते हैं, तो यह बुनियादी बाक़ी लंगर पैटर्न पैटर्न के नीचे आ जाता है । इसलिए मैंने ऑपरेटर वर्गtext_pattern_ops (स्तंभ प्रकार मानकर text) के साथ एक कार्यात्मक बी-ट्री इंडेक्स की कोशिश की ।

CREATE INDEX num_text_pattern_idx ON num(right(number, -1) text_pattern_ops);

यह एकल खोज शब्द के साथ प्रत्यक्ष प्रश्नों के लिए उत्कृष्ट रूप से काम करता है और इससे ट्रायग्राम इंडेक्स तुलना में खराब दिखता है:

SELECT * FROM num WHERE right(number, -1) LIKE '2345%'
  • कुल रनटाइम: 3.816 एमएस (trgm_gin_idx)
  • कुल रनटाइम: 0.147 एमएस (text_pattern_idx)

हालांकि , क्वेरी प्लानर इस सूचकांक को दो तालिकाओं में शामिल होने के लिए नहीं मानेंगे। मैंने यह सीमा पहले भी देखी है। मेरे पास इसके लिए कोई सार्थक स्पष्टीकरण नहीं है, फिर भी।

आंशिक / कार्यात्मक बी-ट्री इंडेक्स

इसका विकल्प यह है कि आंशिक सूचकांक के साथ आंशिक तारों पर समानता की जांच का उपयोग किया जाए। यह एक में इस्तेमाल किया जा सकता है JOIN

चूँकि हम आम तौर पर केवल different lengthsउपसर्गों के लिए सीमित संख्या में होते हैं , हम आंशिक अनुक्रमित के साथ यहां प्रस्तुत समाधान के समान निर्माण कर सकते हैं।

कहते हैं, हमारे पास 1 से 5 अक्षर तक के उपसर्ग हैं । प्रत्येक आंशिक उपसर्ग लंबाई के लिए एक आंशिक कार्यात्मक सूचकांक की संख्या बनाएँ:

CREATE INDEX prefix_code_idx5 ON prefix(code) WHERE length(code) = 5;
CREATE INDEX prefix_code_idx4 ON prefix(code) WHERE length(code) = 4;
CREATE INDEX prefix_code_idx3 ON prefix(code) WHERE length(code) = 3;
CREATE INDEX prefix_code_idx2 ON prefix(code) WHERE length(code) = 2;
CREATE INDEX prefix_code_idx1 ON prefix(code) WHERE length(code) = 1;

चूंकि ये आंशिक सूचकांक हैं, इसलिए ये सभी एक पूर्ण सूचकांक से मुश्किल से बड़े हैं।

संख्याओं के लिए अनुक्रमणिका जोड़ें (खाते में अग्रणी शोर चरित्र को लेते हुए):

CREATE INDEX num_number_idx5 ON num(substring(number, 2, 5)) WHERE length(number) >= 6;
CREATE INDEX num_number_idx4 ON num(substring(number, 2, 4)) WHERE length(number) >= 5;
CREATE INDEX num_number_idx3 ON num(substring(number, 2, 3)) WHERE length(number) >= 4;
CREATE INDEX num_number_idx2 ON num(substring(number, 2, 2)) WHERE length(number) >= 3;
CREATE INDEX num_number_idx1 ON num(substring(number, 2, 1)) WHERE length(number) >= 2;

जबकि ये अनुक्रमणिका केवल प्रत्येक को प्रतिस्थापित करती है और आंशिक होती है, प्रत्येक तालिका के अधिकांश या सभी को कवर करती है। इसलिए वे एक ही कुल सूचकांक की तुलना में बहुत बड़े हैं - लंबी संख्या को छोड़कर। और वे लिखने के संचालन के लिए अधिक काम करते हैं। यह अद्भुत गति के लिए लागत है

यदि आपके लिए यह लागत बहुत अधिक है (लेखन प्रदर्शन महत्वपूर्ण है / बहुत सारे लेखन संचालन / डिस्क स्थान एक समस्या), तो आप इन अनुक्रमों को छोड़ सकते हैं। बाकी अभी भी तेज है, अगर उतना तेज नहीं है जितना कि यह हो सकता है ...

यदि संख्याएँ कभी कम नहीं nहोती हैं, तो वर्ण, WHEREकुछ या सभी से निरर्थक खंडों को छोड़ देते हैं, और WHEREनिम्नलिखित सभी प्रश्नों से संबंधित खंड को भी छोड़ देते हैं ।

पुनरावर्ती CTE

सभी सेटअप के साथ अब तक मैं एक पुनरावर्ती CTE के साथ बहुत ही सुंदर समाधान की उम्मीद कर रहा था :

WITH RECURSIVE cte AS (
   SELECT n.number, p.code, 4 AS len
   FROM   num n
   LEFT    JOIN prefix p
            ON  substring(number, 2, 5) = p.code
            AND length(n.number) >= 6  -- incl. noise character
            AND length(p.code) = 5

   UNION ALL 
   SELECT c.number, p.code, len - 1
   FROM    cte c
   LEFT   JOIN prefix p
            ON  substring(number, 2, c.len) = p.code
            AND length(c.number) >= c.len+1  -- incl. noise character
            AND length(p.code) = c.len
   WHERE    c.len > 0
   AND    c.code IS NULL
   )
SELECT number, code
FROM   cte
WHERE  code IS NOT NULL;
  • कुल रनटाइम: 1045.115 एमएस

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

यूनिअन ऑल

चूंकि हम कम संख्या में पुनरावृत्ति से निपट रहे हैं, इसलिए हम उन्हें पुनरावृत्त कर सकते हैं। यह उनमें से प्रत्येक के लिए अनुकूलित योजनाओं की अनुमति देता है। (हम पहले से ही सफल संख्या के पुनरावर्ती बहिष्कार को खो देते हैं, हालांकि। अभी भी सुधार के लिए कुछ जगह है, खासकर उपसर्ग लंबाई की एक विस्तृत श्रृंखला के लिए):

SELECT DISTINCT ON (1) number, code
FROM  (
   SELECT n.number, p.code
   FROM   num n
   JOIN   prefix p
            ON  substring(number, 2, 5) = p.code
            AND length(n.number) >= 6  -- incl. noise character
            AND length(p.code) = 5
   UNION ALL 
   SELECT n.number, p.code
   FROM   num n
   JOIN   prefix p
            ON  substring(number, 2, 4) = p.code
            AND length(n.number) >= 5
            AND length(p.code) = 4
   UNION ALL 
   SELECT n.number, p.code
   FROM   num n
   JOIN   prefix p
            ON  substring(number, 2, 3) = p.code
            AND length(n.number) >= 4
            AND length(p.code) = 3
   UNION ALL 
   SELECT n.number, p.code
   FROM   num n
   JOIN   prefix p
            ON  substring(number, 2, 2) = p.code
            AND length(n.number) >= 3
            AND length(p.code) = 2
   UNION ALL 
   SELECT n.number, p.code
   FROM   num n
   JOIN   prefix p
            ON  substring(number, 2, 1) = p.code
            AND length(n.number) >= 2
            AND length(p.code) = 1
   ) x
ORDER BY number, code DESC;
  • कुल रनटाइम: 57.578 एमएस (!!)

एक सफलता, आखिर!

SQL फ़ंक्शन

इसे SQL फ़ंक्शन में लपेटने से बार-बार उपयोग के लिए क्वेरी प्लानिंग ओवरहेड को हटा दिया जाता है:

CREATE OR REPLACE FUNCTION f_longest_prefix()
  RETURNS TABLE (number text, code text) LANGUAGE sql AS
$func$
SELECT DISTINCT ON (1) number, code
FROM  (
   SELECT n.number, p.code
   FROM   num n
   JOIN   prefix p
            ON  substring(number, 2, 5) = p.code
            AND length(n.number) >= 6  -- incl. noise character
            AND length(p.code) = 5
   UNION ALL 
   SELECT n.number, p.code
   FROM   num n
   JOIN   prefix p
            ON  substring(number, 2, 4) = p.code
            AND length(n.number) >= 5
            AND length(p.code) = 4
   UNION ALL 
   SELECT n.number, p.code
   FROM   num n
   JOIN   prefix p
            ON  substring(number, 2, 3) = p.code
            AND length(n.number) >= 4
            AND length(p.code) = 3
   UNION ALL 
   SELECT n.number, p.code
   FROM   num n
   JOIN   prefix p
            ON  substring(number, 2, 2) = p.code
            AND length(n.number) >= 3
            AND length(p.code) = 2
   UNION ALL 
   SELECT n.number, p.code
   FROM   num n
   JOIN   prefix p
            ON  substring(number, 2, 1) = p.code
            AND length(n.number) >= 2
            AND length(p.code) = 1
   ) x
ORDER BY number, code DESC
$func$;

कॉल करें:

SELECT * FROM f_longest_prefix_sql();
  • कुल रनटाइम: 17.138 एमएस (!!!)

गतिशील SQL के साथ PL / pgSQL फ़ंक्शन

यह plpgsql फ़ंक्शन ऊपर के पुनरावर्ती CTE की तरह है, लेकिन गतिशील SQL EXECUTEक्वेरी के साथ हर पुनरावृत्ति के लिए फिर से नियोजित होने के लिए मजबूर करता है। अब यह सभी सिलसिलेवार अनुक्रमित का उपयोग करता है।

इसके अतिरिक्त यह उपसर्ग लंबाई की किसी भी सीमा के लिए काम करता है । फ़ंक्शन रेंज के लिए दो पैरामीटर लेता है, लेकिन मैंने इसे DEFAULTमानों के साथ तैयार किया है, इसलिए यह स्पष्ट मापदंडों के बिना भी काम करता है:

CREATE OR REPLACE FUNCTION f_longest_prefix2(_min int = 1, _max int = 5)
  RETURNS TABLE (number text, code text) LANGUAGE plpgsql AS
$func$
BEGIN
FOR i IN REVERSE _max .. _min LOOP  -- longer matches first
   RETURN QUERY EXECUTE '
   SELECT n.number, p.code
   FROM   num n
   JOIN   prefix p
            ON  substring(n.number, 2, $1) = p.code
            AND length(n.number) >= $1+1  -- incl. noise character
            AND length(p.code) = $1'
   USING i;
END LOOP;
END
$func$;

अंतिम चरण को आसानी से एक फ़ंक्शन में नहीं लपेटा जा सकता है। या तो बस इसे इस तरह कहें:

SELECT DISTINCT ON (1)
       number, code
FROM   f_longest_prefix_prefix2() x
ORDER  BY number, code DESC;
  • कुल रनटाइम: 27.413 एमएस

या आवरण के रूप में किसी अन्य SQL फ़ंक्शन का उपयोग करें:

CREATE OR REPLACE FUNCTION f_longest_prefix3(_min int = 1, _max int = 5)
  RETURNS TABLE (number text, code text) LANGUAGE sql AS
$func$
SELECT DISTINCT ON (1)
       number, code
FROM   f_longest_prefix_prefix2($1, $2) x
ORDER  BY number, code DESC
$func$;

कॉल करें:

SELECT * FROM f_longest_prefix3();
  • कुल रनटाइम: 37.622 एमएस

ओवरहेड की योजना के कारण थोड़ा धीमा। लेकिन SQL से अधिक बहुमुखी और लंबे समय तक उपसर्गों के लिए छोटा है।


मैं अभी भी जाँच कर रहा हूँ, लेकिन बहुत अच्छा लग रहा है! आपका विचार ऑपरेटर की तरह "रिवर्स" - शानदार। मैं इतना मूर्ख क्यों था? (
कोरजाविन इवान

5
Whoah! यह काफी संपादित है। काश मैं फिर से उत्थान कर सकता।
स्वैसे

3
मैं पिछले दो वर्षों से आपके अद्भुत उत्तर से सीखता हूं। 17-30 एमएस मेरे लूप समाधान में कई घंटों के खिलाफ है? एक जादू है।
कोराजविन इवान

1
@KorjavinIvan: खैर, जैसा कि प्रलेखित है, मैंने 2k उपसर्गों / 17k संख्याओं के कम सेटअप के साथ परीक्षण किया। लेकिन यह बहुत अच्छी तरह से पैमाने पर होना चाहिए और मेरी परीक्षण मशीन एक छोटा सर्वर था। इसलिए आपको अपने वास्तविक जीवन के मामले में एक सेकंड के भीतर अच्छी तरह से रहना चाहिए।
एरविन ब्रान्डेसटेटर

1
अच्छा जवाब ... क्या आप डिमित्रि के उपसर्ग विस्तार को जानते हैं ? क्या आप इसे अपने परीक्षण मामलों की तुलना में शामिल कर सकते हैं?
माथेउसोएल

0

एक स्ट्रिंग S, स्ट्रिंग T का एक उपसर्ग है। यदि S और SZ के बीच T है, तो Z किसी अन्य स्ट्रिंग की तुलना में लेक्सिक रूप से बड़ा है (उदाहरण के लिए, डेटासेट में सबसे लंबे समय तक संभव फोन नंबर को पार करने के लिए पर्याप्त 9 के साथ 99999999, या कभी-कभी TXFF काम करेगा)।

किसी भी T के लिए सबसे लंबा सामान्य उपसर्ग भी lexicographically maximal है, इसलिए एक साधारण समूह और अधिकतम इसे खोज लेंगे।

select n.number, max(p.code) 
from prefixes p
join numbers n 
on substring(n.number, 2, 255) between p.code and p.code || '99999999'
group by n.number

यदि यह धीमा है, तो यह संकलित अभिव्यक्तियों के कारण होने की संभावना है, इसलिए आप अपने स्वयं के सूचकांक, आदि के साथ कोड तालिका में एक कॉलम में p.code || '999999' को भी भौतिक बनाने की कोशिश कर सकते हैं।

हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.