पिछले महीने, मैंने एक पहेली को कवर किया जिसमें एक तालिका से प्रत्येक पंक्ति को दूसरी तालिका से निकटतम मिलान के साथ मिलान करना शामिल था। मुझे यह पहेली आरबीसी के एक जूनियर फिक्स्ड इनकम एनालिस्ट कैरन लाइ से मिली है। मैंने दो मुख्य संबंधपरक समाधानों को कवर किया है जो लागू ऑपरेटर को शीर्ष-आधारित उपश्रेणियों के साथ जोड़ते हैं। समाधान 1 में हमेशा द्विघात स्केलिंग थी। सॉल्यूशन 2 ने अच्छा सपोर्टिंग इंडेक्स प्रदान किए जाने पर काफी अच्छा किया, लेकिन उन इंडेक्स के बिना क्वाड्रिक स्केलिंग भी थी। इस लेख में मैं पुनरावृत्त समाधानों को शामिल करता हूं, जो आमतौर पर SQL पेशेवरों द्वारा ठुकराए जाने के बावजूद, हमारे मामले में इष्टतम अनुक्रमण के बिना भी बेहतर स्केलिंग प्रदान करते हैं।
चुनौती
एक त्वरित अनुस्मारक के रूप में, हमारी चुनौती में T1 और T2 नामक तालिकाएँ शामिल हैं, जिन्हें आप निम्नलिखित कोड से बनाते हैं:
SET NOCOUNT ON; IF DB_ID('testdb') IS NULL CREATE DATABASE testdb; GO USE testdb; DROP TABLE IF EXISTS dbo.T1, dbo.T2; CREATE TABLE dbo.T1 ( keycol INT NOT NULL IDENTITY CONSTRAINT PK_T1 PRIMARY KEY, val INT NOT NULL, othercols BINARY(100) NOT NULL CONSTRAINT DFT_T1_col1 DEFAULT(0xAA) ); CREATE TABLE dbo.T2 ( keycol INT NOT NULL IDENTITY CONSTRAINT PK_T2 PRIMARY KEY, val INT NOT NULL, othercols BINARY(100) NOT NULL CONSTRAINT DFT_T2_col1 DEFAULT(0xBB) );
फिर आप अपने समाधानों की शुद्धता की जांच करने के लिए नमूना डेटा के छोटे सेट के साथ तालिकाओं को पॉप्युलेट करने के लिए निम्न कोड का उपयोग करते हैं:
TRUNCATE TABLE dbo.T1; TRUNCATE TABLE dbo.T2; INSERT INTO dbo.T1 (val) VALUES(1),(1),(3),(3),(5),(8),(13),(16),(18),(20),(21); INSERT INTO dbo.T2 (val) VALUES(2),(2),(7),(3),(3),(11),(11),(13),(17),(19);
याद रखें कि चुनौती T1 से प्रत्येक पंक्ति से T2 से मिलान करने की थी जहां T2.val और T1.val के बीच पूर्ण अंतर सबसे कम है। संबंधों के मामले में, आपको टाईब्रेकर के रूप में वैल आरोही, कीकोल आरोही क्रम का उपयोग करना चाहिए।
यहां दिए गए नमूना डेटा के लिए वांछित परिणाम दिया गया है:
keycol1 val1 othercols1 keycol2 val2 othercols2 ----------- ----------- ---------- ----------- ----------- ---------- 1 1 0xAA 1 2 0xBB 2 1 0xAA 1 2 0xBB 3 3 0xAA 4 3 0xBB 4 3 0xAA 4 3 0xBB 5 5 0xAA 4 3 0xBB 6 8 0xAA 3 7 0xBB 7 13 0xAA 8 13 0xBB 8 16 0xAA 9 17 0xBB 9 18 0xAA 9 17 0xBB 10 20 0xAA 10 19 0xBB 11 21 0xAA 10 19 0xBB
अपने समाधानों के प्रदर्शन की जांच करने के लिए आपको नमूना डेटा के बड़े सेट की आवश्यकता होती है। आप पहले हेल्पर फ़ंक्शन GetNums बनाते हैं, जो निम्न कोड का उपयोग करके अनुरोधित श्रेणी में पूर्णांकों का एक क्रम उत्पन्न करता है:
DROP FUNCTION IF EXISTS dbo.GetNums; GO CREATE OR ALTER FUNCTION dbo.GetNums(@low AS BIGINT, @high AS BIGINT) RETURNS TABLE AS RETURN WITH L0 AS (SELECT c FROM (SELECT 1 UNION ALL SELECT 1) AS D(c)), L1 AS (SELECT 1 AS c FROM L0 AS A CROSS JOIN L0 AS B), L2 AS (SELECT 1 AS c FROM L1 AS A CROSS JOIN L1 AS B), L3 AS (SELECT 1 AS c FROM L2 AS A CROSS JOIN L2 AS B), L4 AS (SELECT 1 AS c FROM L3 AS A CROSS JOIN L3 AS B), L5 AS (SELECT 1 AS c FROM L4 AS A CROSS JOIN L4 AS B), Nums AS (SELECT ROW_NUMBER() OVER(ORDER BY (SELECT NULL)) AS rownum FROM L5) SELECT TOP(@high - @low + 1) @low + rownum - 1 AS n FROM Nums ORDER BY rownum; GO
फिर आप निम्न कोड का उपयोग करके T1 और T2 को पॉप्युलेट करते हैं, अपनी आवश्यकताओं के आधार पर पंक्तियों की संख्या और अधिकतम मानों को इंगित करने वाले मापदंडों को समायोजित करते हैं:
DECLARE @numrowsT1 AS INT = 1000000, @maxvalT1 AS INT = 10000000, @numrowsT2 AS INT = 1000000, @maxvalT2 AS INT = 10000000; TRUNCATE TABLE dbo.T1; TRUNCATE TABLE dbo.T2; INSERT INTO dbo.T1 WITH(TABLOCK) (val) SELECT ABS(CHECKSUM(NEWID())) % @maxvalT1 + 1 AS val FROM dbo.GetNums(1, @numrowsT1) AS Nums; INSERT INTO dbo.T2 WITH(TABLOCK) (val) SELECT ABS(CHECKSUM(NEWID())) % @maxvalT2 + 1 AS val FROM dbo.GetNums(1, @numrowsT2) AS Nums;
इस उदाहरण में आप वैल कॉलम (कम घनत्व) में 1 - 10,000,000 की श्रेणी में मानों के साथ, 1,000,000 पंक्तियों वाली प्रत्येक तालिका को पॉप्युलेट कर रहे हैं।
समाधान 3, कर्सर और डिस्क-आधारित तालिका चर का उपयोग करके
हमारी निकटतम मैच चुनौती के लिए एक कुशल पुनरावृत्तीय समाधान मर्ज जॉइन एल्गोरिथम के समान एक एल्गोरिथम पर आधारित है। विचार कर्सर का उपयोग करके प्रत्येक तालिका के विरुद्ध केवल एक आदेशित पास लागू करना है, प्रत्येक दौर में ऑर्डरिंग और टाईब्रेकिंग तत्वों का मूल्यांकन करना है ताकि यह तय किया जा सके कि किस तरफ आगे बढ़ना है, और रास्ते में पंक्तियों का मिलान करना है।
प्रत्येक तालिका के खिलाफ आदेशित पास निश्चित रूप से सहायक अनुक्रमणिका से लाभान्वित होगा, लेकिन उनके न होने का निहितार्थ यह है कि स्पष्ट छँटाई होगी। इसका मतलब है कि छँटाई वाले हिस्से में n लॉग n स्केलिंग होगी, लेकिन यह समान परिस्थितियों में आपको समाधान 2 से मिलने वाली द्विघात स्केलिंग से बहुत कम गंभीर है।
साथ ही, वैल कॉलम के घनत्व से समाधान 1 और 2 का प्रदर्शन प्रभावित हुआ। उच्च घनत्व के साथ योजना ने कम रिबाइंड लागू किए। इसके विपरीत, चूंकि पुनरावृत्त समाधान प्रत्येक इनपुट के विरुद्ध केवल एक पास करते हैं, वैल कॉलम का घनत्व प्रदर्शन को प्रभावित करने वाला कारक नहीं है।
सहायक अनुक्रमणिका बनाने के लिए निम्न कोड का उपयोग करें:
CREATE INDEX idx_val_key ON dbo.T1(val, keycol) INCLUDE(othercols); CREATE INDEX idx_val_key ON dbo.T2(val, keycol) INCLUDE(othercols);
सुनिश्चित करें कि आप इन अनुक्रमणिकाओं के साथ और उनके बिना समाधानों का परीक्षण करते हैं।
यहाँ समाधान 3 के लिए पूरा कोड है:
SET NOCOUNT ON; BEGIN TRAN; DECLARE @keycol1 AS INT, @val1 AS INT, @othercols1 AS BINARY(100), @keycol2 AS INT, @val2 AS INT, @othercols2 AS BINARY(100), @prevkeycol2 AS INT, @prevval2 AS INT, @prevothercols2 AS BINARY(100), @C1 AS CURSOR, @C2 AS CURSOR, @C1fetch_status AS INT, @C2fetch_status AS INT; DECLARE @Result AS TABLE ( keycol1 INT NOT NULL PRIMARY KEY, val1 INT NOT NULL, othercols1 BINARY(100) NOT NULL, keycol2 INT NULL, val2 INT NULL, othercols2 BINARY(100) NULL ); SET @C1 = CURSOR FORWARD_ONLY STATIC READ_ONLY FOR SELECT keycol, val, othercols FROM dbo.T1 ORDER BY val, keycol; SET @C2 = CURSOR FORWARD_ONLY STATIC READ_ONLY FOR SELECT keycol, val, othercols FROM dbo.T2 ORDER BY val, keycol; OPEN @C1; OPEN @C2; FETCH NEXT FROM @C2 INTO @keycol2, @val2, @othercols2; SET @C2fetch_status = @@fetch_status; SELECT @prevkeycol2 = @keycol2, @prevval2 = @val2, @prevothercols2 = @othercols2; FETCH NEXT FROM @C1 INTO @keycol1, @val1, @othercols1; SET @C1fetch_status = @@fetch_status; WHILE @C1fetch_status = 0 BEGIN IF @val1 <= @val2 OR @C2fetch_status <> 0 BEGIN IF ABS(@val1 - @val2) < ABS(@val1 - @prevval2) INSERT INTO @Result(keycol1, val1, othercols1, keycol2, val2, othercols2) VALUES(@keycol1, @val1, @othercols1, @keycol2, @val2, @othercols2); ELSE INSERT INTO @Result(keycol1, val1, othercols1, keycol2, val2, othercols2) VALUES(@keycol1, @val1, @othercols1, @prevkeycol2, @prevval2, @prevothercols2); FETCH NEXT FROM @C1 INTO @keycol1, @val1, @othercols1; SET @C1fetch_status = @@fetch_status; END ELSE IF @C2fetch_status = 0 BEGIN IF @val2 > @prevval2 SELECT @prevkeycol2 = @keycol2, @prevval2 = @val2, @prevothercols2 = @othercols2; FETCH NEXT FROM @C2 INTO @keycol2, @val2, @othercols2; SET @C2fetch_status = @@fetch_status; END; END; SELECT keycol1, val1, SUBSTRING(othercols1, 1, 1) AS othercols1, keycol2, val2, SUBSTRING(othercols2, 1, 1) AS othercols2 FROM @Result; COMMIT TRAN;
कोड मैचों को संग्रहीत करने के लिए @Result नामक तालिका चर का उपयोग करता है और अंततः तालिका चर को क्वेरी करके उन्हें वापस कर देता है। ध्यान दें कि लॉगिंग को कम करने के लिए कोड एक लेनदेन में कार्य करता है।
कोड क्रमशः T1 और T2 में पंक्तियों के माध्यम से पुनरावृति करने के लिए @C1 और @C2 नामक कर्सर चर का उपयोग करता है, दोनों मामलों में val, keycol द्वारा आदेशित किया जाता है। स्थानीय चर का उपयोग प्रत्येक कर्सर (@keycol1, @val1 और @othercols1 के लिए @C1 और @keycol2, @val2 और @othercols2 @C2 के लिए) से वर्तमान पंक्ति मानों को संग्रहीत करने के लिए किया जाता है। अतिरिक्त स्थानीय चर पिछली पंक्ति मानों को @ C2 (@prevkeycol2, @prevval2 और @prevothercols2) से संग्रहीत करते हैं। चर @C1fetch_status और @C2fetch_status संबंधित कर्सर से अंतिम फ़ेच की स्थिति रखते हैं।
दोनों कर्सर को घोषित करने और खोलने के बाद, कोड प्रत्येक कर्सर से संबंधित स्थानीय चर में एक पंक्ति प्राप्त करता है, और शुरू में @C2 से वर्तमान पंक्ति मानों को पिछली पंक्ति चर में भी संग्रहीत करता है। कोड तब एक लूप में प्रवेश करता है जो चलता रहता है जबकि @C1 से अंतिम फ़ेच सफल रहा (@C1fetch_status =0)। लूप का शरीर प्रत्येक दौर में निम्नलिखित छद्म कोड लागू करता है:
If @val1 <= @val2 or reached end of @C2 Begin If absolute difference between @val1 and @val2 is less than between @val1 and @prevval2 Add row to @Result with current row values from @C1 and current row values from @C2 Else Add row to @Result with current row values from @C1 and previous row values from @C2 Fetch next row from @C1 End Else if last fetch from @C2 was successful Begin If @val2 > @prevval2 Set variables holding @C2’s previous row values to values of current row variables Fetch next row from @C2 End
कोड तब सभी मैचों को वापस करने के लिए टेबल वैरिएबल @Result से पूछताछ करता है।
नमूना डेटा के बड़े सेट (प्रत्येक तालिका में 1,000,000 पंक्तियों) का उपयोग करते हुए, इष्टतम अनुक्रमण के साथ, इस समाधान को मेरे सिस्टम पर पूरा होने में 38 सेकंड का समय लगा, और 28,240 तार्किक रीड्स का प्रदर्शन किया। बेशक, इस समाधान की स्केलिंग तब रैखिक होती है। इष्टतम अनुक्रमण के बिना, इसे पूरा करने में 40 सेकंड का समय लगा (केवल 2 सेकंड अतिरिक्त!), और 29,519 तार्किक रीड्स किए। इस समाधान में छँटाई वाले हिस्से में n लॉग n स्केलिंग है।
समाधान 4, कर्सर और स्मृति-अनुकूलित तालिका चर का उपयोग करके
पुनरावृत्त दृष्टिकोण के प्रदर्शन को बेहतर बनाने के प्रयास में, एक चीज जिसे आप आजमा सकते हैं वह है डिस्क-आधारित तालिका चर के उपयोग को स्मृति-अनुकूलित एक के साथ बदलना। चूँकि समाधान में तालिका चर में 1,000,000 पंक्तियाँ लिखना शामिल है, इसका परिणाम गैर-नगण्य सुधार हो सकता है।
सबसे पहले, आपको CONTAINS MEMORY_OPTIMIZED_DATA के रूप में चिह्नित एक फ़ाइल समूह बनाकर डेटाबेस में इन-मेमोरी OLTP को सक्षम करने की आवश्यकता है, और इसके भीतर एक कंटेनर जो फ़ाइल सिस्टम में एक फ़ोल्डर को इंगित करता है। यह मानते हुए कि आपने C:\IMOLTP\ नामक एक पैरेंट फ़ोल्डर आगे बनाया है, इन दो चरणों को लागू करने के लिए निम्न कोड का उपयोग करें:
ALTER DATABASE testdb ADD FILEGROUP testdb_MO CONTAINS MEMORY_OPTIMIZED_DATA; ALTER DATABASE testdb ADD FILE ( NAME = testdb_dir, FILENAME = 'C:\IMOLTP\testdb_dir' ) TO FILEGROUP testdb_MO;
अगला कदम निम्नलिखित कोड को चलाकर हमारे टेबल वेरिएबल के लिए एक टेम्पलेट के रूप में एक मेमोरी ऑप्टिमाइज़्ड टेबल टाइप बनाना है:
DROP TYPE IF EXISTS dbo.TYPE_closestmatch; GO CREATE TYPE dbo.TYPE_closestmatch AS TABLE ( keycol1 INT NOT NULL PRIMARY KEY NONCLUSTERED, val1 INT NOT NULL, othercols1 BINARY(100) NOT NULL, keycol2 INT NULL, val2 INT NULL, othercols2 BINARY(100) NULL ) WITH (MEMORY_OPTIMIZED = ON);
फिर तालिका चर @Result की मूल घोषणा के बजाय, आप निम्न कोड का उपयोग करेंगे:
DECLARE @Result AS dbo.TYPE_closestmatch;
यहां संपूर्ण समाधान कोड दिया गया है:
SET NOCOUNT ON; USE testdb; BEGIN TRAN; DECLARE @keycol1 AS INT, @val1 AS INT, @othercols1 AS BINARY(100), @keycol2 AS INT, @val2 AS INT, @othercols2 AS BINARY(100), @prevkeycol2 AS INT, @prevval2 AS INT, @prevothercols2 AS BINARY(100), @C1 AS CURSOR, @C2 AS CURSOR, @C1fetch_status AS INT, @C2fetch_status AS INT; DECLARE @Result AS dbo.TYPE_closestmatch; SET @C1 = CURSOR FORWARD_ONLY STATIC READ_ONLY FOR SELECT keycol, val, othercols FROM dbo.T1 ORDER BY val, keycol; SET @C2 = CURSOR FORWARD_ONLY STATIC READ_ONLY FOR SELECT keycol, val, othercols FROM dbo.T2 ORDER BY val, keycol; OPEN @C1; OPEN @C2; FETCH NEXT FROM @C2 INTO @keycol2, @val2, @othercols2; SET @C2fetch_status = @@fetch_status; SELECT @prevkeycol2 = @keycol2, @prevval2 = @val2, @prevothercols2 = @othercols2; FETCH NEXT FROM @C1 INTO @keycol1, @val1, @othercols1; SET @C1fetch_status = @@fetch_status; WHILE @C1fetch_status = 0 BEGIN IF @val1 <= @val2 OR @C2fetch_status <> 0 BEGIN IF ABS(@val1 - @val2) < ABS(@val1 - @prevval2) INSERT INTO @Result(keycol1, val1, othercols1, keycol2, val2, othercols2) VALUES(@keycol1, @val1, @othercols1, @keycol2, @val2, @othercols2); ELSE INSERT INTO @Result(keycol1, val1, othercols1, keycol2, val2, othercols2) VALUES(@keycol1, @val1, @othercols1, @prevkeycol2, @prevval2, @prevothercols2); FETCH NEXT FROM @C1 INTO @keycol1, @val1, @othercols1; SET @C1fetch_status = @@fetch_status; END ELSE IF @C2fetch_status = 0 BEGIN IF @val2 > @prevval2 SELECT @prevkeycol2 = @keycol2, @prevval2 = @val2, @prevothercols2 = @othercols2; FETCH NEXT FROM @C2 INTO @keycol2, @val2, @othercols2; SET @C2fetch_status = @@fetch_status; END; END; SELECT keycol1, val1, SUBSTRING(othercols1, 1, 1) AS othercols1, keycol2, val2, SUBSTRING(othercols2, 1, 1) AS othercols2 FROM @Result; COMMIT TRAN;
इष्टतम अनुक्रमण के साथ इस समाधान को मेरी मशीन पर पूरा होने में 27 सेकंड का समय लगा (डिस्क-आधारित तालिका चर के साथ 38 सेकंड की तुलना में), और इष्टतम अनुक्रमण के बिना इसे पूरा होने में 29 सेकंड लगे (40 सेकंड की तुलना में)। यह रन टाइम में लगभग 30 प्रतिशत की कमी है।
समाधान 5, SQL CLR का उपयोग कर
पुनरावृत्त दृष्टिकोण के प्रदर्शन को और बेहतर बनाने का एक अन्य तरीका एसक्यूएल सीएलआर का उपयोग करके समाधान को लागू करना है, यह देखते हुए कि टी-एसक्यूएल समाधान का अधिकांश ओवरहेड टी-एसक्यूएल में कर्सर लाने और लूपिंग की अक्षमताओं के कारण है।
टी-एसक्यूएल कर्सर के बजाय एसक्लडाटा रीडर ऑब्जेक्ट्स का उपयोग करके सी # के साथ समाधान 3 और 4 में उपयोग किए गए समान एल्गोरिदम को लागू करने वाला संपूर्ण समाधान कोड यहां दिया गया है:
using System; using System.Data; using System.Data.SqlClient; using System.Data.SqlTypes; using Microsoft.SqlServer.Server; public partial class ClosestMatch { [SqlProcedure] public static void GetClosestMatches() { using (SqlConnection conn = new SqlConnection("data source=MyServer\\MyInstance;Database=testdb;Trusted_Connection=True;MultipleActiveResultSets=true;")) { SqlCommand comm1 = new SqlCommand(); SqlCommand comm2 = new SqlCommand(); comm1.Connection = conn; comm2.Connection = conn; comm1.CommandText = "SELECT keycol, val, othercols FROM dbo.T1 ORDER BY val, keycol;"; comm2.CommandText = "SELECT keycol, val, othercols FROM dbo.T2 ORDER BY val, keycol;"; SqlMetaData[] columns = new SqlMetaData[6]; columns[0] = new SqlMetaData("keycol1", SqlDbType.Int); columns[1] = new SqlMetaData("val1", SqlDbType.Int); columns[2] = new SqlMetaData("othercols1", SqlDbType.Binary, 100); columns[3] = new SqlMetaData("keycol2", SqlDbType.Int); columns[4] = new SqlMetaData("val2", SqlDbType.Int); columns[5] = new SqlMetaData("othercols2", SqlDbType.Binary, 100); SqlDataRecord record = new SqlDataRecord(columns); SqlContext.Pipe.SendResultsStart(record); conn.Open(); SqlDataReader reader1 = comm1.ExecuteReader(); SqlDataReader reader2 = comm2.ExecuteReader(); SqlInt32 keycol1 = SqlInt32.Null; SqlInt32 val1 = SqlInt32.Null; SqlBinary othercols1 = SqlBinary.Null; SqlInt32 keycol2 = SqlInt32.Null; SqlInt32 val2 = SqlInt32.Null; SqlBinary othercols2 = SqlBinary.Null; SqlInt32 prevkeycol2 = SqlInt32.Null; SqlInt32 prevval2 = SqlInt32.Null; SqlBinary prevothercols2 = SqlBinary.Null; Boolean reader2foundrow = reader2.Read(); if (reader2foundrow) { keycol2 = reader2.GetSqlInt32(0); val2 = reader2.GetSqlInt32(1); othercols2 = reader2.GetSqlBinary(2); prevkeycol2 = keycol2; prevval2 = val2; prevothercols2 = othercols2; } Boolean reader1foundrow = reader1.Read(); if (reader1foundrow) { keycol1 = reader1.GetSqlInt32(0); val1 = reader1.GetSqlInt32(1); othercols1 = reader1.GetSqlBinary(2); } while (reader1foundrow) { if (val1 <= val2 || !reader2foundrow) { if (Math.Abs((int)(val1 - val2)) < Math.Abs((int)(val1 - prevval2))) { record.SetSqlInt32(0, keycol1); record.SetSqlInt32(1, val1); record.SetSqlBinary(2, othercols1); record.SetSqlInt32(3, keycol2); record.SetSqlInt32(4, val2); record.SetSqlBinary(5, othercols2); SqlContext.Pipe.SendResultsRow(record); } else { record.SetSqlInt32(0, keycol1); record.SetSqlInt32(1, val1); record.SetSqlBinary(2, othercols1); record.SetSqlInt32(3, prevkeycol2); record.SetSqlInt32(4, prevval2); record.SetSqlBinary(5, prevothercols2); SqlContext.Pipe.SendResultsRow(record); } reader1foundrow = reader1.Read(); if (reader1foundrow) { keycol1 = reader1.GetSqlInt32(0); val1 = reader1.GetSqlInt32(1); othercols1 = reader1.GetSqlBinary(2); } } else if (reader2foundrow) { if (val2 > prevval2) { prevkeycol2 = keycol2; prevval2 = val2; prevothercols2 = othercols2; } reader2foundrow = reader2.Read(); if (reader2foundrow) { keycol2 = reader2.GetSqlInt32(0); val2 = reader2.GetSqlInt32(1); othercols2 = reader2.GetSqlBinary(2); } } } SqlContext.Pipe.SendResultsEnd(); } } }
डेटाबेस से कनेक्ट करने के लिए आप सामान्य रूप से पूर्ण विकसित कनेक्शन स्ट्रिंग के बजाय "संदर्भ कनेक्शन =सत्य" विकल्प का उपयोग करेंगे। दुर्भाग्य से, यह विकल्प तब उपलब्ध नहीं होता है जब आपको कई सक्रिय परिणाम सेट के साथ काम करने की आवश्यकता होती है। हमारा समाधान दो SqlDataReader ऑब्जेक्ट्स का उपयोग करके दो कर्सर के साथ समानांतर कार्य का अनुकरण करता है, और इसलिए आपको MultiActiveResultSets=true विकल्प के साथ एक पूर्ण विकसित कनेक्शन स्ट्रिंग की आवश्यकता है। ये रही पूरी कनेक्शन स्ट्रिंग:
"data source=MyServer\\MyInstance;Database=testdb;Trusted_Connection=True;MultipleActiveResultSets=true;"
बेशक आपके मामले में आपको MyServer\\MyInstance को अपने सर्वर और इंस्टेंस (यदि प्रासंगिक हो) नामों से बदलना होगा।
साथ ही, तथ्य यह है कि आपने "संदर्भ कनेक्शन =सत्य" का उपयोग नहीं किया बल्कि एक स्पष्ट कनेक्शन स्ट्रिंग का अर्थ है कि असेंबली को बाहरी संसाधन तक पहुंच की आवश्यकता है और इसलिए भरोसा किया जाना चाहिए। आम तौर पर, आप इसे एक प्रमाण पत्र या एक असममित कुंजी के साथ हस्ताक्षर करके प्राप्त करेंगे, जिसमें सही अनुमति के साथ संबंधित लॉगिन है, या इसे sp_add_trusted_assembly प्रक्रिया का उपयोग करके श्वेतसूची में डाल दें। सादगी के लिए, मैं डेटाबेस विकल्प TRUSTWORTHY को चालू पर सेट करूंगा, और असेंबली बनाते समय EXTERNAL_ACCESS अनुमति सेट निर्दिष्ट करूंगा। निम्नलिखित कोड डेटाबेस में समाधान को परिनियोजित करता है:
EXEC sys.sp_configure 'advanced', 1; RECONFIGURE; EXEC sys.sp_configure 'clr enabled', 1; EXEC sys.sp_configure 'clr strict security', 0; RECONFIGURE; EXEC sys.sp_configure 'advanced', 0; RECONFIGURE; ALTER DATABASE testdb SET TRUSTWORTHY ON; USE testdb; DROP PROC IF EXISTS dbo.GetClosestMatches; DROP ASSEMBLY IF EXISTS ClosestMatch; CREATE ASSEMBLY ClosestMatch FROM 'C:\ClosestMatch\ClosestMatch\bin\Debug\ClosestMatch.dll' WITH PERMISSION_SET = EXTERNAL_ACCESS; GO CREATE PROCEDURE dbo.GetClosestMatches AS EXTERNAL NAME ClosestMatch.ClosestMatch.GetClosestMatches;
कोड उदाहरण में CLR को सक्षम बनाता है, CLR सख्त सुरक्षा विकल्प को अक्षम करता है, डेटाबेस विकल्प TRUSTWORTHY को ON पर सेट करता है, असेंबली बनाता है, और प्रक्रिया GetClosestMatches बनाता है।
संग्रहीत कार्यविधि का परीक्षण करने के लिए निम्न कोड का उपयोग करें:
EXEC dbo.GetClosestMatches;
सीएलआर समाधान को मेरे सिस्टम पर इष्टतम अनुक्रमण के साथ पूरा करने में 8 सेकंड लगे, और बिना 9 सेकंड के। यह अन्य सभी समाधानों की तुलना में एक बहुत प्रभावशाली प्रदर्शन सुधार है - दोनों संबंधपरक और पुनरावृत्तीय।
निष्कर्ष
SQL समुदाय में इटरेटिव सॉल्यूशंस को विशेष रूप से पसंद किया जाता है क्योंकि वे रिलेशनल मॉडल का पालन नहीं करते हैं। हालांकि वास्तविकता यह है कि कभी-कभी आप अच्छा प्रदर्शन करने वाले संबंधपरक समाधान नहीं बना पाते हैं और प्रदर्शन प्राथमिकता होती है। एक पुनरावृत्त दृष्टिकोण का उपयोग करते हुए, आप उस एल्गोरिदम तक सीमित नहीं हैं जिस तक SQL सर्वर ऑप्टिमाइज़र की पहुंच है, बल्कि आप अपनी पसंद के किसी भी एल्गोरिदम को लागू कर सकते हैं। जैसा कि इस आलेख में दिखाया गया है, मर्ज-जैसे एल्गोरिदम का उपयोग करके, आप प्रत्येक इनपुट के विरुद्ध एकल आदेशित पास के साथ कार्य को प्राप्त करने में सक्षम थे। टी-एसक्यूएल कर्सर और डिस्क-आधारित तालिका चर का उपयोग करके आपको उचित प्रदर्शन और स्केलिंग मिली। आप स्मृति अनुकूलित तालिका चर पर स्विच करके, और SQL CLR का उपयोग करके उल्लेखनीय रूप से अधिक प्रदर्शन करके प्रदर्शन में लगभग 30 प्रतिशत सुधार करने में सक्षम थे।