Database
 sql >> база данни >  >> RDS >> Database

Най-близкото съвпадение, част 2

Миналия месец разгледах пъзел, включващ съпоставяне на всеки ред от една таблица с най-близкото съвпадение от друга таблица. Получих този пъзел от Карън Ли, младши анализатор по фиксирани доходи в RBC. Обхванах две основни релационни решения, които комбинираха оператора APPLY с ТОП-базирани подзаявки. Решение 1 винаги е имало квадратично мащабиране. Решение 2 се справи доста добре, когато беше снабдено с добри поддържащи индекси, но без тези индекси имаше и квадратично мащабиране. В тази статия разглеждам итеративните решения, които въпреки че като цяло не се харесват от SQL професионалистите, осигуряват много по-добро мащабиране в нашия случай дори без оптимално индексиране.

Предизвикателството

Като бързо напомняне, нашето предизвикателство включва таблици, наречени T1 и T2, които създавате със следния код:

 ЗАДАДЕТЕ NOCOUNT ON; АКО DB_ID('testdb') Е NULL СЪЗДАВАНЕ НА БАЗА ДАННИ testdb; ИЗПОЛЗВАЙТЕ testdb; ИЗПУСКАНЕ ТАБЛИЦА, АКО СЪЩЕСТВУВА 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 ОГРАНИЧЕНИЕ DFT_T1_col1 DEFAULT(0xAA) ); СЪЗДАВАНЕ НА ТАБЛИЦА dbo.T2 ( keycol INT NOT NULL ОГРАНИЧЕНИЕ НА ИДЕНТИЧНОСТ PK_T2 ПЪРВИЧЕН КЛЮЧ, val INT NOT NULL, други cols BINARY(100) NOT NULL ОГРАНИЧЕНИЕ DFT_T2_col1 DEFAULT(0xBB) );

След това използвате следния код, за да попълните таблиците с малки набори от примерни данни, за да проверите правилността на вашите решения:

 ОТСЪЩАНЕ НА ТАБЛИЦА dbo.T1; ТАБЛИЦА ОТСЪЖАНЕ dbo.T2; ВМЕСТЕ В dbo.T1 (val) СТОЙНОСТИ(1),(1),(3),(3),(5),(8),(13),(16),(18),(20),( 21); ВМЕСТЕ В dbo.T2 (val) СТОЙНОСТИ(2),(2),(7),(3),(3),(11),(11),(13),(17),(19); 

Припомнете си, че предизвикателството беше да съпоставим на всеки ред от T1 реда от T2, където абсолютната разлика между T2.val и T1.val е най-ниската. В случай на равенство, трябва да използвате възходящ ред val, възходящ ред на keycol като тайбрейк.

Ето желания резултат за дадените примерни данни:

 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 8 16 0xAA 9 17 0xBB 9 18 0xAA 9 17 0xBB 10 20 0xAA 10 19 0x10 BB0 10 19 0x1 AA 

