SQL सर्वर 2005 के बाद से, FOR XML PATH
का उपयोग करने की ट्रिक स्ट्रिंग्स को असामान्य बनाना और उन्हें एक एकल (आमतौर पर अल्पविराम से अलग) सूची में जोड़ना बहुत लोकप्रिय रहा है। SQL सर्वर 2017 में, हालांकि, STRING_AGG()
अंत में GROUP_CONCAT()
. का अनुकरण करने के लिए समुदाय की लंबे समय से चली आ रही और व्यापक दलीलों का जवाब दिया और इसी तरह की कार्यक्षमता अन्य प्लेटफार्मों में पाई जाती है। मैंने हाल ही में मौजूदा कोड को बेहतर बनाने और आधुनिक संस्करणों के लिए बेहतर अनुकूल एक अतिरिक्त उदाहरण जोड़ने के लिए पुरानी पद्धति का उपयोग करके अपने कई स्टैक ओवरफ़्लो उत्तरों को संशोधित करना शुरू किया है।
मैंने जो पाया उससे मैं थोड़ा हैरान था।
एक से अधिक मौकों पर, मुझे दोबारा जांचना पड़ा कि कोड मेरा भी था।
एक त्वरित उदाहरण
आइए समस्या का एक सरल प्रदर्शन देखें। किसी के पास इस तरह की एक टेबल है:
टेबल डीबीओ बनाएं। पसंदीदा बैंड (उपयोगकर्ता आईडी int, बैंडनाम nvarchar (255)); INSERT dbo.FavoriteBands(UserID, BandName) VALUES (1, N'Pink Floyd'), (1, N'New Order'), (1, N'The Hip'), (2, N'Zamfir'), ( 2, एन'एबीबीए');
प्रत्येक उपयोगकर्ता के पसंदीदा बैंड दिखाने वाले पृष्ठ पर, वे चाहते हैं कि आउटपुट इस तरह दिखे:
यूजरआईडी बैंड------------------------------------------- 1 पिंक फ़्लॉइड, न्यू ऑर्डर, द हिप2 ज़म्फिर, एबीबीए
SQL सर्वर 2005 दिनों में, मैं इस समाधान की पेशकश करता:
DISTINCT UserID चुनें, बैंड =(DBO से चुनें।लेकिन जब मैं अब इस कोड को देखता हूं, तो मुझे कई समस्याएं दिखाई देती हैं जिन्हें मैं ठीक करने से नहीं रोक सकता।
सामान
उपरोक्त कोड में सबसे घातक दोष यह है कि यह एक पिछला कॉमा छोड़ देता है:
यूजरआईडी बैंड------------------------------------------- 1 पिंक फ़्लॉइड, न्यू ऑर्डर, द हिप, 2 ज़म्फिर, एबीबीए,इसे हल करने के लिए, मैं अक्सर देखता हूं कि लोग क्वेरी को दूसरे के अंदर लपेटते हैं और फिर
Bands
. को घेर लेते हैं आउटपुट के साथLEFT(Bands, LEN(Bands)-1)
. लेकिन यह अनावश्यक अतिरिक्त गणना है; इसके बजाय, हम अल्पविराम को स्ट्रिंग की शुरुआत में ले जा सकते हैं औरSTUFF
का उपयोग करके पहले एक या दो वर्णों को हटा सकते हैं . फिर, हमें स्ट्रिंग की लंबाई की गणना करने की आवश्यकता नहीं है क्योंकि यह अप्रासंगिक है।विशिष्ट उपयोगकर्ता आईडी चुनें, बैंड =सामान (--------------------------------^^^^^^ ( चुनें ',' + बैंडनाम --------------- ^ ^ ^ ^ ^ ^ डीबीओ से। पसंदीदा बैंड जहां यूजरआईडी =एफबी। एक्सएमएल पाथ ('') के लिए यूजरआईडी), 1, 2, ' ')--------------------------^^^^^^^^^^^ डीबीओ से। पसंदीदा बैंड एएस एफबी;यदि आप लंबे या सशर्त सीमांकक का उपयोग कर रहे हैं तो आप इसे और अधिक समायोजित कर सकते हैं।
DISTINCT
अगली समस्या
DISTINCT
. के उपयोग की है . जिस तरह से कोड काम करता है वह है व्युत्पन्न तालिका प्रत्येकUserID
के लिए अल्पविराम से अलग सूची उत्पन्न करती है मान, फिर डुप्लिकेट हटा दिए जाते हैं। हम योजना को देखकर और एक्सएमएल से संबंधित ऑपरेटर को सात बार निष्पादित करते हुए देख सकते हैं, भले ही केवल तीन पंक्तियां ही वापस आती हैं:चित्र 1:एकत्रीकरण के बाद फ़िल्टर दिखाने की योजना
अगर हम
GROUP BY
. का उपयोग करने के लिए कोड बदलते हैंDISTINCT
. के बजाय :चुनें /* DISTINCT */ UserID, Bands =STUFF ((चुनें ',' + BandName dbo से। पसंदीदा बैंड जहां UserID =fb. XML PATH के लिए UserID ('')), 1, 2, '') dbo से उपयोगकर्ता आईडी द्वारा एफबी ग्रुप के रूप में पसंदीदा बैंड;--^^^^^^^^^^^^^^यह एक सूक्ष्म अंतर है, और यह परिणाम नहीं बदलता है, लेकिन हम योजना में सुधार देख सकते हैं। मूल रूप से, XML संचालन को तब तक के लिए टाल दिया जाता है जब तक कि डुप्लीकेट हटा दिए जाते हैं:
चित्र 2:एकत्रीकरण से पहले फ़िल्टर दिखाने की योजना बनाएं
इस पैमाने पर, अंतर महत्वहीन है। लेकिन क्या होगा अगर हम कुछ और डेटा जोड़ दें? मेरे सिस्टम पर, यह 11,000 से अधिक पंक्तियों को जोड़ता है:
INSERT dbo.FavoriteBands(UserID, BandName) चुनें [object_id], sys.all_columns से नाम;यदि हम दो प्रश्नों को फिर से चलाते हैं, तो अवधि और CPU में अंतर तुरंत स्पष्ट हो जाता है:
चित्र 3:DISTINCT और GROUP BY की तुलना करने वाले रनटाइम परिणाम
लेकिन योजनाओं में अन्य दुष्प्रभाव भी स्पष्ट हैं।
DISTINCT
. के मामले में , यूडीएक्स एक बार फिर तालिका में प्रत्येक पंक्ति के लिए निष्पादित करता है, एक अत्यधिक उत्सुक इंडेक्स स्पूल है, एक अलग प्रकार है (हमेशा मेरे लिए एक लाल झंडा), और क्वेरी में एक उच्च स्मृति अनुदान है, जो समवर्ती में गंभीर सेंध लगा सकता है :चित्र 4:पैमाने पर DISTINCT योजना
इस बीच,
GROUP BY
. में क्वेरी, UDX प्रत्येक अद्वितीयUserID
. के लिए केवल एक बार निष्पादित करता है , उत्सुक स्पूल पंक्तियों की बहुत कम संख्या पढ़ता है, कोई अलग सॉर्ट ऑपरेटर नहीं है (इसे हैश मैच से बदल दिया गया है), और मेमोरी अनुदान तुलना में छोटा है:चित्र 5:स्केल पर ग्रुप बाय प्लान
वापस जाने और इस तरह पुराने कोड को ठीक करने में कुछ समय लगता है, लेकिन पिछले कुछ समय से, मैं हमेशा
GROUP BY
का उपयोग करने के बारे में बहुत अनुशासित रहा हूंDISTINCT
. के बजाय ।N उपसर्ग
बहुत सारे पुराने कोड नमूने जो मुझे मिले, उन्होंने मान लिया कि कोई यूनिकोड वर्ण कभी भी उपयोग में नहीं होगा, या कम से कम नमूना डेटा ने संभावना का सुझाव नहीं दिया। मैं ऊपर के रूप में अपना समाधान पेश करूंगा, और फिर उपयोगकर्ता वापस आकर कहेगा, "लेकिन एक पंक्ति में मेरे पास
'просто красный'
है। , और यह'?????? ???????'
!" मैं अक्सर लोगों को याद दिलाता हूं कि उन्हें हमेशा एन उपसर्ग के साथ संभावित यूनिकोड स्ट्रिंग अक्षर को उपसर्ग करने की आवश्यकता होती है जब तक कि वे पूरी तरह से नहीं जानते कि वे केवलvarchar
से निपटेंगे तार या पूर्णांक। मैं इसके बारे में बहुत स्पष्ट और शायद अति सतर्क होने लगा:यूज़र आईडी चुनें, बैंड =स्टफ ((चुनें एन', ' + बैंडनाम ------ डीबीओ से। पसंदीदा बैंड जहां यूजर आईडी =एफबी। एक्सएमएल पाथ (एन') )), 1, 2, एन '') ----------------------- ^ -----------^ डीबीओ से। पसंदीदा बैंड UserID द्वारा fb ग्रुप के रूप में;XML एंटिटाइज़ेशन
एक और "क्या होगा?" उपयोगकर्ता के नमूना डेटा में हमेशा मौजूद नहीं होने वाला परिदृश्य XML वर्ण होता है। उदाहरण के लिए, क्या होगा यदि मेरे पसंदीदा बैंड का नाम “
Bob & Sheila <> Strawberries
. है "? उपरोक्त क्वेरी के साथ आउटपुट एक्सएमएल-सुरक्षित बना दिया गया है, जो कि हम हमेशा नहीं चाहते हैं (उदाहरण के लिए,Bob & Sheila <> Strawberries
) उस समय की Google खोजें सुझाव देती हैं कि “आपकोTYPE
जोड़ना होगा ," और मुझे याद है कुछ इस तरह की कोशिश करना:उपयोगकर्ता आईडी चुनें, बैंड =स्टफ ((चयन एन', '+ डीबीओ से बैंडनाम। पसंदीदा बैंड जहां यूजर आईडी =एफबी। एक्सएमएल पाथ (एन''), प्रकार), 1, 2, एन '') के लिए यूजरआईडी - ------------------------^^^^^^ डीबीओ से। यूजर आईडी द्वारा पसंदीदा बैंड के रूप में एफबी ग्रुप;दुर्भाग्य से, इस मामले में सबक्वेरी से आउटपुट डेटा प्रकार
संदेश 8116, स्तर 16, राज्य 1xml
है . यह निम्न त्रुटि संदेश की ओर जाता है:
तर्क डेटा प्रकार xml सामान समारोह के तर्क 1 के लिए अमान्य है।आपको SQL सर्वर को यह बताना होगा कि आप डेटा प्रकार को इंगित करके परिणामी मान को एक स्ट्रिंग के रूप में निकालना चाहते हैं और आप पहला तत्व चाहते हैं। इसके बाद, मैं इसे निम्नलिखित के रूप में जोड़ूंगा:
सेलेक्ट यूजर आईडी, बैंड्स =स्टफ ((सेलेक्ट एन', '+ डीबीओ से बैंडनाम। पसंदीदा बैंड जहां यूजर आईडी =एफबी। एक्सएमएल पाथ (एन''), टाइप) के लिए यूजरआईडी। वैल्यू (एन'।', एन'नवरचर (अधिकतम)'), --------------------------^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^ 1, 2, एन'') डीबीओ से। यूजर आईडी द्वारा एफबी ग्रुप के रूप में पसंदीदा बैंड;यह एक्सएमएल एंटाइटेलाइजेशन के बिना स्ट्रिंग वापस कर देगा। लेकिन क्या यह सबसे कुशल है? पिछले साल, चार्लीफेस ने मुझे याद दिलाया कि मिस्टर मागू ने कुछ व्यापक परीक्षण किए और पाया कि
./text()[1]
.
और.[1]
. (मैंने मूल रूप से इसे एक टिप्पणी से सुना है जो मिकेल एरिक्सन ने मेरे लिए यहां छोड़ा था।) मैंने एक बार फिर इस तरह दिखने के लिए अपना कोड समायोजित किया:सेलेक्ट यूजरआईडी, बैंड्स =स्टफ ((सेलेक्ट एन', '+ डीबीओ से बैंडनाम। फेवरेटबैंड्स जहां यूजरआईडी =एफबी। एक्सएमएल पाथ (एन''), टाइप) के लिए यूजरआईडी। वैल्यू (एन'./टेक्स्ट()[ 1]', नवरचर (अधिकतम)'), ------------------------------------- -----^^^^^^^^^ 1, 2, एन'') डीबीओ से। यूजर आईडी द्वारा एफबी ग्रुप के रूप में पसंदीदा बैंड;आप देख सकते हैं कि इस तरह से मूल्य निकालने से थोड़ी अधिक जटिल योजना बनती है (आप इसे केवल अवधि को देखने से नहीं जान पाएंगे, जो उपरोक्त परिवर्तनों में काफी स्थिर रहता है):
चित्र 6:./text()[1] के साथ योजना बनाएं
रूट पर चेतावनी
SELECT
ऑपरेटर स्पष्ट रूपांतरण सेnvarchar(max)
. में आता है ।आदेश
कभी-कभी, उपयोगकर्ता व्यक्त करेंगे कि ऑर्डर देना महत्वपूर्ण है। अक्सर, यह केवल आपके द्वारा जोड़े जा रहे कॉलम द्वारा क्रमित होता है-लेकिन कभी-कभी, इसे कहीं और जोड़ा जा सकता है। लोगों का मानना है कि यदि उन्होंने एक बार SQL सर्वर से एक विशिष्ट आदेश को बाहर आते देखा है, तो यह वह क्रम है जिसे वे हमेशा देखेंगे, लेकिन यहां कोई विश्वसनीयता नहीं है। जब तक आप ऐसा नहीं कहते तब तक आदेश की गारंटी नहीं है। इस मामले में, मान लें कि हम
BandName
. द्वारा ऑर्डर करना चाहते हैं वर्णानुक्रम में। हम इस निर्देश को सबक्वायरी के अंदर जोड़ सकते हैं:यूज़र आईडी चुनें, बैंड =स्टफ ((चयन एन', '+ डीबीओ से बैंडनाम। पसंदीदा बैंड जहां यूजर आईडी =एफबी। बैंडनाम द्वारा यूजरआईडी ऑर्डर ---------^^^^^^^^^^ ^^^^^^^ एक्सएमएल पाथ (एन''), टाइप) के लिए। वैल्यू (एन'./टेक्स्ट()[1]', एन'नवर्चर (अधिकतम)'), 1, 2, एन'') उपयोगकर्ता आईडी द्वारा डीबीओ.पसंदीदा बैंड के रूप में एफबी ग्रुप से;ध्यान दें कि यह अतिरिक्त सॉर्ट ऑपरेटर के कारण थोड़ा निष्पादन समय जोड़ सकता है, यह इस बात पर निर्भर करता है कि कोई सहायक अनुक्रमणिका है या नहीं।
STRING_AGG()
जैसा कि मैं अपने पुराने उत्तरों को अपडेट करता हूं, जो अभी भी उस संस्करण पर काम करना चाहिए जो प्रश्न के समय प्रासंगिक था, ऊपर का अंतिम स्निपेट (
ORDER BY
के साथ या बिना) ) वह फ़ॉर्म है जिसे आप संभवतः देखेंगे। लेकिन आपको अधिक आधुनिक रूप के लिए एक अतिरिक्त अपडेट भी दिखाई दे सकता है।
STRING_AGG()
यकीनन SQL सर्वर 2017 में जोड़ी गई सबसे अच्छी सुविधाओं में से एक है। यह उपरोक्त किसी भी दृष्टिकोण की तुलना में सरल और कहीं अधिक कुशल है, जिससे इस तरह के सुव्यवस्थित, अच्छी तरह से प्रदर्शन करने वाले प्रश्न होते हैं:उपयोगकर्ता आईडी, बैंड =STRING_AGG(BandName, N', ') का चयन करें dbo से। UserID द्वारा पसंदीदा बैंड समूह;यह मजाक नहीं है; यह बात है। ये रही योजना—सबसे महत्वपूर्ण बात, तालिका के सामने केवल एक स्कैन है:
चित्र 7:STRING_AGG() योजना
अगर आप ऑर्डर करना चाहते हैं,
STRING_AGG()
इसका भी समर्थन करता है (जब तक आप संगतता स्तर 110 या उससे अधिक हैं, जैसा कि मार्टिन स्मिथ यहां बताते हैं):यूज़र आईडी चुनें, बैंड =STRING_AGG (बैंडनाम, एन', ') ग्रुप के भीतर (बैंडनाम द्वारा ऑर्डर) ---- ^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^ डीबीओ से। उपयोगकर्ता आईडी द्वारा पसंदीदा बैंड समूह;योजना दिखती है बिना छांटे वाले के समान, लेकिन क्वेरी मेरे परीक्षणों में एक धीमी गति से धीमी है। यह अभी भी किसी भी
FOR XML PATH
. से कहीं अधिक तेज़ है विविधताएं।सूचकांक
ढेर शायद ही उचित हो। यदि आपके पास एक गैर-संकुल अनुक्रमणिका भी है जिसका उपयोग क्वेरी कर सकती है, तो योजना और भी बेहतर दिखती है। उदाहरण के लिए:
डीबो पर इंडेक्स ix_पसंदीदा बैंड बनाएं। पसंदीदा बैंड (उपयोगकर्ता आईडी, बैंडनाम);
STRING_AGG()
. का उपयोग करके समान आदेशित क्वेरी की योजना यहां दी गई है -एक सॉर्ट ऑपरेटर की कमी पर ध्यान दें, क्योंकि स्कैन का आदेश दिया जा सकता है:चित्र 8:STRING_AGG() एक सहायक अनुक्रमणिका के साथ योजना
यह कुछ समय की छुट्टी भी देता है—लेकिन निष्पक्ष होने के लिए, यह अनुक्रमणिका
FOR XML PATH
में सहायता करती है विविधताएं भी। यहाँ उस क्वेरी के आदेशित संस्करण के लिए नई योजना है:चित्र 9:सहायक सूचकांक के साथ XML PATH योजना के लिए
यह योजना पहले की तुलना में थोड़ी मित्रवत है, जिसमें एक स्थान पर स्कैन के बजाय खोज करना शामिल है, लेकिन यह दृष्टिकोण अभी भी
STRING_AGG()
की तुलना में काफी धीमा है। ।एक चेतावनी
संदेश 9829, स्तर 16, राज्य 1
STRING_AGG()
. का उपयोग करने के लिए एक छोटी सी तरकीब है जहां, यदि परिणामी स्ट्रिंग 8,000 बाइट्स से अधिक है, तो आपको यह त्रुटि संदेश प्राप्त होगा:
STRING_AGG एकत्रीकरण परिणाम 8000 बाइट्स की सीमा को पार कर गया है। परिणाम में कटौती से बचने के लिए LOB प्रकारों का उपयोग करें।इस समस्या से बचने के लिए, आप एक स्पष्ट रूपांतरण इंजेक्ट कर सकते हैं:
उपयोगकर्ता आईडी चुनें, बैंड =STRING_AGG(CONVERT(nvarchar(max), BandName), N', ')-------------------------- -^^^^^^^^^^^^^^^^^^^^^ डीबीओ से। यूजर आईडी द्वारा पसंदीदा बैंड समूह;यह योजना में एक कंप्यूट स्केलर ऑपरेशन जोड़ता है—और एक आश्चर्यजनक
CONVERT
रूट पर चेतावनीSELECT
ऑपरेटर—लेकिन अन्यथा, इसका प्रदर्शन पर बहुत कम प्रभाव पड़ता है।निष्कर्ष
यदि आप SQL सर्वर 2017+ पर हैं और आपके पास कोई
FOR XML PATH
. है आपके कोडबेस में स्ट्रिंग एकत्रीकरण, मैं नए दृष्टिकोण पर स्विच करने की अत्यधिक अनुशंसा करता हूं। मैंने यहां SQL सर्वर 2017 सार्वजनिक पूर्वावलोकन के दौरान कुछ और गहन प्रदर्शन परीक्षण किया था और यहां आप फिर से आना चाहेंगे।एक आम आपत्ति जो मैंने सुनी है वह यह है कि लोग SQL सर्वर 2017 या उससे अधिक पर हैं लेकिन फिर भी पुराने संगतता स्तर पर हैं। ऐसा लगता है कि आशंका इसलिए है क्योंकि
STRING_SPLIT()
130 से कम संगतता स्तरों पर अमान्य है, इसलिए उन्हें लगता है किSTRING_AGG()
इस तरह से भी काम करता है, लेकिन यह थोड़ा अधिक उदार है। यह केवल एक समस्या है यदि आपWITHIN GROUP
. का उपयोग कर रहे हैं और एक संगत स्तर 110 से कम है। तो सुधार करें!