जबकि मैं अन्य टिप्पणीकारों से सहमत हूं कि यह एक कम्प्यूटेशनल रूप से महंगी समस्या है, मुझे लगता है कि आपके द्वारा उपयोग किए जा रहे एसक्यूएल को ट्वीक करके सुधार के लिए बहुत जगह है। वर्णन करने के लिए, मैं 15MM नामों और 3K वाक्यांशों के साथ एक नकली डेटा सेट बनाता हूं, पुराने दृष्टिकोण को चलाया, और एक नया दृष्टिकोण चलाया।
एक नकली डेटा सेट उत्पन्न करने के लिए पूरी स्क्रिप्ट और नए दृष्टिकोण की कोशिश करें
टी एल; डॉ
मेरी मशीन और इस नकली डेटा सेट पर, मूल दृष्टिकोण को चलने में लगभग 4 घंटे लगते हैं। प्रस्तावित नए दृष्टिकोण में लगभग 10 मिनट लगते हैं , काफी सुधार हुआ है। यहाँ प्रस्तावित दृष्टिकोण का एक संक्षिप्त सारांश है:
- प्रत्येक नाम के लिए, प्रत्येक वर्ण ऑफसेट पर शुरू होने वाली सबस्ट्रिंग उत्पन्न करें (और अनुकूलन के रूप में सबसे लंबे समय तक खराब वाक्यांश की लंबाई पर छाया हुआ)
- इन सबस्ट्रिंग पर क्लस्टर इंडेक्स बनाएं
- प्रत्येक बुरे वाक्यांश के लिए, किसी भी मैच की पहचान करने के लिए इन सबस्ट्रिंग में तलाश करें
- प्रत्येक मूल स्ट्रिंग के लिए, उस स्ट्रिंग के एक या अधिक सबस्ट्रिंग से मेल खाने वाले विभिन्न बुरे वाक्यांशों की संख्या की गणना करें
मूल दृष्टिकोण: एल्गोरिथम विश्लेषण
मूल UPDATE
कथन की योजना से , हम देख सकते हैं कि काम की राशि दोनों नामों की संख्या (15 मिमी) और वाक्यांशों की संख्या (3K) दोनों के लिए आनुपातिक रूप से आनुपातिक है। इसलिए यदि हम नामों और वाक्यांशों की संख्या 10 से कई गुणा करते हैं, तो कुल रन समय ~ 100 गुना धीमा होने वाला है।
क्वेरी वास्तव में की लंबाई के समानुपाती है name
; जबकि यह क्वेरी प्लान में थोड़ा छिपा हुआ है, यह टेबल स्पूल में मांगने के लिए "निष्पादन की संख्या" में आता है। वास्तविक योजना में, हम यह देख सकते हैं कि यह न केवल एक बार होता है name
, बल्कि वास्तव में एक बार प्रति वर्ण ऑफसेट होता है name
। तो यह दृष्टिकोण रन-टाइम जटिलता में ओ ( # names
* # phrases
* name length
) है।
नया तरीका: कोड
यह कोड पूर्ण pastebin में भी उपलब्ध है लेकिन मैंने इसे सुविधा के लिए यहाँ कॉपी किया है। पास्टबिन की पूरी प्रक्रिया परिभाषा भी है, जिसमें वर्तमान बैच की सीमाओं को परिभाषित करने के लिए नीचे दिए गए चर @minId
और @maxId
चर शामिल हैं।
-- For each name, generate the string at each offset
DECLARE @maxBadPhraseLen INT = (SELECT MAX(LEN(phrase)) FROM Bad_Phrase)
SELECT s.id, sub.sub_name
INTO #SubNames
FROM (SELECT * FROM SourceTable WHERE id BETWEEN @minId AND @maxId) s
CROSS APPLY (
-- Create a row for each substring of the name, starting at each character
-- offset within that string. For example, if the name is "abcd", this CROSS APPLY
-- will generate 4 rows, with values ("abcd"), ("bcd"), ("cd"), and ("d"). In order
-- for the name to be LIKE the bad phrase, the bad phrase must match the leading X
-- characters (where X is the length of the bad phrase) of at least one of these
-- substrings. This can be efficiently computed after indexing the substrings.
-- As an optimization, we only store @maxBadPhraseLen characters rather than
-- storing the full remainder of the name from each offset; all other characters are
-- simply extra space that isn't needed to determine whether a bad phrase matches.
SELECT TOP(LEN(s.name)) SUBSTRING(s.name, n.n, @maxBadPhraseLen) AS sub_name
FROM Numbers n
ORDER BY n.n
) sub
-- Create an index so that bad phrases can be quickly compared for a match
CREATE CLUSTERED INDEX IX_SubNames ON #SubNames (sub_name)
-- For each name, compute the number of distinct bad phrases that match
-- By "match", we mean that the a substring starting from one or more
-- character offsets of the overall name starts with the bad phrase
SELECT s.id, COUNT(DISTINCT b.phrase) AS bad_count
INTO #tempBadCounts
FROM dbo.Bad_Phrase b
JOIN #SubNames s
ON s.sub_name LIKE b.phrase + '%'
GROUP BY s.id
-- Perform the actual update into a "bad_count_new" field
-- For validation, we'll compare bad_count_new with the originally computed bad_count
UPDATE s
SET s.bad_count_new = COALESCE(b.bad_count, 0)
FROM dbo.SourceTable s
LEFT JOIN #tempBadCounts b
ON b.id = s.id
WHERE s.id BETWEEN @minId AND @maxId
नया तरीका: क्वेरी प्लान
सबसे पहले, हम प्रत्येक वर्ण ऑफसेट पर शुरू होने वाले सबस्ट्रिंग को उत्पन्न करते हैं
फिर इन सबस्ट्रिंग पर एक क्लस्टर इंडेक्स बनाएं
अब, प्रत्येक बुरे वाक्यांश के लिए हम किसी भी मैच की पहचान करने के लिए इन सबस्ट्रिंग्स की तलाश करते हैं। फिर हम उस स्ट्रिंग के एक या अधिक सबस्ट्रिंग से मेल खाने वाले अलग-अलग बुरे वाक्यांशों की संख्या की गणना करते हैं। यह वास्तव में महत्वपूर्ण कदम है; जिस तरह से हमने सबस्ट्रिंग को अनुक्रमित किया है, उसके कारण, हमें अब खराब वाक्यांशों और नामों के पूर्ण क्रॉस-उत्पाद की जांच नहीं करनी है। यह कदम, जो वास्तविक गणना करता है, वास्तविक रन-टाइम के लगभग 10% के लिए जिम्मेदार है (शेष सबस्ट्रिंग का पूर्व-प्रसंस्करण है)।
अंत में, LEFT OUTER JOIN
किसी भी नाम के लिए 0 की गिनती निर्दिष्ट करने के लिए वास्तविक अपडेट स्टेटमेंट का उपयोग करें, जिसके लिए हमें कोई भी खराब वाक्यांश नहीं मिला।
नया दृष्टिकोण: एल्गोरिथम विश्लेषण
नए दृष्टिकोण को दो चरणों में विभाजित किया जा सकता है, पूर्व-प्रसंस्करण और मिलान। आइए निम्नलिखित चर को परिभाषित करें:
N
= # नामों का
B
= # बुरे वाक्यांशों का
L
= औसत नाम लंबाई, वर्णों में
प्री-प्रोसेसिंग चरण O(N*L * LOG(N*L))
बनाने के लिए हैN*L
सब्सट्रिंग और फिर उन्हें सॉर्ट करने के लिए है।
वास्तविक मिलान है O(B * LOG(N*L))
प्रत्येक बुरे वाक्यांश के लिए सबस्ट्रिंग में तलाश करने के लिए है।
इस तरह, हमने एक एल्गोरिथ्म बनाया है जो कि खराब वाक्यांशों की संख्या के साथ रैखिक रूप से स्केल नहीं करता है, एक प्रमुख प्रदर्शन अनलॉक होता है, जैसा कि हम 3K वाक्यांशों और उससे परे के पैमाने पर करते हैं। एक और तरीका कहा, मूल कार्यान्वयन में लगभग 10x लगते हैं जब तक हम 300 बुरे वाक्यांशों से 3K बुरे वाक्यांशों पर जाते हैं। इसी तरह जब तक हम 3K खराब वाक्यांशों से 30K तक जाने के लिए एक और 10x लगेंगे। नए कार्यान्वयन, हालांकि, उप-रैखिक रूप से बड़े पैमाने पर होगा और वास्तव में 2x से कम समय लेता है जो 3K खराब वाक्यांशों पर मापा जाता है जब 30K खराब वाक्यांशों तक बढ़ाया जाता है।
मान्यताओं / Caveats
- मैं समग्र कार्य को मामूली आकार के बैचों में विभाजित कर रहा हूं। यह शायद या तो दृष्टिकोण के लिए एक अच्छा विचार है, लेकिन नए दृष्टिकोण के लिए यह विशेष रूप से महत्वपूर्ण है ताकि
SORT
सबस्ट्रिंग पर प्रत्येक बैच के लिए स्वतंत्र हो और आसानी से स्मृति में फिट हो। आप आवश्यकतानुसार बैच आकार में हेरफेर कर सकते हैं, लेकिन एक बैच में सभी 15 मिमी पंक्तियों को आज़माना बुद्धिमानी नहीं होगी।
- मैं SQL 2014 पर हूँ, SQL 2005 नहीं, क्योंकि मेरे पास SQL 2005 मशीन नहीं है। मैं SQL 2005 में उपलब्ध किसी भी वाक्यविन्यास का उपयोग नहीं करने के लिए सावधान रहा, लेकिन मुझे अभी भी SQL 2012 में tempdb आलसी लेखन सुविधा और SQL 2014 में समानांतर चयन INTO सुविधा से लाभ मिल सकता है ।
- दोनों नामों और वाक्यांशों की लंबाई नए दृष्टिकोण के लिए काफी महत्वपूर्ण है। मैं मान रहा हूं कि खराब वाक्यांश आमतौर पर काफी कम होते हैं क्योंकि वास्तविक दुनिया के उपयोग के मामलों से मेल खाने की संभावना है। नाम बुरे वाक्यांशों की तुलना में थोड़ा लंबा है, लेकिन माना जाता है कि यह हजारों वर्ण नहीं हैं। मुझे लगता है कि यह एक उचित धारणा है, और लंबे समय तक नाम के तार आपके मूल दृष्टिकोण को धीमा कर देंगे।
- सुधार का कुछ हिस्सा (लेकिन कहीं भी यह सभी के करीब है) इस तथ्य के कारण है कि नया दृष्टिकोण पुराने दृष्टिकोण (जो एकल-थ्रेडेड चलाता है) की तुलना में अधिक प्रभावी ढंग से समानता का लाभ उठा सकता है। मैं एक क्वाड कोर लैपटॉप पर हूं, इसलिए यह दृष्टिकोण अच्छा है जो इन कोर को उपयोग करने के लिए रख सकता है।
संबंधित ब्लॉग पोस्ट
हारून बर्ट्रेंड ने अपने ब्लॉग पोस्ट में इस तरह के समाधान की अधिक विस्तार से खोज की एक प्रमुख% वाइल्डकार्ड के लिए एक सूचकांक प्राप्त करने का एक तरीका ।