За да проверите производителността на вашите решения, имате нужда от по-големи набори от примерни данни. Първо създавате помощната функция GetNums, която генерира поредица от цели числа в заявен диапазон, като използвате следния код:

 ИЗПУСКАНЕ ФУНКЦИЯ, АКО СЪЩЕСТВУВА dbo.GetNums; ИЗБИРАЙТЕ СЪЗДАВАТЕ ИЛИ ПРОМЕНЯТЕ ФУНКЦИЯ dbo.GetNums(@low AS BIGINT, @high AS BIGINT) ВРЪЩА ТАБЛИЦА КАТО ВЪЗВРЪЩАНЕ С L0 AS (ИЗБЕРЕТЕ c ОТ (ИЗБЕРЕТЕ 1 СЪЕДИНЕНИЕ ВСИЧКИ SELECT 1) КАТО D(c)), L1 КАТО (ИЗБЕРЕТЕ 1 КАТО c ОТ L0 КАТО КРЪСТО СЪЕДИНЕНИЕ L0 AS B), L2 AS (ИЗБЕРЕТЕ 1 КАТО c ОТ L1 КАТО КРЪСТО СЪЕДИНЕНИЕ L1 AS B), L3 AS (ИЗБЕРЕТЕ 1 КАТО c ОТ L2 КАТО КРЪСТО СЪЕДИНЕНИЕ L2 AS B), L4 AS (ИЗБЕРЕТЕ 1 КАТО c ОТ L3 КАТО КРЪСТО СЪЕДИНЕНИЕ L3 КАТО B), L5 AS (ИЗБЕРЕТЕ 1 КАТО c ОТ L4 КАТО КРЪСТО СЪЕДИНЕНИЕ L4 КАТО B), Nums AS (ИЗБЕРЕТЕ ROW_NUMBER() НАД (ПОРЪЧАЙТЕ ОТ (ИЗБЕРЕТЕ NULL) ) КАТО rownum ОТ L5) SELECT TOP(@high - @low + 1) @low + rownum - 1 КАТО n FROM Nums ORDER BY rownum; ОТПРАВИ

След това попълвате T1 и T2, като използвате следния код, като коригирате параметрите, указващи броя на редовете и максималните стойности въз основа на вашите нужди:

 ДЕКЛАРИРАНЕ @numrowsT1 КАТО INT =1000000, @maxvalT1 КАТО INT =10000000, @numrowsT2 КАТО INT =1000000, @maxvalT2 КАТО INT =10000000; ТАБЛИЦА ОТСЪЖАНЕ dbo.T1; ТАБЛИЦА ОТСЪЖАНЕ dbo.T2; INSERT INTO dbo.T1 WITH(TABLOCK) (val) SELECT ABS(CHECKSSUM(NEWID())) % @maxvalT1 + 1 AS val FROM dbo.GetNums(1, @numrowsT1) AS Nums; INSERT INTO dbo.T2 WITH(TABLOCK) (val) SELECT ABS(COCKKSUM(NEWID())) % @maxvalT2 + 1 AS val FROM dbo.GetNums(1, @numrowsT2) AS Nums;

В този пример попълвате таблиците с по 1 000 000 реда всяка, със стойности в диапазона 1 – 10 000 000 в колоната val (ниска плътност).

Решение 3, използвайки курсор и дискова променлива на таблица

Ефективно итеративно решение за нашето най-близко съвпадение се основава на алгоритъм, подобен на алгоритъма за свързване с обединяване. Идеята е да се приложи само един подреден пас срещу всяка маса, като се използват курсори, като се оценяват елементите за подреждане и тайбрейк във всеки рунд, за да се реши от коя страна да се премине, и се съпоставят редовете по пътя.

Подреденият пропуск към всяка таблица със сигурност ще се възползва от поддържащи индекси, но изводът от липсата на такива е, че ще се извърши изрично сортиране. Това означава, че частта за сортиране ще има n log n мащабиране, но това е много по-малко сериозно от квадратичното мащабиране, което получавате от Решение 2 при подобни обстоятелства.

Също така, производителността на разтвори 1 и 2 беше повлияна от плътността на колоната val. При по-висока плътност планът прилага по-малко превързвания. Обратно, тъй като итеративните решения извършват само едно преминаване срещу всеки от входовете, плътността на колоната val не е фактор, влияещ върху производителността.

Използвайте следния код, за да създадете поддържащи индекси:

 СЪЗДАЙТЕ ИНДЕКС idx_val_key ON dbo.T1(val, keycol) INCLUDE(othercols); СЪЗДАЙТЕ ИНДЕКС idx_val_key НА dbo.T2(val, keycol) INCLUDE(othercols);

Уверете се, че сте тествали решенията както със, така и без тези индекси.

