एक एसक्यूएल खंड में परिमाणित करें


1041

मैं INइस तरह एक तर्क के एक चर संख्या के साथ एक खंड युक्त क्वेरी को कैसे मानकीकृत कर सकता हूं ?

SELECT * FROM Tags 
WHERE Name IN ('ruby','rails','scruffy','rubyonrails')
ORDER BY Count DESC

इस प्रश्न में, तर्कों की संख्या 1 से 5 तक कहीं भी हो सकती है।

मैं इस (या एक्सएमएल) के लिए एक समर्पित संग्रहित प्रक्रिया का उपयोग नहीं करना पसंद करूंगा, लेकिन अगर SQL Server 2008 के लिए विशिष्ट कुछ सुंदर तरीका है , तो मैं इसके लिए खुला हूं।



जवाबों:


316

यहाँ एक त्वरित और गंदी तकनीक है जिसका मैंने उपयोग किया है:

SELECT * FROM Tags
WHERE '|ruby|rails|scruffy|rubyonrails|'
LIKE '%|' + Name + '|%'

तो यहाँ C # कोड है:

string[] tags = new string[] { "ruby", "rails", "scruffy", "rubyonrails" };
const string cmdText = "select * from tags where '|' + @tags + '|' like '%|' + Name + '|%'";

using (SqlCommand cmd = new SqlCommand(cmdText)) {
   cmd.Parameters.AddWithValue("@tags", string.Join("|", tags);
}

दो कैविएट:

  • प्रदर्शन बहुत ही भयानक है। LIKE "%...%"प्रश्नों को अनुक्रमित नहीं किया जाता है।
  • सुनिश्चित करें कि आपके पास कोई |, रिक्त या रिक्त टैग नहीं है या यह काम नहीं करेगा

इसे पूरा करने के अन्य तरीके हैं जो कुछ लोग क्लीनर पर विचार कर सकते हैं, इसलिए कृपया पढ़ते रहें।


119
यह धीमी गति से होगा
मैट रोजिश

13
हां, यह एक टेबल स्कैन है। 10 पंक्तियों के लिए महान, 100,000 के लिए घटिया।
विल हार्टुंग

17
सुनिश्चित करें कि आप उन टैगों पर परीक्षण करें जिनके पास पाइप हैं।
जोएल कोएहॉर्न

17
यह भी सवाल का जवाब नहीं है। दी गई, यह देखना आसान है कि मापदंडों को कहां जोड़ा जाए, लेकिन आप इस समाधान को कैसे स्वीकार कर सकते हैं यदि यह क्वेरी को पैरामीटर करने के लिए परेशान नहीं करता है? यह केवल @Mark Brackett की तुलना में सरल दिखता है क्योंकि यह पैरामीटर नहीं किया गया है।
tvanfosson

21
अगर आपका टैग 'रूबी | रेल्स' है तो क्या होगा यह मैच होगा, जो गलत होगा। जब आप ऐसे समाधान निकालते हैं, तो आपको या तो यह सुनिश्चित करने की आवश्यकता होती है कि टैग में पाइप न हों, या स्पष्ट रूप से उन्हें फ़िल्टर न करें: * टैग के लिए * का चयन करें जहां 'रूबी | '% |' + नाम + '|%' और नाम '%!%' की तरह नहीं है
AK

729

आप प्रत्येक मान को मानकीकृत कर सकते हैं , इसलिए कुछ इस तरह है:

string[] tags = new string[] { "ruby", "rails", "scruffy", "rubyonrails" };
string cmdText = "SELECT * FROM Tags WHERE Name IN ({0})";

string[] paramNames = tags.Select(
    (s, i) => "@tag" + i.ToString()
).ToArray();

string inClause = string.Join(", ", paramNames);
using (SqlCommand cmd = new SqlCommand(string.Format(cmdText, inClause))) {
    for(int i = 0; i < paramNames.Length; i++) {
       cmd.Parameters.AddWithValue(paramNames[i], tags[i]);
    }
}

जो आपको देगा:

cmd.CommandText = "SELECT * FROM Tags WHERE Name IN (@tag0, @tag1, @tag2, @tag3)"
cmd.Parameters["@tag0"] = "ruby"
cmd.Parameters["@tag1"] = "rails"
cmd.Parameters["@tag2"] = "scruffy"
cmd.Parameters["@tag3"] = "rubyonrails"

नहीं, यह SQL इंजेक्शन के लिए खुला नहीं है । CommandText में एकमात्र इंजेक्ट किया गया पाठ उपयोगकर्ता इनपुट पर आधारित नहीं है। यह पूरी तरह से हार्डकोड "@tag" उपसर्ग, और एक सरणी के सूचकांक पर आधारित है। सूचकांक हमेशा एक पूर्णांक होगा, उपयोगकर्ता उत्पन्न नहीं है, और सुरक्षित है।

उपयोगकर्ता इनपुट किए गए मान अभी भी मापदंडों में भरे हुए हैं, इसलिए वहां कोई भेद्यता नहीं है।

संपादित करें:

इंजेक्शन की चिंता एक तरफ, ध्यान दें कि कमांड पाठ का निर्माण करने के लिए पैरामीटर्स की एक चर संख्या को समायोजित करने के लिए (जैसा कि ऊपर) कैश्ड प्रश्नों का लाभ उठाने के लिए SQL सर्वर की क्षमता को बाधित करता है। शुद्ध परिणाम यह है कि आप निश्चित रूप से पहली बार में मापदंडों का उपयोग करने के मूल्य को खो देते हैं (जैसा कि एसक्यूएल में केवल विधेय तारों को सम्मिलित करने का विरोध किया गया है)।

ऐसा नहीं है कि कैश की गई क्वेरी योजनाएँ मूल्यवान नहीं हैं, लेकिन IMO इस क्वेरी के लिए पर्याप्त रूप से जटिल नहीं है, ताकि इसका अधिक लाभ देखा जा सके। जबकि संकलन लागत निष्पादन लागतों (या इससे अधिक) तक पहुंच सकती है, आप अभी भी मिलीसेकंड पर बात कर रहे हैं।

यदि आपके पास पर्याप्त रैम है, तो मुझे उम्मीद है कि एसक्यूएल सर्वर शायद मापदंडों के सामान्य गणना के लिए एक योजना को कैश करेगा। मुझे लगता है कि आप हमेशा पांच पैरामीटर जोड़ सकते हैं, और अनिर्दिष्ट टैग को NULL होने दें - क्वेरी प्लान समान होना चाहिए, लेकिन यह मुझे बहुत बदसूरत लगता है और मुझे यकीन नहीं है कि यह माइक्रो-ऑप्टिमाइज़ेशन के लायक होगा (हालांकि, स्टैक ओवरफ्लो पर - यह बहुत अच्छी तरह से इसके लायक हो सकता है)।

इसके अलावा, SQL सर्वर 7 और बाद में प्रश्नों को ऑटो-पैरामीटर करेगा , इसलिए मापदंडों का उपयोग करना वास्तव में एक प्रदर्शन के दृष्टिकोण से आवश्यक नहीं है - हालांकि, यह सुरक्षा के दृष्टिकोण से महत्वपूर्ण है - विशेष रूप से उपयोगकर्ता इनपुट डेटा जैसे।


2
मूल रूप से "संबंधित" प्रश्न के लिए मेरा उत्तर और स्पष्ट रूप से सबसे अच्छा समाधान चूंकि यह व्याख्यात्मक (बहुत कठिन) के बजाय रचनात्मक और कुशल है।
tvanfosson 16

49
इसी तरह से LINQ to SQL इसे करता है, BTW
Mark Cidade

3
@Pure: इसका पूरा बिंदु SQL इंजेक्शन से बचना है, जिसे आप डायनेमिक SQL इस्तेमाल करते हैं तो आप असुरक्षित होंगे।
रे

4
डेटा का @God - हाँ, मुझे लगता है कि अगर आपको 2100 से अधिक टैग चाहिए तो आपको एक अलग समाधान की आवश्यकता होगी। लेकिन अगर टैग की औसत लंबाई <3 चार्ट थी (तो आपको एक सीमांकक के रूप में भी आवश्यक है) बसरब 2100 तक पहुंच सकता है। msdn.microsoft.com/en-us/library/ms143432.aspx
मार्क ब्रैकेट

2
@bonCodigo - आपके चयनित मान एक सरणी में हैं; आप केवल सरणी पर लूप करते हैं और प्रत्येक के लिए एक पैरामीटर (इंडेक्स के साथ प्रत्यय) जोड़ते हैं।
मार्क ब्रैकेट

249

SQL Server 2008 के लिए, आप एक तालिका मान पैरामीटर का उपयोग कर सकते हैं । यह थोड़ा सा काम है, लेकिन यह मेरी अन्य विधि की तुलना में यकीनन क्लीनर है ।

सबसे पहले, आपको एक प्रकार बनाना होगा

CREATE TYPE dbo.TagNamesTableType AS TABLE ( Name nvarchar(50) )

फिर, आपका ADO.NET कोड इस तरह दिखता है:

string[] tags = new string[] { "ruby", "rails", "scruffy", "rubyonrails" };
cmd.CommandText = "SELECT Tags.* FROM Tags JOIN @tagNames as P ON Tags.Name = P.Name";

// value must be IEnumerable<SqlDataRecord>
cmd.Parameters.AddWithValue("@tagNames", tags.AsSqlDataRecord("Name")).SqlDbType = SqlDbType.Structured;
cmd.Parameters["@tagNames"].TypeName = "dbo.TagNamesTableType";

// Extension method for converting IEnumerable<string> to IEnumerable<SqlDataRecord>
public static IEnumerable<SqlDataRecord> AsSqlDataRecord(this IEnumerable<string> values, string columnName) {
    if (values == null || !values.Any()) return null; // Annoying, but SqlClient wants null instead of 0 rows
    var firstRecord = values.First();
    var metadata = SqlMetaData.InferFromValue(firstRecord, columnName);
    return values.Select(v => 
    {
       var r = new SqlDataRecord(metadata);
       r.SetValues(v);
       return r;
    });
}

41
हमने यह परीक्षण किया है और तालिका के मान मूल्यवान हैं डीओजी धीमी हैं। यह शाब्दिक रूप से 5 प्रश्नों को निष्पादित करने के लिए तेजी से है, यह एक TVP करना है।
जेफ एटवुड

4
@JeffAtwood - क्या आपने क्वेरी को कुछ इस तरह फेरबदल करने की कोशिश की है SELECT * FROM tags WHERE tags.name IN (SELECT name from @tvp);? सिद्धांत रूप में, यह वास्तव में सबसे तेज़ दृष्टिकोण होना चाहिए। आप प्रासंगिक अनुक्रमणिका का उपयोग कर सकते हैं (उदाहरण के लिए टैग नाम पर एक सूचकांक जो INCLUDEगिनती आदर्श होगा), और एसक्यूएल सर्वर को सभी टैग और उनकी गिनती को हथियाने के लिए कुछ प्रयास करना चाहिए। योजना क्या दिखती है?
निक चामास

9
मैंने इसका परीक्षण भी किया है और यह फास्ट ऐज लाइटिंग है (एक बड़े IN स्ट्रिंग के निर्माण की तुलना में)। हालाँकि मुझे पैरामीटर सेट करने में कुछ समस्याएँ थीं, क्योंकि मुझे लगातार "Int32 से पैरामीटर मूल्य को IEnumerable`1 में बदलने में विफल रहा था।" वैसे भी, कि और यहाँ एक नमूना मैं pastebin.com/qHP05CXc
फ्रेड्रिक जोहानसन

6
@FredrikJohansson - 130 अपवोट्स में से, आप केवल वही रन हो सकते हैं जो वास्तव में इसे चलाने की कोशिश की गई हो! मैंने डॉक्स पढ़ने में गलती की है, और आपको वास्तव में IEnumerable ही नहीं, IEnumerable <SqlDataRecord> की आवश्यकता है। कोड अपडेट कर दिया गया है।
मार्क ब्रैकेट

3
@MarkBrackett अपडेट के साथ बढ़िया! वास्तव में इस कोड ने वास्तव में मेरे लिए दिन बचा लिया क्योंकि मैं एक ल्यूसीन खोज-इंडेक्स की पूर्ति कर रहा हूं और यह कभी-कभी 50.000 से अधिक रिटर्न देता है या इसलिए हिट होता है जिसे SQL सर्वर के खिलाफ दोहराए जाने की आवश्यकता होती है - इसलिए मैं int की एक सरणी बनाता हूं [] (दस्तावेज़ /) SQL कुंजी) और फिर ऊपर दिया गया कोड आता है। पूरा ओपी अब 200ms से कम समय लेता है :)
फ्रेड्रिक जोहानसन