Ето пълния код за Решение 3:

 ЗАДАДЕТЕ NOCOUNT ON; ЗАПОЧНЕТЕ TRAN; ДЕКЛАРИРАНЕ @keycol1 КАТО INT, @val1 КАТО INT, @othercols1 КАТО BINARY(100), @keycol2 КАТО INT, @val2 КАТО INT, @othercols2 КАТО BINARY(100), @prevkeycol2 КАТО INT, @prevval2 КАТО INT, @prevothercol BINARY(100), @C1 КАТО КУРСОР, @C2 КАТО КУРСОР, @C1fetch_status КАТО INT, @C2fetch_status КАТО INT; ДЕКЛАРИРАНЕ @Result КАТО ТАБЛИЦА ( 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; ОТВОРЕНО @C1; ОТВОРЕНО @C2; ИЗВЛЕКАНЕ СЛЕДВАЩО ОТ @C2 В @keycol2, @val2, @othercols2; SET @C2fetch_status =@@fetch_status; SELECT @prevkeycol2 =@keycol2, @prevval2 =@val2, @prevothercols2 =@othercols2; ИЗВЛЕКАНЕ СЛЕДВАЩО ОТ @C1 В @keycol1, @val1, @othercols1; SET @C1fetch_status =@@fetch_status; ДОКАТО @C1fetch_status =0 BEGIN IF @val1 <=@val2 ИЛИ @C2fetch_status <> 0 BEGIN IF ABS(@val1 - @val2)  @prevval2 SELECT @prevkeycol2 =@keycol2, @prevval2 =@val2, @prevothercols2 =@othercols2; ИЗВЛЕКАНЕ СЛЕДВАЩО ОТ @C2 В @keycol2, @val2, @othercols2; SET @C2fetch_status =@@fetch_status; КРАЙ; КРАЙ; SELECT keycol1, val1, SUBSTRING(othercols1, 1, 1) AS othercols1, keycol2, val2, SUBSTRING(othercols2, 1, 1) AS othercols2 ОТ @Result; COMMIT TRAN;

Кодът използва таблична променлива, наречена @Result, за да съхранява съвпаденията и в крайна сметка ги връща чрез запитване на променливата на таблицата. Забележете, че кодът изпълнява работата в една транзакция, за да намали регистрирането.

Кодът използва курсорни променливи, наречени @C1 и @C2, за да преглежда редове в T1 и T2, съответно, и в двата случая подредени по val, keycol. Локалните променливи се използват за съхраняване на текущите стойности на редове от всеки курсор (@keycol1, @val1 и @othercols1 за @C1 и @keycol2, @val2 и @othercols2 за @C2). Допълнителните локални променливи съхраняват стойностите на предишния ред от @C2 (@prevkeycol2, @prevval2 и @prevothercols2). Променливите @C1fetch_status и @C2fetch_status държат състоянието на последното извличане от съответния курсор.

След деклариране и отваряне на двата курсора, кодът извлича ред от всеки курсор в съответните локални променливи и първоначално съхранява текущите стойности на ред от @C2 също в предишните променливи на ред. След това кодът влиза в цикъл, който продължава да работи, докато последното извличане от @C1 е било успешно (@C1fetch_status =0). Тялото на цикъла прилага следния псевдокод във всеки кръг:

 Ако @val1 <=@val2 или достигнат края на @C2 Начало Ако абсолютната разлика между @val1 и @val2 е по-малка от между @val1 и @prevval2 Добавете ред към @Result с текущи стойности на ред от @C1 и текущия ред стойности от @C2 Иначе Добавяне на ред към @Result с текущи стойности на ред от @C1 и стойности на предишния ред от @C2 Извличане на следващия ред от @C1 Край Иначе, ако последното извличане от @C2 е било успешно Начало Ако @val2> @prevval2 Задаване на променливи задържане Стойностите на предишния ред на @C2 към стойностите на текущите променливи на реда Извличане на следващия ред от @C2 End

След това кодът просто отправя заявка към променливата на таблицата @Result, за да върне всички съвпадения.

Използвайки големите набори от примерни данни (1 000 000 реда във всяка таблица), с оптимално индексиране, това решение отне 38 секунди за завършване в моята система и извърши 28 240 логически четения. Разбира се, мащабирането на това решение е линейно. Без оптимално индексиране са били необходими 40 секунди за завършване (само 2 секунди допълнително!) и са извършени 29 519 логически четения. Частта за сортиране в това решение има n log n мащабиране.

Решение 4, използвайки курсор и оптимизирана за памет променлива на таблица

В опит да подобрите производителността на итеративния подход, едно нещо, което можете да опитате, е да замените използването на базираната на диск променлива на таблицата с оптимизирана за памет. Тъй като решението включва записване на 1 000 000 реда в променливата на таблицата, това може да доведе до непренебрежимо подобрение.

Първо, трябва да активирате In-Memory OLTP в базата данни, като създадете файлова група, която е маркирана като СЪДЪРЖИ MEMORY_OPTIMIZED_DATA, и в нея контейнер, който сочи към папка във файловата система. Ако приемем, че сте създали родителска папка, наречена C:\IMOLTP\, използвайте следния код, за да приложите тези две стъпки:

 ПРОМЕНИ БАЗА ДАННИ testdb ДОБАВЯНЕ НА ФАЙЛОВА ГРУПА testdb_MO СЪДЪРЖА MEMORY_OPTIMIZED_DATA; ПРОМЕНИ БАЗА ДАННИ testdb ДОБАВЯНЕ НА ФАЙЛ ( ИМЕ =testdb_dir, FILENAME ='C:\IMOLTP\testdb_dir') КЪМ ФАЙЛОВА ГРУПА testdb_MO;

Следващата стъпка е да създадете оптимизиран за памет тип таблица като шаблон за нашата променлива на таблица, като изпълните следния код:

 ИЗПУСКАНЕ ТИП, АКО СЪЩЕСТВУВА dbo.TYPE_closestmatch; СЪЗДАДЕТЕ ТИП dbo.TYPE_closestmatch КАТО ТАБЛИЦА ( keycol1 INT NOT NULL ПЪРВИЧЕН КЛЮЧ НЕКЛУСТРИРАН, val1 INT НЕ NULL, othercols1 BINARY(100) NOT NULL, keycol2 INT NULL, val2 INT NULL, val2 INT NULL NULL (val2 INT NULL NULL) );

След това вместо оригиналната декларация на табличната променлива @Result, ще използвате следния код:

 ДЕКЛАРИРАНЕ @Result КАТО dbo.TYPE_closestmatch;

Ето пълния код на решението:

 ЗАДАДЕТЕ NOCOUNT ON; ИЗПОЛЗВАЙТЕ testdb; ЗАПОЧНЕТЕ TRAN; ДЕКЛАРИРАНЕ @keycol1 КАТО INT, @val1 КАТО INT, @othercols1 КАТО BINARY(100), @keycol2 КАТО INT, @val2 КАТО INT, @othercols2 КАТО BINARY(100), @prevkeycol2 КАТО INT, @prevval2 КАТО INT, @prevothercol BINARY(100), @C1 КАТО КУРСОР, @C2 КАТО КУРСОР, @C1fetch_status КАТО INT, @C2fetch_status КАТО INT; ДЕКЛАРИРАНЕ @Result КАТО 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; ОТВОРЕНО @C1; ОТВОРЕНО @C2; ИЗВЛЕКАНЕ СЛЕДВАЩО ОТ @C2 В @keycol2, @val2, @othercols2; SET @C2fetch_status =@@fetch_status; SELECT @prevkeycol2 =@keycol2, @prevval2 =@val2, @prevothercols2 =@othercols2; ИЗВЛЕКАНЕ СЛЕДВАЩО ОТ @C1 В @keycol1, @val1, @othercols1; SET @C1fetch_status =@@fetch_status; ДОКАТО @C1fetch_status =0 BEGIN IF @val1 <=@val2 ИЛИ @C2fetch_status <> 0 BEGIN IF ABS(@val1 - @val2)  @prevval2 SELECT @prevkeycol2 =@keycol2, @prevval2 =@val2, @prevothercols2 =@othercols2; ИЗВЛЕКАНЕ СЛЕДВАЩО ОТ @C2 В @keycol2, @val2, @othercols2; SET @C2fetch_status =@@fetch_status; КРАЙ; КРАЙ; SELECT keycol1, val1, SUBSTRING(othercols1, 1, 1) AS othercols1, keycol2, val2, SUBSTRING(othercols2, 1, 1) AS othercols2 ОТ @Result; COMMIT TRAN;

С оптималното индексиране на място, това решение отне 27 секунди за завършване на моята машина (в сравнение с 38 секунди с променливата на дисково базирана таблица), а без оптимално индексиране отне 29 секунди за завършване (в сравнение с 40 секунди). Това е близо 30 процента намаляване на времето за изпълнение.

Решение 5, използвайки SQL CLR

Друг начин за по-нататъшно подобряване на производителността на итеративния подход е да се внедри решението с помощта на SQL CLR, като се има предвид, че по-голямата част от режийните разходи на T-SQL решението се дължат на неефективността на извличането на курсора и цикъла в T-SQL.

Ето пълния код на решение, внедряващ същия алгоритъм, който използвах в решения 3 и 4 с C#, използвайки обекти SqlDataReader вместо T-SQL курсори:

 с помощта на System; използване на System.Data; използване на System.Data.SqlClient; използване на System.Data.SqlTypes; използване на Microsoft.SqlServer.Server; публичен частичен клас ClosestMatch { [SqlProcedure] public static void GetClosestMatches() { using (SqlConnection conn =new SqlConnection("data source=MyServer\\MyInstance;Database=testdb;Trusted_Connection=True;MultipleScomnection=True;MultipleScomnection=True;MultipleScommand=Command)"SqlConnection=SqlConnection; =нова SqlCommand(); SqlCommand comm2 =нов SqlCommand(); comm1.Connection =връзка; comm2.Connection =връзка; comm1.CommandText ="ИЗБЕРЕТЕ keycol, val, othercols ОТ dbo.T1 ПОРЪЧАЙТЕ ПО val, keycol;"; comm2.CommandText ="ИЗБЕРЕТЕ keycol, val, othercols ОТ dbo.T2 ПОРЪЧАЙТЕ ПО val, keycol;"; SqlMetaData[] колони =нови SqlMetaData[6]; columns[0] =new SqlMetaData("keycol1", SqlDbType.Int); columns[1] =new SqlMetaData("val1", SqlDbType.Int); колони[2] =нов 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 запис =нов SqlDataRecord(колони); SqlContext.Pipe.SendResultsStart(запис); 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; Булев reader2foundrow =reader2.Read(); if (reader2foundrow) { keycol2 =reader2.GetSqlInt32(0); val2 =reader2.GetSqlInt32(1); othercols2 =reader2.GetSqlBinary(2); prevkeycol2 =keycol2; prevval2 =val2; prevothercols2 =othercols2; } Булев 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))  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(); } } }

За да се свържете с базата данни, обикновено използвате опцията "context connection=true" вместо пълен низ за връзка. За съжаление тази опция не е налична, когато трябва да работите с множество активни набори от резултати. Нашето решение емулира паралелна работа с два курсора, използващи два обекта SqlDataReader и следователно имате нужда от пълен низ за връзка с опцията MultipleActiveResultSets=true. Ето пълния низ за връзка:

 "data source=MyServer\\MyInstance;Database=testdb;Trusted_Connection=True;MultipleActiveResultSets=true;"

Разбира се, във вашия случай ще трябва да замените MyServer\\MyInstance с имената на вашия сървър и екземпляр (ако има значение).

Освен това фактът, че не сте използвали „контекстна връзка=true“, а изрично низ за връзка, означава, че сборката се нуждае от достъп до външен ресурс и следователно има доверие. Обикновено бихте постигнали това, като го подпишете със сертификат или асиметричен ключ, който има съответното влизане с правилното разрешение, или го поставите в белия списък с помощта на процедурата sp_add_trusted_assembly. За простота ще задам опцията на базата данни TRUSTWORTHY на ON и ще посоча разрешението EXTERNAL_ACCESS, зададено при създаване на сборката. Следният код разгръща решението в базата данни:

 EXEC sys.sp_configure 'advanced', 1; ПРЕКОНФИГУРИРАНЕ; EXEC sys.sp_configure 'clr е активиран', 1; EXEC sys.sp_configure 'clr strict security', 0; ПРЕКОНФИГУРИРАНЕ; EXEC sys.sp_configure 'advanced', 0; ПРЕКОНФИГУРИРАНЕ; ПРОМЕНИ БАЗА ДАННИ testdb ВКЛЮЧИ НАДЕРЖЕН; ИЗПОЛЗВАЙТЕ testdb; DROP PROC, АКО СЪЩЕСТВУВА dbo.GetClosestMatches; ИЗПУСКАНЕ НА СЪБОРКАТА, АКО СЪЩЕСТВУВА ClosestMatch; СЪЗДАЙТЕ АСЕМБЛИРАНЕ ClosestMatch ОТ 'C:\ClosestMatch\ClosestMatch\bin\Debug\ClosestMatch.dll' С PERMISSION_SET =EXTERNAL_ACCESS; СЪЗДАВАЙТЕ ПРОЦЕДУРА dbo.GetClosestMatches КАТО ВЪНШНО ИМЕ ClosestMatch.ClosestMatch.GetClosestMatches;