188

मूल प्रश्न यह था कि "मैं किसी क्वेरी को कैसे मानकीकृत करूँ ..."

मुझे यहीं बता दें, कि यह मूल प्रश्न का उत्तर नहीं है । अन्य अच्छे उत्तरों में पहले से ही कुछ प्रदर्शन हैं।

इसके साथ ही, आगे बढ़ें और इस उत्तर को चिह्नित करें, इसे डाउनवोट करें, इसे एक उत्तर के रूप में चिह्नित करें ... जो कुछ भी आप मानते हैं वह सही है।

मार्क ब्रैकेट के उत्तर को उस पसंदीदा उत्तर के लिए देखें जिसे मैंने (और 231 अन्य) ने अपवोट किया था। उनके उत्तर में दिया गया दृष्टिकोण 1) बाइंड चर के प्रभावी उपयोग के लिए अनुमति देता है, और 2) उन विधेय के लिए जो कि व्यर्थ हैं।

चयनित उत्तर

मैं यहां जोल को संबोधित करना चाहता हूं, जोएल स्पोल्स्की के उत्तर में दिया गया दृष्टिकोण है, उत्तर "चयनित" सही उत्तर के रूप में।

जोएल स्पोलस्की का दृष्टिकोण चतुर है। और यह यथोचित रूप से काम करता है, यह "सामान्य" मूल्यों को देखते हुए, पूर्वानुमेय व्यवहार और पूर्वानुमेय प्रदर्शन को प्रदर्शित करने वाला है, और नाल और खाली स्ट्रिंग जैसे मानक किनारे के मामलों के साथ। और यह किसी विशेष अनुप्रयोग के लिए पर्याप्त हो सकता है।

लेकिन इस दृष्टिकोण को सामान्य बनाने के संदर्भ में, आइए अधिक अस्पष्ट कोने वाले मामलों पर भी विचार करें, जैसे कि जब Nameकॉलम में वाइल्डकार्ड वर्ण होता है (जैसा कि LIKE द्वारा मान्यता प्राप्त है।) वाइल्डकार्ड वर्ण जो मुझे सबसे अधिक उपयोग में आता है %(एक प्रतिशत संकेत है)। तो चलो अब यहाँ से निपटते हैं, और बाद में अन्य मामलों पर चलते हैं।

% चरित्र के साथ कुछ समस्याएं

के नाम मान पर विचार करें 'pe%ter'। (यहां उदाहरणों के लिए, मैं कॉलम नाम के स्थान पर शाब्दिक स्ट्रिंग मान का उपयोग करता हूं।) '' pe% ter 'के नाम मान के साथ एक पंक्ति फॉर्म की एक क्वेरी द्वारा लौटा दी जाएगी:

select ...
 where '|peanut|butter|' like '%|' + 'pe%ter' + '|%'

लेकिन अगर खोज शब्दों का क्रम उल्टा हो तो वही पंक्ति वापस नहीं होगी :

select ...
 where '|butter|peanut|' like '%|' + 'pe%ter' + '|%'

हम जो व्यवहार करते हैं, वह अजीब है। सूची में खोज शब्दों का क्रम बदलने से परिणाम सेट बदल जाता है।

यह लगभग यह कहे बिना चला जाता है कि हम pe%terमूंगफली के मक्खन से मेल नहीं खाना चाहते , चाहे वह कितना भी पसंद कर ले।

अस्पष्ट कोने का मामला

(हां, मैं इस बात को मानूंगा कि यह एक अस्पष्ट मामला है। संभवतः एक जिसका परीक्षण होने की संभावना नहीं है। हम एक कॉलम मूल्य में वाइल्डकार्ड की उम्मीद नहीं करेंगे। हम मान सकते हैं कि एप्लिकेशन ऐसे मूल्य को संग्रहीत होने से रोकता है। लेकिन मेरे अनुभव में, मैंने शायद ही कभी एक डेटाबेस बाधा देखी है जो विशेष रूप से वर्ण या पैटर्न को अस्वीकार कर दिया था जिसे एक LIKEतुलना ऑपरेटर के दाईं ओर वाइल्डकार्ड माना जाएगा ।

छेद करना

इस छेद को पैच करने का एक तरीका %वाइल्डकार्ड चरित्र से बचना है । (ऑपरेटर पर एस्केप क्लॉज से परिचित किसी के लिए भी, यहां SQL सर्वर प्रलेखन का लिंक नहीं है ।

select ...
 where '|peanut|butter|'
  like '%|' + 'pe\%ter' + '|%' escape '\'

अब हम शाब्दिक% का मिलान कर सकते हैं। बेशक, जब हमारे पास एक कॉलम नाम होता है, तो हमें वाइल्डकार्ड से गतिशील रूप से बचने की आवश्यकता होती है। हम REPLACEफ़ंक्शन का उपयोग %वर्ण की घटनाओं को खोजने और प्रत्येक के सामने एक बैकस्लैश चरित्र डालने के लिए कर सकते हैं , जैसे:

select ...
 where '|pe%ter|'
  like '%|' + REPLACE( 'pe%ter' ,'%','\%') + '|%' escape '\'

ताकि% वाइल्डकार्ड के साथ समस्या हल हो जाए। लगभग।

पलायन बच गया

हम मानते हैं कि हमारे समाधान ने एक और समस्या पेश की है। भागने का पात्र। हम देखते हैं कि हमें भागने वाले चरित्र की किसी भी घटना से बचने की आवश्यकता है। इस समय, हम का उपयोग करें! बच चरित्र के रूप में:

select ...
 where '|pe%t!r|'
  like '%|' + REPLACE(REPLACE( 'pe%t!r' ,'!','!!'),'%','!%') + '|%' escape '!'

अंडरस्कोर भी

अब जब हम एक रोल पर हैं, तो हम REPLACEअंडरस्कोर वाइल्डकार्ड को एक और हैंडल जोड़ सकते हैं । और सिर्फ मनोरंजन के लिए, इस बार, हम बच चरित्र के रूप में $ का उपयोग करेंगे।

select ...
 where '|p_%t!r|'
  like '%|' + REPLACE(REPLACE(REPLACE( 'p_%t!r' ,'$','$$'),'%','$%'),'_','$_') + '|%' escape '$'

मैं इस दृष्टिकोण को बचने के लिए पसंद करता हूं क्योंकि यह ओरेकल और MySQL के साथ-साथ SQL सर्वर में भी काम करता है। (मैं आमतौर पर भागने के चरित्र के रूप में \ backslash का उपयोग करता हूं, क्योंकि यह चरित्र है जिसे हम नियमित अभिव्यक्तियों में उपयोग करते हैं। लेकिन सम्मेलन द्वारा विवश क्यों किया जाए!

उन pesky कोष्ठक

SQL सर्वर वाइल्डकार्ड वर्णों को कोष्ठक में संलग्न करके शाब्दिक के रूप में माना जाता है []। इसलिए हमने अभी तक फिक्सिंग नहीं की है, कम से कम SQL सर्वर के लिए। चूंकि कोष्ठक के जोड़ों का विशेष अर्थ है, इसलिए हमें इनसे भी बचना होगा। यदि हम कोष्ठक से ठीक से बचने का प्रबंधन करते हैं, तो कम से कम हमें कोष्ठक के भीतर हाइफ़न -और कैरेट से परेशान नहीं होना पड़ेगा ^। और हम किसी भी छोड़ सकते हैं %और _पात्रों के अंदर कोष्ठक बच गए, हम मूल रूप से कोष्ठक की विशेष अर्थ को निष्क्रिय कर दिया जाएगा के बाद से।

कोष्ठक के मिलान जोड़े ढूँढना उतना कठिन नहीं होना चाहिए। यह सिंगलटन% और _ की घटनाओं को संभालने की तुलना में थोड़ा अधिक कठिन है। (ध्यान दें कि यह केवल कोष्ठक की सभी घटनाओं से बचने के लिए पर्याप्त नहीं है, क्योंकि एक सिंगलटन ब्रैकेट को शाब्दिक माना जाता है, और इससे बचने की आवश्यकता नहीं है। तर्क थोड़ा फजीर हो रहा है क्योंकि मैं अधिक परीक्षण मामलों को चलाने के बिना संभाल सकता हूं। ।)

इनलाइन अभिव्यक्ति गड़बड़ हो जाती है

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

एक समारोह कहाँ?

ठीक है, इसलिए, यदि हम एसक्यूएल में इनलाइन अभिव्यक्ति के रूप में नहीं संभालते हैं, तो हमारे पास निकटतम विकल्प उपयोगकर्ता परिभाषित फ़ंक्शन है। और हम जानते हैं कि चीजों को गति नहीं दी जाएगी (जब तक कि हम उस पर एक सूचकांक को परिभाषित नहीं कर सकते हैं, जैसे कि हम ओरेकल के साथ कर सकते हैं।) यदि हमें एक फ़ंक्शन बनाने के लिए मिला है, तो हम बेहतर हो सकता है कि कोड में जो एसक्यूएल कहता है। बयान।

और उस फ़ंक्शन के व्यवहार में कुछ अंतर हो सकते हैं, जो डीबीएमएस और संस्करण पर निर्भर हैं। (आप सभी जावा डेवलपर्स के लिए एक चिल्लाओ ताकि किसी भी डेटाबेस इंजन का उपयोग करने में सक्षम होने के लिए उत्सुक हो।)

डोमेन की जानकारी

हमारे पास कॉलम के लिए डोमेन का विशेष ज्ञान हो सकता है, (यह है कि कॉलम के लिए लागू स्वीकार्य मूल्यों का सेट। हम एक प्राथमिकता जान सकते हैं कि कॉलम में संग्रहीत मूल्यों में कभी भी प्रतिशत चिह्न, अंडरस्कोर, या ब्रैकेट नहीं होगा। जोड़े। उस मामले में, हम सिर्फ एक त्वरित टिप्पणी शामिल करते हैं कि उन मामलों को कवर किया गया है।

कॉलम में संग्रहीत मान% या _ वर्णों के लिए अनुमति दे सकते हैं, लेकिन एक बाधा को उन मूल्यों से बचने की आवश्यकता हो सकती है, शायद एक परिभाषित चरित्र का उपयोग करके, जैसे कि मानों की तुलना "सुरक्षित" है। फिर, मूल्यों के अनुमत सेट के बारे में एक त्वरित टिप्पणी, और विशेष रूप से किस चरित्र का उपयोग एक भागने चरित्र के रूप में किया जाता है, और जोएल स्पोल्स्की के दृष्टिकोण के साथ चलते हैं।

लेकिन, विशेष ज्ञान और गारंटी के अभाव में, कम से कम उन अस्पष्ट कोने के मामलों को संभालने पर विचार करना हमारे लिए महत्वपूर्ण है, और विचार करें कि क्या व्यवहार उचित है और "विनिर्देश के अनुसार" है।


अन्य मुद्दों पर पुनरावृत्ति हुई

मेरा मानना ​​है कि दूसरों ने पहले से ही चिंता के अन्य क्षेत्रों में से कुछ को पर्याप्त रूप से इंगित किया है:

  • SQL इंजेक्शन (उपयोगकर्ता की आपूर्ति की गई जानकारी के लिए जो दिखाई देगा, और जिसमें बाइंड चर के माध्यम से आपूर्ति करने के बजाय SQL पाठ में शामिल है। बाइंड चर का उपयोग करना आवश्यक नहीं है, यह SQL इंजेक्शन के साथ विफल करने के लिए सिर्फ एक सुविधाजनक तरीका है। अन्य हैं। इससे निपटने के तरीके:

  • इंडेक्स स्कैन की बजाय इंडेक्स स्कैन का उपयोग करके ऑप्टिमाइज़र योजना, वाइल्डकार्ड से बचने के लिए एक अभिव्यक्ति या फ़ंक्शन के लिए संभावित आवश्यकता (संभव इंडेक्स या फ़ंक्शन पर इंडेक्स)

  • बाइंड चर के स्थान पर शाब्दिक मूल्यों का उपयोग स्केलेबिलिटी को प्रभावित करता है


निष्कर्ष

मुझे जोएल स्पोल्स्की का दृष्टिकोण पसंद है। यह चतुर है। और यह काम करता है।

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

हां, मैं मूल प्रश्न से बहुत दूर चला गया हूं। लेकिन एक प्रश्न के लिए "चयनित" उत्तर के साथ एक महत्वपूर्ण मुद्दा होने के संबंध में मैं इस नोट को कहां छोड़ता हूं?


क्या आप कृपया हमें बता सकते हैं कि क्या आप पैरामीटर क्वेरी का उपयोग करते हैं या पसंद करते हैं? इस विशेष मामले में क्या 'उपयोग किए गए क्वेरीज़ का उपयोग' के मूल नियम पर कूदना और मूल भाषा के साथ सफाई करना सही है? बहुत बहुत
लुइस सिकोट

2
@Luis: हाँ, मैं एसक्यूएल स्टेटमेंट्स में बाइंड वेरिएबल्स का उपयोग करना पसंद करता हूं, और केवल एक प्रॉम्प्ट प्रॉब्लम का कारण बनने पर बाइंड वेरिएबल्स से बचना चाहूँगा। मूल समस्या के लिए मेरा आदर्श पैटर्न एसएन स्टेटमेंट को आवश्यक रूप से IN सूची में प्लेसहोल्डर्स की संख्या के साथ बनाना होगा, और फिर प्रत्येक मान को प्लेसहोल्डर्स में से एक में बाँधना होगा। मार्क ब्रैकेट के उत्तर को देखें, जो उत्तर है कि मैंने (और 231 अन्य) ने उत्थान किया है।
spencer7593

133

आप एक स्ट्रिंग के रूप में पैरामीटर पास कर सकते हैं

तो आपके पास तार है

DECLARE @tags

SET @tags = ruby|rails|scruffy|rubyonrails

select * from Tags 
where Name in (SELECT item from fnSplit(@tags, ‘|’))
order by Count desc

फिर आपको केवल 1 पैरामीटर के रूप में स्ट्रिंग पास करना होगा।

यहाँ विभाजित फंक्शन है जिसका मैं उपयोग करता हूँ।

CREATE FUNCTION [dbo].[fnSplit](
    @sInputList VARCHAR(8000) -- List of delimited items
  , @sDelimiter VARCHAR(8000) = ',' -- delimiter that separates items
) RETURNS @List TABLE (item VARCHAR(8000))

BEGIN
DECLARE @sItem VARCHAR(8000)
WHILE CHARINDEX(@sDelimiter,@sInputList,0) <> 0
 BEGIN
 SELECT
  @sItem=RTRIM(LTRIM(SUBSTRING(@sInputList,1,CHARINDEX(@sDelimiter,@sInputList,0)-1))),
  @sInputList=RTRIM(LTRIM(SUBSTRING(@sInputList,CHARINDEX(@sDelimiter,@sInputList,0)+LEN(@sDelimiter),LEN(@sInputList))))

 IF LEN(@sItem) > 0
  INSERT INTO @List SELECT @sItem
 END

IF LEN(@sInputList) > 0
 INSERT INTO @List SELECT @sInputList -- Put the last item in
RETURN
END

2
आप इस दृष्टिकोण के साथ टेबल-फ़ंक्शन में भी शामिल हो सकते हैं।
माइकल हरेन

मैं ओरेकल में इसके समान एक समाधान का उपयोग करता हूं। इसे फिर से पार्स करने की आवश्यकता नहीं है क्योंकि कुछ अन्य समाधान करते हैं।
लीघ रिफ़ेल

9
यह एक शुद्ध डेटाबेस दृष्टिकोण है जो डेटाबेस के बाहर कोड में अन्य आवश्यक कार्य करता है।
डेविड बसाराब

क्या यह एक टेबल स्कैन के लिए है या क्या यह सूचकांक, आदि का लाभ उठा सकता है?
शुद्ध.क्रोम

बेहतर होगा कि SQL टेबल फ़ंक्शन (कम से कम 2005 के बाद) में CROSS APPLY का उपयोग किया जाए, जो अनिवार्य रूप से उस तालिका के विरुद्ध जुड़ता है
adolf लहसुन

66

मैंने सुना है कि जेफ / जोएल ने आज पॉडकास्ट पर इस बारे में बात की ( प्रकरण 34 , 2008-12-16 (एमपी 3, 31 एमबी), 1 एच 03 मिनट 38 सेकंड - 1 घंटे 06 मिनट 45 सेकंड), और मुझे लगा कि मुझे स्टैक ओवरफ्लो याद आया। LINQ to SQL का उपयोग कर रहा था , लेकिन शायद यह खाई थी। यहाँ LINQ से SQL में एक ही बात है।

var inValues = new [] { "ruby","rails","scruffy","rubyonrails" };

var results = from tag in Tags
              where inValues.Contains(tag.Name)
              select tag;

बस। और, हां, LINQ पहले से ही काफी पीछे की ओर दिखता है, लेकिन यह Containsखंड मेरे लिए अतिरिक्त पीछे की ओर लगता है। जब मुझे काम पर एक परियोजना के लिए एक समान क्वेरी करनी थी, तो मैंने स्वाभाविक रूप से स्थानीय सरणी और SQL सर्वर तालिका के बीच एक संयोजन करके इसे गलत तरीके से करने की कोशिश की, LINQ को SQL अनुवादक को लगा देना काफी स्मार्ट होगा। अनुवाद किसी तरह। यह नहीं था, लेकिन इसने एक त्रुटि संदेश प्रदान किया था जो वर्णनात्मक था और मुझे इसमें शामिल था

वैसे भी, यदि आप इसे अत्यधिक अनुशंसित LINQPad में चलाते हैं, और इस क्वेरी को चलाते हैं, तो आप SQL LINQ प्रदाता द्वारा उत्पन्न वास्तविक SQL देख सकते हैं। यह आपको प्रत्येक मान को एक INखंड में परिमाणित करता हुआ दिखाएगा ।


50

यदि आप .NET से कॉल कर रहे हैं, तो आप Dapper डॉट नेट का उपयोग कर सकते हैं :

string[] names = new string[] {"ruby","rails","scruffy","rubyonrails"};
var tags = dataContext.Query<Tags>(@"
select * from Tags 
where Name in @names
order by Count desc", new {names});

यहाँ डैपर सोचता है, इसलिए आपको नहीं करना है। LINQ से SQL के साथ कुछ ऐसा ही संभव है , बेशक:

string[] names = new string[] {"ruby","rails","scruffy","rubyonrails"};
var tags = from tag in dataContext.Tags
           where names.Contains(tag.Name)
           orderby tag.Count descending
           select tag;

11
जो इस पृष्ठ पर उपयोग होता है, वह वास्तविक प्रश्न के लिए पूछा जाता है (डैपर) i.stack.imgur.com/RBAjL.png
सैम केसर


यदि नाम लंबा है तो यह गिर जाता है
cs0815

29

यह संभवतः इसे करने का एक आधा बुरा तरीका है, मैंने इसे एक बार इस्तेमाल किया था, बल्कि प्रभावी था।

आपके लक्ष्यों के आधार पर इसका उपयोग हो सकता है।

  1. एक स्तंभ के साथ एक अस्थायी तालिका बनाएं ।
  2. INSERT उस कॉलम में प्रत्येक लुक-अप मान।
  3. ए का उपयोग करने के बजाय IN, आप तब अपने मानक JOINनियमों का उपयोग कर सकते हैं। (लचीलापन ++)

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

मैं कभी नहीं रूपरेखा के आसपास वास्तव में कितनी तेजी से यह है, लेकिन मेरी स्थिति में इसकी आवश्यकता थी।


यह बिल्कुल भी बुरा नहीं है! इससे भी अधिक, यह एक बहुत ही साफ तरीका है। और यदि आप निष्पादन योजना को देखते हैं, तो आप देखते हैं कि यह IN खंड के समान है। एक अस्थायी तालिका के बजाय, आप अनुक्रमित के साथ एक निश्चित तालिका भी बना सकते हैं, जहां आप पैरामीटर को SESSIONID के साथ संग्रहीत करते हैं।
SQL पुलिस

27

में SQL Server 2016+आप इस्तेमाल कर सकते हैं STRING_SPLITसमारोह:

DECLARE @names NVARCHAR(MAX) = 'ruby,rails,scruffy,rubyonrails';

SELECT * 
FROM Tags
WHERE Name IN (SELECT [value] FROM STRING_SPLIT(@names, ','))
ORDER BY [Count] DESC;

या:

DECLARE @names NVARCHAR(MAX) = 'ruby,rails,scruffy,rubyonrails';

SELECT t.*
FROM Tags t
JOIN STRING_SPLIT(@names,',')
  ON t.Name = [value]
ORDER BY [Count] DESC;

लाइव डेमो

स्वीकार किए जाते हैं जवाब पाठ्यक्रम काम की इच्छा है और यह रास्ता तय करना है, लेकिन यह विरोधी पैटर्न है।

ई। मूल्यों की सूची द्वारा पंक्तियों का पता लगाएं

यह सामान्य एंटी-पैटर्न के लिए प्रतिस्थापन है जैसे एप्लिकेशन लेयर या Transact-SQL में गतिशील SQL स्ट्रिंग बनाना, या LIKE ऑपरेटर का उपयोग करके:

SELECT ProductId, Name, Tags
FROM Product
WHERE ',1,2,3,' LIKE '%,' + CAST(ProductId AS VARCHAR(20)) + ',%';

परिशिष्ट :

STRING_SPLITतालिका फ़ंक्शन पंक्ति अनुमान में सुधार करने के लिए , अस्थायी तालिका / टेबल चर के रूप में विभाजित मूल्यों को उत्प्रेरित करना एक अच्छा विचार है:

DECLARE @names NVARCHAR(MAX) = 'ruby,rails,scruffy,rubyonrails,sql';

CREATE TABLE #t(val NVARCHAR(120));
INSERT INTO #t(val) SELECT s.[value] FROM STRING_SPLIT(@names, ',') s;

SELECT *
FROM Tags tg
JOIN #t t
  ON t.val = tg.TagName
ORDER BY [Count] DESC;

SEDE - लाइव डेमो

संबंधित: एक प्रक्रिया में मूल्यों की सूची कैसे पारित करें


मूल प्रश्न की आवश्यकता है SQL Server 2008। क्योंकि यह प्रश्न अक्सर डुप्लिकेट के रूप में उपयोग किया जाता है, इसलिए मैंने इस उत्तर को संदर्भ के रूप में जोड़ा है।


1
मैंने इसका परीक्षण नहीं किया है, लेकिन मुझे लगता है कि यह सबसे साफ 2016+ समाधान है। मैं अभी भी इंट की एक सरणी पारित करने में सक्षम होना चाहता हूं, लेकिन तब तक ...
डैनियल

24

हमारे पास एक फ़ंक्शन है जो एक टेबल वैरिएबल बनाता है जिसे आप इसमें शामिल कर सकते हैं:

ALTER FUNCTION [dbo].[Fn_sqllist_to_table](@list  AS VARCHAR(8000),
                                           @delim AS VARCHAR(10))
RETURNS @listTable TABLE(
  Position INT,
  Value    VARCHAR(8000))
AS
  BEGIN
      DECLARE @myPos INT

      SET @myPos = 1

      WHILE Charindex(@delim, @list) > 0
        BEGIN
            INSERT INTO @listTable
                        (Position,Value)
            VALUES     (@myPos,LEFT(@list, Charindex(@delim, @list) - 1))

            SET @myPos = @myPos + 1

            IF Charindex(@delim, @list) = Len(@list)
              INSERT INTO @listTable
                          (Position,Value)
              VALUES     (@myPos,'')

            SET @list = RIGHT(@list, Len(@list) - Charindex(@delim, @list))
        END

      IF Len(@list) > 0
        INSERT INTO @listTable
                    (Position,Value)
        VALUES     (@myPos,@list)

      RETURN
  END 

इसलिए:

@Name varchar(8000) = null // parameter for search values    

select * from Tags 
where Name in (SELECT value From fn_sqllist_to_table(@Name,',')))
order by Count desc

20

यह सकल है, लेकिन अगर आपको कम से कम एक होने की गारंटी है, तो आप कर सकते हैं:

SELECT ...
       ...
 WHERE tag IN( @tag1, ISNULL( @tag2, @tag1 ), ISNULL( @tag3, @tag1 ), etc. )

IN ('tag1', 'tag2', 'tag1', 'tag1', 'tag1') होने से SQL सर्वर आसानी से दूर हो जाएगा। साथ ही, आपको डायरेक्ट इंडेक्स की तलाश है


1
नल चेक के साथ वैकल्पिक पैरामीटर प्रदर्शन को खराब करते हैं, क्योंकि ऑप्टिमाइज़र को कुशल प्रश्न बनाने के लिए उपयोग किए जाने वाले मापदंडों की संख्या की आवश्यकता होती है। 5 मापदंडों के लिए क्वेरी को 500 पैरामीटर के लिए एक से भिन्न क्वेरी प्लान की आवश्यकता हो सकती है।
एरिक हार्ट

18

मेरी राय में, इस समस्या को हल करने के लिए सबसे अच्छा स्रोत है, जो इस साइट पर पोस्ट किया गया है:

Syscomments। दिनकर नेति

CREATE FUNCTION dbo.fnParseArray (@Array VARCHAR(1000),@separator CHAR(1))
RETURNS @T Table (col1 varchar(50))
AS 
BEGIN
 --DECLARE @T Table (col1 varchar(50))  
 -- @Array is the array we wish to parse
 -- @Separator is the separator charactor such as a comma
 DECLARE @separator_position INT -- This is used to locate each separator character
 DECLARE @array_value VARCHAR(1000) -- this holds each array value as it is returned
 -- For my loop to work I need an extra separator at the end. I always look to the
 -- left of the separator character for each array value

 SET @array = @array + @separator

 -- Loop through the string searching for separtor characters
 WHILE PATINDEX('%' + @separator + '%', @array) <> 0 
 BEGIN
    -- patindex matches the a pattern against a string
    SELECT @separator_position = PATINDEX('%' + @separator + '%',@array)
    SELECT @array_value = LEFT(@array, @separator_position - 1)
    -- This is where you process the values passed.
    INSERT into @T VALUES (@array_value)    
    -- Replace this select statement with your processing
    -- @array_value holds the value of this element of the array
    -- This replaces what we just processed with and empty string
    SELECT @array = STUFF(@array, 1, @separator_position, '')
 END
 RETURN 
END

उपयोग:

SELECT * FROM dbo.fnParseArray('a,b,c,d,e,f', ',')

क्रेडिट के लिए: दिनकर नेति


तालिका में प्रारंभिक CSV पार्सिंग (एक समय, तत्वों की छोटी संख्या) को छोड़कर महान जवाब, स्वच्छ और मॉड्यूलर, सुपर फास्ट निष्पादन। हालांकि patindex () के बजाय सरल / तेज charindex () का उपयोग कर सकता है? चारिंडेक्स () भी तर्क 'start_location' की अनुमति देता है जो प्रत्येक पुनरावृति इनपुट स्ट्रिंग को काटने से बचने में सक्षम हो सकता है? मूल प्रश्न का उत्तर देने के लिए केवल फ़ंक्शन परिणाम के साथ शामिल हो सकते हैं।
क्रुकसेक

18

मैं एक तालिका प्रकार पैरामीटर पास करूंगा (क्योंकि यह SQL सर्वर 2008 है ), और एक where exists, या इनर जॉइन करें। आप XML का उपयोग कर सकते हैं, का उपयोग कर सकते हैं sp_xml_preparedocument, और फिर उस अस्थायी तालिका को भी अनुक्रमित कर सकते हैं।


पीएचई के जवाब में एक उदाहरण बिल्डिंग टेम्‍प टेबल (सीएसवी से) है।
क्रुकसेक

12

IMHO चरित्र स्ट्रिंग में सूची संग्रहीत करने का उचित तरीका है (DBMS समर्थन द्वारा लंबाई में सीमित); एकमात्र चाल यह है कि (प्रसंस्करण को सरल बनाने के लिए) मेरे पास शुरुआत में और स्ट्रिंग के अंत में एक विभाजक (मेरे उदाहरण में एक अल्पविराम) है। विचार "मक्खी पर सामान्यीकरण" करने के लिए है, सूची को एक-स्तंभ तालिका में बदलकर जिसमें प्रति पंक्ति एक पंक्ति होती है। यह आपको मुड़ने की अनुमति देता है

में (ct1, ct2, ct3 ... ctn)

एक में

में (चयन करें ...)

या (जो समाधान मैं शायद पसंद करूंगा) एक नियमित रूप से शामिल हों, यदि आप सूची में डुप्लिकेट मानों के साथ समस्याओं से बचने के लिए सिर्फ एक "अलग" जोड़ते हैं।

दुर्भाग्य से, एक स्ट्रिंग को टुकड़ा करने की तकनीकें काफी उत्पाद-विशिष्ट हैं। यहाँ SQL सर्वर संस्करण है:

 with qry(n, names) as
       (select len(list.names) - len(replace(list.names, ',', '')) - 1 as n,
               substring(list.names, 2, len(list.names)) as names
        from (select ',Doc,Grumpy,Happy,Sneezy,Bashful,Sleepy,Dopey,' names) as list
        union all
        select (n - 1) as n,
               substring(names, 1 + charindex(',', names), len(names)) as names
        from qry
        where n > 1)
 select n, substring(names, 1, charindex(',', names) - 1) dwarf
 from qry;

Oracle संस्करण:

 select n, substr(name, 1, instr(name, ',') - 1) dwarf
 from (select n,
             substr(val, 1 + instr(val, ',', 1, n)) name
      from (select rownum as n,
                   list.val
            from  (select ',Doc,Grumpy,Happy,Sneezy,Bashful,Sleepy,Dopey,' val
                   from dual) list
            connect by level < length(list.val) -
                               length(replace(list.val, ',', ''))));

और MySQL संस्करण:

select pivot.n,
      substring_index(substring_index(list.val, ',', 1 + pivot.n), ',', -1) from (select 1 as n
     union all
     select 2 as n
     union all
     select 3 as n
     union all
     select 4 as n
     union all
     select 5 as n
     union all
     select 6 as n
     union all
     select 7 as n
     union all
     select 8 as n
     union all
     select 9 as n
     union all
     select 10 as n) pivot,    (select ',Doc,Grumpy,Happy,Sneezy,Bashful,Sleepy,Dopey,' val) as list where pivot.n <  length(list.val) -
                   length(replace(list.val, ',', ''));

(निश्चित रूप से, "पिवट" को उन पंक्तियों के रूप में वापस लौटना चाहिए, जितने आइटम हम सूची में पा सकते हैं)


11

यदि आपको SQL Server 2008 मिला है या बाद में मैं एक टेबल वेलिड पैरामीटर का उपयोग करूंगा

यदि आप SQL सर्वर 2005 पर अटकने के लिए पर्याप्त नहीं हैं, तो आप इस तरह से CLR फ़ंक्शन जोड़ सकते हैं ,

[SqlFunction(
    DataAccessKind.None,
    IsDeterministic = true,
    SystemDataAccess = SystemDataAccessKind.None,
    IsPrecise = true,
    FillRowMethodName = "SplitFillRow",
    TableDefinintion = "s NVARCHAR(MAX)"]
public static IEnumerable Split(SqlChars seperator, SqlString s)
{
    if (s.IsNull)
        return new string[0];

    return s.ToString().Split(seperator.Buffer);
}

public static void SplitFillRow(object row, out SqlString s)
{
    s = new SqlString(row.ToString());
}

जिसे आप इस तरह इस्तेमाल कर सकते हैं,

declare @desiredTags nvarchar(MAX);
set @desiredTags = 'ruby,rails,scruffy,rubyonrails';

select * from Tags
where Name in [dbo].[Split] (',', @desiredTags)
order by Count desc

10

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

मैंने देखा है कि विभाजन के कार्यों को कई मामलों में खुद को क्वेरी से निष्पादित करने में अधिक समय लगता है जहां पैरामीटर बड़े होते हैं। SQL 2008 में तालिका के मानदंड के साथ एक संग्रहीत प्रक्रिया एकमात्र अन्य विकल्प है, जिस पर मैं विचार करूंगा, हालांकि यह आपके मामले में शायद धीमा होगा। यदि आप TVP की प्राथमिक कुंजी पर खोज कर रहे हैं, तो TVP संभवतः केवल बड़ी सूचियों के लिए तेज़ होगी, क्योंकि SQL वैसे भी सूची के लिए एक अस्थायी तालिका का निर्माण करेगा (यदि सूची बड़ी है)। जब तक आप इसका परीक्षण नहीं करेंगे, आपको यह पता नहीं चलेगा।

मैंने संग्रहीत कार्यविधियाँ भी देखी हैं जिनमें 500 मान शून्य के डिफ़ॉल्ट मानों के साथ थे, और जिनमें कॉलम 1 IN (@ Param1, @ Param2, @ Param3, ..., @ Param500) था। इसके कारण SQL को एक टेम्‍प टेबल बनाने के लिए, एक सॉर्ट / अलग करना, और फिर इंडेक्स की तलाश के बजाय एक टेबल स्‍कैन करना चाहिए। यह अनिवार्य रूप से है कि आप उस क्वेरी को पैरामीटर करके क्या करेंगे, हालांकि एक छोटे से पर्याप्त पैमाने पर कि यह ध्यान देने योग्य अंतर नहीं करेगा। मैं आपकी IN सूचियों में NULL होने के खिलाफ अत्यधिक अनुशंसा करता हूं, जैसे कि वह IN को नहीं बदला जाता है, जैसा कि वह इरादा नहीं करेगा। आप गतिशील रूप से पैरामीटर सूची का निर्माण कर सकते हैं, लेकिन केवल स्पष्ट चीज जो आपको प्राप्त होगी, वह यह है कि ऑब्जेक्ट आपके लिए एकल उद्धरण से बच जाएंगे। ऑब्जेक्ट के पैरामीटर को खोजने के लिए क्वेरी को पार्स करने के लिए उस दृष्टिकोण को एप्लिकेशन के अंत में थोड़ा धीमा है।

संग्रहीत प्रक्रियाओं या पैरामीटर किए गए प्रश्नों के लिए निष्पादन योजनाओं का पुन: उपयोग आपको एक प्रदर्शन लाभ दे सकता है, लेकिन यह आपको निष्पादित होने वाली पहली क्वेरी द्वारा निर्धारित एक निष्पादन योजना में लॉक कर देगा। यह कई मामलों में बाद के प्रश्नों के लिए आदर्श से कम हो सकता है। आपके मामले में, निष्पादन योजनाओं का पुन: उपयोग संभवतः एक प्लस होगा, लेकिन यह बिल्कुल भी अंतर नहीं कर सकता है क्योंकि उदाहरण वास्तव में सरल क्वेरी है।

चट्टानें नोट:

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

आपका मामला / कुछ मापदंडों के साथ सरल प्रश्न:

डायनामिक एसक्यूएल, शायद मापदंडों के साथ अगर परीक्षण बेहतर प्रदर्शन दिखाता है।

पुन: प्रयोज्य निष्पादन योजनाओं के साथ क्वेरी, जिन्हें केवल पैरामीटर बदलकर या यदि प्रश्न जटिल है, तो कई बार कहा जाता है:

गतिशील मापदंडों के साथ एसक्यूएल।

बड़ी सूची के साथ प्रश्न:

तालिका के मानदंड के साथ संग्रहित प्रक्रिया। यदि सूची संग्रहीत कार्यविधि पर RECOMPILE के साथ बड़ी मात्रा में उपयोग द्वारा भिन्न हो सकती है, या बस प्रत्येक क्वेरी के लिए एक नई निष्पादन योजना बनाने के लिए मापदंडों के बिना गतिशील SQL का उपयोग कर सकती है।


यहाँ "संग्रहीत प्रक्रिया" से आपका क्या तात्पर्य है? क्या आप एक उदाहरण पोस्ट कर सकते हैं?
स्ट्रहटनोव

9

हम यहाँ XML का उपयोग कर सकते हैं:

    declare @x xml
    set @x='<items>
    <item myvalue="29790" />
    <item myvalue="31250" />
    </items>
    ';
    With CTE AS (
         SELECT 
            x.item.value('@myvalue[1]', 'decimal') AS myvalue
        FROM @x.nodes('//items/item') AS x(item) )

    select * from YourTable where tableColumnName in (select myvalue from cte)

1
CTEऔर इस लेख@x में दिखाए अनुसार, बहुत सावधानी से किए जाने पर, अवहेलना में समाप्त / इनलाइन हो सकता है ।
रोबर्ट 4

9

मैं डिफ़ॉल्ट रूप से टेबल वैल्यू फ़ंक्शन (जो एक स्ट्रिंग से एक टेबल लौटाता है) को IN कंडीशन में पास करने के साथ संपर्क करूँगा।

यहाँ UDF के लिए कोड है (मुझे यह स्टैक ओवरफ्लो कहीं से मिला है, मुझे अभी स्रोत नहीं मिल सकता है)

CREATE FUNCTION [dbo].[Split] (@sep char(1), @s varchar(8000))
RETURNS table
AS
RETURN (
    WITH Pieces(pn, start, stop) AS (
      SELECT 1, 1, CHARINDEX(@sep, @s)
      UNION ALL
      SELECT pn + 1, stop + 1, CHARINDEX(@sep, @s, stop + 1)
      FROM Pieces
      WHERE stop > 0
    )
    SELECT 
      SUBSTRING(@s, start, CASE WHEN stop > 0 THEN stop-start ELSE 512 END) AS s
    FROM Pieces
  )

आपके द्वारा इसे प्राप्त करने के बाद आपका कोड इस प्रकार सरल हो जाएगा:

select * from Tags 
where Name in (select s from dbo.split(';','ruby;rails;scruffy;rubyonrails'))
order by Count desc

जब तक आपके पास हास्यास्पद रूप से लंबी स्ट्रिंग नहीं होती है, तब तक टेबल इंडेक्स के साथ अच्छी तरह से काम करना चाहिए।

यदि आवश्यक हो तो आप इसे एक अस्थायी तालिका में सम्मिलित कर सकते हैं, इसे अनुक्रमित कर सकते हैं, फिर एक रन चला सकते हैं ...


8

एक अन्य संभावित समाधान एक संग्रहित प्रक्रिया के लिए तर्कों की एक परिवर्तनीय संख्या को पारित करने के बजाय, एक एकल स्ट्रिंग पास करें जिसमें आपके नाम हैं, लेकिन उन्हें '<>' के साथ आसपास से अद्वितीय बनाते हैं। फिर नाम खोजने के लिए PATINDEX का उपयोग करें:

SELECT * 
FROM Tags 
WHERE PATINDEX('%<' + Name + '>%','<jo>,<john>,<scruffy>,<rubyonrails>') > 0

8

निम्न संग्रहित प्रक्रिया का उपयोग करें। यह एक कस्टम विभाजन फ़ंक्शन का उपयोग करता है, जो यहां पाया जा सकता है

 create stored procedure GetSearchMachingTagNames 
    @PipeDelimitedTagNames varchar(max), 
    @delimiter char(1) 
    as  
    begin
         select * from Tags 
         where Name in (select data from [dbo].[Split](@PipeDelimitedTagNames,@delimiter) 
    end

8

यदि हमारे पास सीमांकित (,) सीमांकित के साथ IN खंड के अंदर संग्रहीत तार हैं, तो हम मान प्राप्त करने के लिए charindex फ़ंक्शन का उपयोग कर सकते हैं। यदि आप .NET का उपयोग करते हैं, तो आप SqlParameters के साथ मैप कर सकते हैं।

DDL स्क्रिप्ट:

CREATE TABLE Tags
    ([ID] int, [Name] varchar(20))
;

INSERT INTO Tags
    ([ID], [Name])
VALUES
    (1, 'ruby'),
    (2, 'rails'),
    (3, 'scruffy'),
    (4, 'rubyonrails')
;

T-SQL:

DECLARE @Param nvarchar(max)

SET @Param = 'ruby,rails,scruffy,rubyonrails'

SELECT * FROM Tags
WHERE CharIndex(Name,@Param)>0

आप अपने .NET कोड में उपरोक्त कथन का उपयोग कर सकते हैं और SqlParameter के साथ पैरामीटर को मैप कर सकते हैं।

फिडलर डेमो

संपादित करें: निम्न स्क्रिप्ट का उपयोग करके चयनितटैग नामक तालिका बनाएं।

DDL स्क्रिप्ट:

Create table SelectedTags
(Name nvarchar(20));

INSERT INTO SelectedTags values ('ruby'),('rails')

T-SQL:

DECLARE @list nvarchar(max)
SELECT @list=coalesce(@list+',','')+st.Name FROM SelectedTags st

SELECT * FROM Tags
WHERE CharIndex(Name,@Param)>0

क्या आप इस कार्य का एक उदाहरण दिखा सकते हैं जहाँ संभावित मूल्यों की हार्ड-कोडित सूची नहीं है?
जॉन साउन्डर्स

@ जॉनसनर्स, मैंने किसी भी हार्डकोड सूची का उपयोग किए बिना स्क्रिप्ट को संपादित किया है। कृप्या सत्यापित करें।
गोवधमान ००8

3
इस विकल्प के साथ एक सीमा। यदि स्ट्रिंग मिलती है, तो CharIndex 1 लौटाता है। एक सटीक शब्दों के लिए एक मैच देता है। "स्टैक" के लिए चारआईंडेक्स 1 "स्टैकऑवरफ्लो" शब्द के लिए वापस आ जाएगा। इस उत्तर के लिए एक छोटा सा ट्वीस्ट है, जो ऊपर दिए गए PatIndex का उपयोग करते हुए '' '% नाम%'> 'के साथ नाम संलग्न करता है जो इस सीमा को पार कर जाता है। इस समस्या का रचनात्मक समाधान हालांकि।
रिचर्ड विवियन

7

इस तरह के तर्कों की एक चर संख्या के लिए, जिस तरह से मैं जानता हूँ कि या तो एसक्यूएल स्पष्ट रूप से उत्पन्न करना है या ऐसा कुछ करना है जिसमें आपके द्वारा इच्छित वस्तुओं के साथ एक अस्थायी तालिका को आबाद करना और अस्थायी तालिका के खिलाफ शामिल होना शामिल है।


7

में ColdFusion हम सिर्फ कार्य करें:

<cfset myvalues = "ruby|rails|scruffy|rubyonrails">
    <cfquery name="q">
        select * from sometable where values in <cfqueryparam value="#myvalues#" list="true">
    </cfquery>

7

यहां एक तकनीक है जो क्वेरी स्ट्रिंग में उपयोग की जाने वाली स्थानीय तालिका को पुन: बनाता है। इस तरह से करने से पार्सिंग की सभी समस्याएं खत्म हो जाती हैं।

स्ट्रिंग को किसी भी भाषा में बनाया जा सकता है। इस उदाहरण में मैंने एसक्यूएल का उपयोग किया क्योंकि वह मूल समस्या थी जिसे मैं हल करने की कोशिश कर रहा था। मुझे बाद में निष्पादित होने के लिए एक स्ट्रिंग में मक्खी पर तालिका डेटा में पारित करने के लिए एक साफ तरीके की आवश्यकता थी।

उपयोगकर्ता परिभाषित प्रकार का उपयोग करना वैकल्पिक है। प्रकार बनाना केवल एक बार बनाया जाता है और समय से पहले किया जा सकता है। अन्यथा केवल स्ट्रिंग में घोषणा के लिए एक पूर्ण तालिका प्रकार जोड़ें।

सामान्य पैटर्न का विस्तार करना आसान है और इसका उपयोग अधिक जटिल तालिकाओं को पारित करने के लिए किया जा सकता है।

-- Create a user defined type for the list.
CREATE TYPE [dbo].[StringList] AS TABLE(
    [StringValue] [nvarchar](max) NOT NULL
)

-- Create a sample list using the list table type.
DECLARE @list [dbo].[StringList]; 
INSERT INTO @list VALUES ('one'), ('two'), ('three'), ('four')

-- Build a string in which we recreate the list so we can pass it to exec
-- This can be done in any language since we're just building a string.
DECLARE @str nvarchar(max);
SET @str = 'DECLARE @list [dbo].[StringList]; INSERT INTO @list VALUES '

-- Add all the values we want to the string. This would be a loop in C++.
SELECT @str = @str + '(''' + StringValue + '''),' FROM @list

-- Remove the trailing comma so the query is valid sql.
SET @str = substring(@str, 1, len(@str)-1)

-- Add a select to test the string.
SET @str = @str + '; SELECT * FROM @list;'

-- Execute the string and see we've pass the table correctly.
EXEC(@str)

7

SQL सर्वर 2016+ में एक और संभावना का उपयोग करने के लिए है OPENJSON फ़ंक्शन ।

यह दृष्टिकोण OPENJSON के बारे में ब्लॉग किया गया है - आईडी की सूची द्वारा पंक्तियों का चयन करने के सर्वोत्तम तरीकों में से एक

नीचे एक पूर्ण काम किया गया उदाहरण

CREATE TABLE dbo.Tags
  (
     Name  VARCHAR(50),
     Count INT
  )

INSERT INTO dbo.Tags
VALUES      ('VB',982), ('ruby',1306), ('rails',1478), ('scruffy',1), ('C#',1784)

GO

CREATE PROC dbo.SomeProc
@Tags VARCHAR(MAX)
AS
SELECT T.*
FROM   dbo.Tags T
WHERE  T.Name IN (SELECT J.Value COLLATE Latin1_General_CI_AS
                  FROM   OPENJSON(CONCAT('[', @Tags, ']')) J)
ORDER  BY T.Count DESC

GO

EXEC dbo.SomeProc @Tags = '"ruby","rails","scruffy","rubyonrails"'

DROP TABLE dbo.Tags 

7

यहाँ एक और विकल्प है। संग्रहीत कार्यविधि के लिए एक स्ट्रिंग पैरामीटर के रूप में अल्पविराम-सीमांकित सूची पास करें और:

CREATE PROCEDURE [dbo].[sp_myproc]
    @UnitList varchar(MAX) = '1,2,3'
AS
select column from table
where ph.UnitID in (select * from CsvToInt(@UnitList))

और समारोह:

CREATE Function [dbo].[CsvToInt] ( @Array varchar(MAX))
returns @IntTable table
(IntValue int)
AS
begin
    declare @separator char(1)
    set @separator = ','
    declare @separator_position int
    declare @array_value varchar(MAX)

    set @array = @array + ','

    while patindex('%,%' , @array) <> 0
    begin

        select @separator_position = patindex('%,%' , @array)
        select @array_value = left(@array, @separator_position - 1)

        Insert @IntTable
        Values (Cast(@array_value as int))
        select @array = stuff(@array, 1, @separator_position, '')
    end
    return
end

6

मेरे पास एक उत्तर है जिसे यूडीएफ, एक्सएमएल की आवश्यकता नहीं है क्योंकि एक चयनित कथन स्वीकार करता है जैसे कि चयन * से परीक्षण जहां डेटा में (चयन मूल्य से तालिका)

आपको वास्तव में केवल स्ट्रिंग को तालिका में बदलने का एक तरीका चाहिए।

यह एक पुनरावर्ती CTE, या एक संख्या तालिका (या Master..spt_value) के साथ एक क्वेरी के साथ किया जा सकता है

यहाँ सीटीई संस्करण है।

DECLARE @InputString varchar(8000) = 'ruby,rails,scruffy,rubyonrails'

SELECT @InputString = @InputString + ','

;WITH RecursiveCSV(x,y) 
AS 
(
    SELECT 
        x = SUBSTRING(@InputString,0,CHARINDEX(',',@InputString,0)),
        y = SUBSTRING(@InputString,CHARINDEX(',',@InputString,0)+1,LEN(@InputString))
    UNION ALL
    SELECT 
        x = SUBSTRING(y,0,CHARINDEX(',',y,0)),
        y = SUBSTRING(y,CHARINDEX(',',y,0)+1,LEN(y))
    FROM 
        RecursiveCSV 
    WHERE
        SUBSTRING(y,CHARINDEX(',',y,0)+1,LEN(y)) <> '' OR 
        SUBSTRING(y,0,CHARINDEX(',',y,0)) <> ''
)
SELECT
    * 
FROM 
    Tags
WHERE 
    Name IN (select x FROM RecursiveCSV)
OPTION (MAXRECURSION 32767);

6

मैं शीर्ष मतदान जवाब के अधिक संक्षिप्त संस्करण का उपयोग करता हूं :

List<SqlParameter> parameters = tags.Select((s, i) => new SqlParameter("@tag" + i.ToString(), SqlDbType.NVarChar(50)) { Value = s}).ToList();

var whereCondition = string.Format("tags in ({0})", String.Join(",",parameters.Select(s => s.ParameterName)));

यह दो बार टैग मापदंडों के माध्यम से लूप करता है; लेकिन यह ज़्यादातर मायने नहीं रखता (यह आपकी अड़चन नहीं होगी; अगर ऐसा है तो लूप को अनियंत्रित करें)।

यदि आप वास्तव में प्रदर्शन में रुचि रखते हैं और दो बार लूप के माध्यम से पुनरावृति नहीं करना चाहते हैं, तो यहां एक कम सुंदर संस्करण है:

var parameters = new List<SqlParameter>();
var paramNames = new List<string>();
for (var i = 0; i < tags.Length; i++)  
{
    var paramName = "@tag" + i;

    //Include size and set value explicitly (not AddWithValue)
    //Because SQL Server may use an implicit conversion if it doesn't know
    //the actual size.
    var p = new SqlParameter(paramName, SqlDbType.NVarChar(50) { Value = tags[i]; } 
    paramNames.Add(paramName);
    parameters.Add(p);
}

var inClause = string.Join(",", paramNames);

5

यहाँ इस समस्या का एक और जवाब है।

(नया संस्करण 6/4/13 को पोस्ट किया गया)।

    private static DataSet GetDataSet(SqlConnectionStringBuilder scsb, string strSql, params object[] pars)
    {
        var ds = new DataSet();
        using (var sqlConn = new SqlConnection(scsb.ConnectionString))
        {
            var sqlParameters = new List<SqlParameter>();
            var replacementStrings = new Dictionary<string, string>();
            if (pars != null)
            {
                for (int i = 0; i < pars.Length; i++)
                {
                    if (pars[i] is IEnumerable<object>)
                    {
                        List<object> enumerable = (pars[i] as IEnumerable<object>).ToList();
                        replacementStrings.Add("@" + i, String.Join(",", enumerable.Select((value, pos) => String.Format("@_{0}_{1}", i, pos))));
                        sqlParameters.AddRange(enumerable.Select((value, pos) => new SqlParameter(String.Format("@_{0}_{1}", i, pos), value ?? DBNull.Value)).ToArray());
                    }
                    else
                    {
                        sqlParameters.Add(new SqlParameter(String.Format("@{0}", i), pars[i] ?? DBNull.Value));
                    }
                }
            }
            strSql = replacementStrings.Aggregate(strSql, (current, replacementString) => current.Replace(replacementString.Key, replacementString.Value));
            using (var sqlCommand = new SqlCommand(strSql, sqlConn))
            {
                if (pars != null)
                {
                    sqlCommand.Parameters.AddRange(sqlParameters.ToArray());
                }
                else
                {
                    //Fail-safe, just in case a user intends to pass a single null parameter
                    sqlCommand.Parameters.Add(new SqlParameter("@0", DBNull.Value));
                }
                using (var sqlDataAdapter = new SqlDataAdapter(sqlCommand))
                {
                    sqlDataAdapter.Fill(ds);
                }
            }
        }
        return ds;
    }

चीयर्स।


4

जीतने का एकमात्र कदम नहीं खेलना है।

आपके लिए कोई अनंत परिवर्तनशीलता नहीं है। केवल परिमित परिवर्तनशीलता।

एसक्यूएल में आप इस तरह एक खंड है:

and ( {1}==0 or b.CompanyId in ({2},{3},{4},{5},{6}) )

C # कोड में आप ऐसा कुछ करते हैं:

  int origCount = idList.Count;
  if (origCount > 5) {
    throw new Exception("You may only specify up to five originators to filter on.");
  }
  while (idList.Count < 5) { idList.Add(-1); }  // -1 is an impossible value
  return ExecuteQuery<PublishDate>(getValuesInListSQL, 
               origCount,   
               idList[0], idList[1], idList[2], idList[3], idList[4]);

तो मूल रूप से अगर गिनती 0 है तो कोई फिल्टर नहीं है और सब कुछ गुजरता है। यदि गिनती 0 से अधिक है, तो मूल्य सूची में होना चाहिए, लेकिन सूची को असंभव मानों के साथ पांच में रखा गया है (ताकि SQL अभी भी समझ में आता है)

कभी-कभी लंगड़ा समाधान केवल एक ही होता है जो वास्तव में काम करता है।

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