Кодът активира CLR в екземпляра, деактивира опцията за строга сигурност на CLR, задава опцията TRUSTWORTHY на базата данни на ON, създава сборката и създава процедурата GetClosestMatches.

Използвайте следния код, за да тествате съхранената процедура:

 EXEC dbo.GetClosestMatches;

Решението на CLR отне 8 секунди за завършване на моята система с оптимално индексиране и 9 секунди без. Това е доста впечатляващо подобрение на производителността в сравнение с всички други решения – както релационни, така и итеративни.

Заключение

Итеративните решения обикновено не се гледат с недоволство в SQL общността, тъй като не следват релационния модел. Реалността обаче е, че понякога не сте в състояние да създадете добри релационни решения и производителността е приоритет. Използвайки итеративен подход, вие не сте ограничени до алгоритмите, до които оптимизаторът на SQL Server има достъп, а по-скоро можете да приложите всеки алгоритъм, който ви харесва. Както е показано в тази статия, с помощта на алгоритъм, подобен на сливане, вие успяхте да постигнете задачата с едно подредено преминаване срещу всеки от входовете. Използвайки T-SQL курсори и дискова променлива на таблица, вие получавате разумна производителност и мащабиране. Успяхте да подобрите производителността с около 30 процента, като преминете към оптимизирана за памет променлива на таблицата и значително повече, като използвате SQL CLR.


  1. Database
  2.   
  3. Mysql
  4.   
  5. Oracle
  6.   
  7. Sqlserver
  8.   
  9. PostgreSQL
  10.   
  11. Access
  12.   
  13. SQLite
  14.   
  15. MariaDB
  1. Проблемът за Хелоуин – част 1

  2. Хеш присъединява към нулеви колони

  3. Колко бърз е ODBC? „Заредено“ сравнение.

  4. SQL АКТУАЛИЗАЦИЯ

  5. Избягвайте самозаблудата на HA/DR разтвор