Jak przekonwertować pierwsze 100 milionów dodatnich liczb całkowitych na ciągi?

13

Jest to trochę odwrócenie uwagi od prawdziwego problemu. Jeśli pomaga kontekst, generowanie tych danych może być przydatne do testowania wydajności metod przetwarzania ciągów, do generowania ciągów, które wymagają pewnej operacji zastosowanej wobec nich w obrębie kursora, lub do generowania unikalnych, anonimowych zamienników nazw dla wrażliwych danych. Interesują mnie tylko wydajne sposoby generowania danych w serwerach SQL, proszę nie pytaj, dlaczego muszę generować te dane.

Spróbuję zacząć od nieco formalnej definicji. Ciąg jest zawarty w serii, jeśli składa się tylko z wielkich liter od A do Z. Pierwszy termin serii to „A”. Seria składa się ze wszystkich prawidłowych ciągów posortowanych według długości w pierwszej kolejności i typowej kolejności alfabetycznej w kolejności drugiej. Gdyby ciągi znaków znajdowały się w tabeli w kolumnie o nazwie STRING_COL, kolejność może być zdefiniowana w T-SQL jako ORDER BY LEN(STRING_COL) ASC, STRING_COL ASC.

Aby podać mniej formalną definicję, spójrz na nagłówki kolumn alfabetycznych w programie Excel. Seria ma ten sam wzór. Zastanów się, jak przekonwertować liczbę całkowitą na liczbę podstawową 26:

1 -> A, 2 -> B, 3 -> C, ..., 25 -> Y, 26 -> Z, 27 -> AA, 28 -> AB, ...

Analogia nie jest całkiem idealna, ponieważ „A” zachowuje się inaczej niż 0 w bazie dziesiątej. Poniżej znajduje się tabela wybranych wartości, które, mam nadzieję, wyjaśnią:

╔════════════╦════════╗
 ROW_NUMBER  STRING 
╠════════════╬════════╣
          1  A      
          2  B      
         25  Y      
         26  Z      
         27  AA     
         28  AB     
         51  AY     
         52  AZ     
         53  BA     
         54  BB     
      18278  ZZZ    
      18279  AAAA   
     475253  ZZZY   
     475254  ZZZZ   
     475255  AAAAA  
  100000000  HJUNYV 
╚════════════╩════════╝

Celem jest napisanie SELECTzapytania, które zwraca pierwsze 100000000 ciągów w kolejności określonej powyżej. Testy wykonałem, uruchamiając zapytania w SSMS z odrzuconym zestawem wyników, zamiast zapisywać go w tabeli:

odrzuć zestaw wyników

Idealnie byłoby, gdyby zapytanie było dość wydajne. Tutaj definiuję efektywny czas procesora dla zapytania szeregowego i czas, który upłynął dla zapytania równoległego. Możesz użyć dowolnych nieudokumentowanych sztuczek, które ci się podobają. Poleganie na niezdefiniowanym lub niegwarantowanym zachowaniu jest również w porządku, ale byłoby to mile widziane, gdybyś przywołał to w swojej odpowiedzi.

Jakie są metody efektywnego generowania zestawu danych opisane powyżej? Martin Smith wskazał, że procedura przechowywana CLR prawdopodobnie nie jest dobrym podejściem ze względu na obciążenie związane z przetwarzaniem tak wielu wierszy.

Joe Obbish
źródło

Odpowiedzi:

7

Twoje rozwiązanie działa przez 35 sekund na moim laptopie. Poniższy kod zajmuje 26 sekund (w tym tworzenie i wypełnianie tabel tymczasowych):

Tymczasowe stoły

DROP TABLE IF EXISTS #T1, #T2, #T3, #T4;

CREATE TABLE #T1 (string varchar(6) NOT NULL PRIMARY KEY);
CREATE TABLE #T2 (string varchar(6) NOT NULL PRIMARY KEY);
CREATE TABLE #T3 (string varchar(6) NOT NULL PRIMARY KEY);
CREATE TABLE #T4 (string varchar(6) NOT NULL PRIMARY KEY);

INSERT #T1 (string)
VALUES
    ('A'), ('B'), ('C'), ('D'), ('E'), ('F'), ('G'),
    ('H'), ('I'), ('J'), ('K'), ('L'), ('M'), ('N'),
    ('O'), ('P'), ('Q'), ('R'), ('S'), ('T'), ('U'),
    ('V'), ('W'), ('X'), ('Y'), ('Z');

INSERT #T2 (string)
SELECT T1a.string + T1b.string
FROM #T1 AS T1a, #T1 AS T1b;

INSERT #T3 (string)
SELECT #T2.string + #T1.string
FROM #T2, #T1;

INSERT #T4 (string)
SELECT #T3.string + #T1.string
FROM #T3, #T1;

Chodzi o to, aby wstępnie wypełnić uporządkowane kombinacje maksymalnie czterech znaków.

Kod główny

SELECT TOP (100000000)
    UA.string + UA.string2
FROM
(
    SELECT U.Size, U.string, string2 = '' FROM 
    (
        SELECT Size = 1, string FROM #T1
        UNION ALL
        SELECT Size = 2, string FROM #T2
        UNION ALL
        SELECT Size = 3, string FROM #T3
        UNION ALL
        SELECT Size = 4, string FROM #T4
    ) AS U
    UNION ALL
    SELECT Size = 5, #T1.string, string2 = #T4.string
    FROM #T1, #T4
    UNION ALL
    SELECT Size = 6, #T2.string, #T4.string
    FROM #T2, #T4
) AS UA
ORDER BY 
    UA.Size, 
    UA.string, 
    UA.string2
OPTION (NO_PERFORMANCE_SPOOL, MAXDOP 1);

Jest to prosty układ zachowujący porządek * czterech wstępnie obliczonych tabel, z 5-znakowymi i 6-znakowymi łańcuchami wyprowadzanymi w razie potrzeby. Oddzielenie prefiksu od sufiksu pozwala uniknąć sortowania.

Plan wykonania

100 milionów wierszy


* W powyższym kodzie SQL nie ma nic, co bezpośrednio określa związek zachowujący porządek . Optymalizator wybiera fizyczne operatory o właściwościach zgodnych ze specyfikacją zapytania SQL, w tym kolejności najwyższego poziomu według. Tutaj wybiera konkatenację zaimplementowaną przez fizyczny operator łączenia scalania, aby uniknąć sortowania.

Gwarancją jest to, że plan wykonania dostarcza zapytanie semantyczne i zamówienie najwyższego poziomu według specyfikacji. Wiedza o tym, że scalanie łączenia konkat zachowuje porządek, pozwala twórcy zapytań przewidzieć plan wykonania, ale optymalizator dostarczy tylko, jeśli oczekiwanie jest poprawne.

Paul White 9
źródło
6

Wyślę odpowiedź, aby zacząć. Moja pierwsza myśl była taka, że ​​powinna istnieć możliwość zachowania porządku zagnieżdżonego łączenia pętli wraz z kilkoma tabelami pomocniczymi, które mają jeden wiersz dla każdej litery. Trudna część miała być zapętlona w taki sposób, aby wyniki były uporządkowane według długości, a także aby uniknąć duplikatów. Na przykład, łącząc krzyżowo CTE, które zawiera wszystkie 26 wielkich liter wraz z „”, możesz wygenerować 'A' + '' + 'A'i '' + 'A' + 'A'który jest oczywiście tym samym ciągiem.

Pierwszą decyzją było miejsce przechowywania danych pomocnika. Próbowałem użyć tabeli tymczasowej, ale miało to zaskakująco negatywny wpływ na wydajność, mimo że dane mieszczą się na jednej stronie. Tabela temp zawierała następujące dane:

SELECT 'A'
UNION ALL SELECT 'B'
...
UNION ALL SELECT 'Y'
UNION ALL SELECT 'Z'

W porównaniu z użyciem CTE, zapytanie zajęło 3 razy więcej w przypadku tabeli klastrowej i 4 razy dłużej w przypadku sterty. Nie sądzę, że problemem jest to, że dane są na dysku. Należy go wczytać do pamięci jako pojedynczą stronę i przetworzyć w pamięci dla całego planu. Być może SQL Server może pracować z danymi od operatora Constant Scan wydajniej niż z danymi przechowywanymi na typowych stronach magazynu.

Co ciekawe, SQL Server wybiera umieszczenie uporządkowanych wyników z jednostronicowej tabeli tempdb z uporządkowanymi danymi w buforze tabeli:

zła łyżka

SQL Server często umieszcza wyniki dla wewnętrznej tabeli krzyżowego łączenia w buforze tabeli, nawet jeśli wydaje się to nonsensowne. Myślę, że optymalizator wymaga trochę pracy w tym obszarze. Uruchomiłem kwerendę, NO_PERFORMANCE_SPOOLaby uniknąć spadku wydajności.

Jednym z problemów z użyciem CTE do przechowywania danych pomocnika jest to, że nie ma gwarancji, że dane zostaną zamówione. Nie mogę wymyślić, dlaczego optymalizator postanowiłby tego nie zamawiać, a we wszystkich moich testach dane były przetwarzane w kolejności, w której napisałem CTE:

stała kolejność skanowania

Najlepiej jednak nie ryzykować, zwłaszcza jeśli istnieje sposób, aby to zrobić bez dużego obciążenia wydajności. Możliwe jest uporządkowanie danych w tabeli pochodnej poprzez dodanie zbędnego TOPoperatora. Na przykład:

(SELECT TOP (26) CHR FROM FIRST_CHAR ORDER BY CHR)

To dodanie do zapytania powinno gwarantować, że wyniki zostaną zwrócone we właściwej kolejności. Spodziewałem się, że wszystkie rodzaje będą miały duży negatywny wpływ na wydajność. Optymalizator zapytań również tego oczekiwał na podstawie szacunkowych kosztów:

drogie rodzaje

Bardzo zaskakujące, że nie zauważyłem żadnej statystycznie istotnej różnicy w czasie procesora lub czasie działania z wyraźnym zamówieniem lub bez niego. Jeśli cokolwiek, zapytanie wydawało się działać szybciej z ORDER BY! Nie mam wytłumaczenia tego zachowania.

Problem polegał na tym, aby dowiedzieć się, jak wstawić puste znaki we właściwe miejsca. Jak wspomniano wcześniej, prosty CROSS JOINspowodowałby zduplikowanie danych. Wiemy, że 100000000-ty ciąg będzie miał długość sześciu znaków, ponieważ:

26 + 26 ^ 2 + 26 ^ 3 + 26 ^ 4 + 26 ^ 5 = 914654 <100000000

ale

26 + 26 ^ 2 + 26 ^ 3 + 26 ^ 4 + 26 ^ 5 + 26 ^ 6 = 321272406> 100000000

Dlatego musimy dołączyć się do litery CTE tylko sześć razy. Załóżmy, że dołączamy do CTE sześć razy, bierzemy jedną literę z każdego CTE i łączymy je wszystkie razem. Załóżmy, że pierwsza lewa litera nie jest pusta. Jeśli którakolwiek z kolejnych liter jest pusta, oznacza to, że łańcuch ma mniej niż sześć znaków, więc jest duplikatem. Dlatego możemy zapobiec duplikatom, znajdując pierwszy niepusty znak i wymagając, aby wszystkie znaki po nim również nie były puste. Wybrałem śledzenie tego, przypisując FLAGkolumnę do jednego z CTE i dodając zaznaczenie do WHEREklauzuli. Powinno to być bardziej zrozumiałe po spojrzeniu na zapytanie. Ostatnie zapytanie jest następujące:

WITH FIRST_CHAR (CHR) AS
(
    SELECT 'A'
    UNION ALL SELECT 'B'
    UNION ALL SELECT 'C'
    UNION ALL SELECT 'D'
    UNION ALL SELECT 'E'
    UNION ALL SELECT 'F'
    UNION ALL SELECT 'G'
    UNION ALL SELECT 'H'
    UNION ALL SELECT 'I'
    UNION ALL SELECT 'J'
    UNION ALL SELECT 'K'
    UNION ALL SELECT 'L'
    UNION ALL SELECT 'M'
    UNION ALL SELECT 'N'
    UNION ALL SELECT 'O'
    UNION ALL SELECT 'P'
    UNION ALL SELECT 'Q'
    UNION ALL SELECT 'R'
    UNION ALL SELECT 'S'
    UNION ALL SELECT 'T'
    UNION ALL SELECT 'U'
    UNION ALL SELECT 'V'
    UNION ALL SELECT 'W'
    UNION ALL SELECT 'X'
    UNION ALL SELECT 'Y'
    UNION ALL SELECT 'Z'
)
, ALL_CHAR (CHR, FLAG) AS
(
    SELECT '', 0 CHR
    UNION ALL SELECT 'A', 1
    UNION ALL SELECT 'B', 1
    UNION ALL SELECT 'C', 1
    UNION ALL SELECT 'D', 1
    UNION ALL SELECT 'E', 1
    UNION ALL SELECT 'F', 1
    UNION ALL SELECT 'G', 1
    UNION ALL SELECT 'H', 1
    UNION ALL SELECT 'I', 1
    UNION ALL SELECT 'J', 1
    UNION ALL SELECT 'K', 1
    UNION ALL SELECT 'L', 1
    UNION ALL SELECT 'M', 1
    UNION ALL SELECT 'N', 1
    UNION ALL SELECT 'O', 1
    UNION ALL SELECT 'P', 1
    UNION ALL SELECT 'Q', 1
    UNION ALL SELECT 'R', 1
    UNION ALL SELECT 'S', 1
    UNION ALL SELECT 'T', 1
    UNION ALL SELECT 'U', 1
    UNION ALL SELECT 'V', 1
    UNION ALL SELECT 'W', 1
    UNION ALL SELECT 'X', 1
    UNION ALL SELECT 'Y', 1
    UNION ALL SELECT 'Z', 1
)
SELECT TOP (100000000)
d6.CHR + d5.CHR + d4.CHR + d3.CHR + d2.CHR + d1.CHR
FROM (SELECT TOP (27) FLAG, CHR FROM ALL_CHAR ORDER BY CHR) d6
CROSS JOIN (SELECT TOP (27) FLAG, CHR FROM ALL_CHAR ORDER BY CHR) d5
CROSS JOIN (SELECT TOP (27) FLAG, CHR FROM ALL_CHAR ORDER BY CHR) d4
CROSS JOIN (SELECT TOP (27) FLAG, CHR FROM ALL_CHAR ORDER BY CHR) d3
CROSS JOIN (SELECT TOP (27) FLAG, CHR FROM ALL_CHAR ORDER BY CHR) d2
CROSS JOIN (SELECT TOP (26) CHR FROM FIRST_CHAR ORDER BY CHR) d1
WHERE (d2.FLAG + d3.FLAG + d4.FLAG + d5.FLAG + d6.FLAG) =
    CASE 
    WHEN d6.FLAG = 1 THEN 5
    WHEN d5.FLAG = 1 THEN 4
    WHEN d4.FLAG = 1 THEN 3
    WHEN d3.FLAG = 1 THEN 2
    WHEN d2.FLAG = 1 THEN 1
    ELSE 0 END
OPTION (MAXDOP 1, FORCE ORDER, LOOP JOIN, NO_PERFORMANCE_SPOOL);

Wartości CTE są takie, jak opisano powyżej. ALL_CHARjest łączony pięciokrotnie, ponieważ zawiera wiersz pustego znaku. Ostateczna postać w ciągu nigdy nie powinien być pusty, dzięki czemu osobny CTE jest zdefiniowany dla niego FIRST_CHAR. Dodatkowa kolumna flagi w ALL_CHARsłuży do zapobiegania duplikatom, jak opisano powyżej. Może istnieć bardziej skuteczny sposób na wykonanie tej kontroli, ale są zdecydowanie bardziej nieefektywne metody. Jedna próba mnie LEN()i POWER()wykonany przebieg zapytań sześć razy wolniej niż w obecnej wersji.

MAXDOP 1I FORCE ORDERwskazówki są niezbędne, aby upewnić się, że kolejność jest zachowana w zapytaniu. Plan szacunkowy z adnotacjami może być pomocny w ustaleniu, dlaczego połączenia są w ich bieżącej kolejności:

opatrzone adnotacją

Plany zapytań są często odczytywane od prawej do lewej, ale żądania wierszy zdarzają się od lewej do prawej. Idealnie byłoby, gdyby SQL Server zażądał dokładnie 100 milionów wierszy od d1stałego operatora skanowania. Podczas przechodzenia od lewej do prawej oczekuję, że od każdego operatora będzie wymagane mniej wierszy. Widzimy to w rzeczywistym planie wykonania . Ponadto poniżej znajduje się zrzut ekranu z eksploratora SQL Sentry Plan Explorer:

poszukiwacz

Mamy dokładnie 100 milionów wierszy z d1, co jest dobrą rzeczą. Zauważ, że stosunek rzędów między d2 i d3 wynosi prawie dokładnie 27: 1 (165336 * 27 = 4464072), co ma sens, jeśli pomyślisz o tym, jak będzie działać połączenie krzyżowe. Stosunek rzędów między d1 i d2 wynosi 22,4, co oznacza trochę zmarnowanej pracy. Wierzę, że dodatkowe wiersze pochodzą z duplikatów (z powodu pustych znaków na środku ciągów), które nie przechodzą obok zagnieżdżonego operatora łączenia pętli, który wykonuje filtrowanie.

LOOP JOINWskazówką jest to technicznie konieczne, ponieważ CROSS JOINmogą być realizowane tylko jako pętla dołączyć w SQL Server. Ma NO_PERFORMANCE_SPOOLto na celu zapobieganie zbędnemu buforowaniu tabeli. Pominięcie wskazówki dotyczącej szpuli spowodowało, że zapytanie zajęło 3 razy więcej czasu na mojej maszynie.

Ostatnie zapytanie ma czas procesora około 17 sekund i całkowity czas, który upłynął 18 sekund. Tak było podczas uruchamiania zapytania przez SSMS i odrzucania zestawu wyników. Jestem bardzo zainteresowany widzeniem innych metod generowania danych.

Joe Obbish
źródło
2

Mam rozwiązanie zoptymalizowane pod kątem uzyskania kodu ciągu dla dowolnej konkretnej liczby do 217,180,147,158 (8 znaków). Ale nie mogę pobić twojego czasu:

Na moim komputerze z SQL Server 2014 twoje zapytanie zajmuje 18 sekund, podczas gdy moje zajmuje 3m 46s. Oba zapytania używają nieudokumentowanej flagi śledzenia 8690, ponieważ 2014 nie obsługuje NO_PERFORMANCE_SPOOLpodpowiedzi.

Oto kod:

/* precompute offsets and powers to simplify final query */
CREATE TABLE #ExponentsLookup (
    offset          BIGINT NOT NULL,
    offset_end      BIGINT NOT NULL,
    position        INTEGER NOT NULL,
    divisor         BIGINT NOT NULL,
    shifts          BIGINT NOT NULL,
    chars           INTEGER NOT NULL,
    PRIMARY KEY(offset, offset_end, position)
);

WITH base_26_multiples AS ( 
    SELECT  number  AS exponent,
            CAST(POWER(26.0, number) AS BIGINT) AS multiple
    FROM    master.dbo.spt_values
    WHERE   [type] = 'P'
            AND number < 8
),
num_offsets AS (
    SELECT  *,
            -- The maximum posible value is 217180147159 - 1
            LEAD(offset, 1, 217180147159) OVER(
                ORDER BY exponent
            ) AS offset_end
    FROM    (
                SELECT  exponent,
                        SUM(multiple) OVER(
                            ORDER BY exponent
                        ) AS offset
                FROM    base_26_multiples
            ) x
)
INSERT INTO #ExponentsLookup(offset, offset_end, position, divisor, shifts, chars)
SELECT  ofst.offset, ofst.offset_end,
        dgt.number AS position,
        CAST(POWER(26.0, dgt.number) AS BIGINT)     AS divisor,
        CAST(POWER(256.0, dgt.number) AS BIGINT)    AS shifts,
        ofst.exponent + 1                           AS chars
FROM    num_offsets ofst
        LEFT JOIN master.dbo.spt_values dgt --> as many rows as resulting chars in string
            ON [type] = 'P'
            AND dgt.number <= ofst.exponent;

/*  Test the cases in table example */
SELECT  /*  1.- Get the base 26 digit and then shift it to align it to 8 bit boundaries
            2.- Sum the resulting values
            3.- Bias the value with a reference that represent the string 'AAAAAAAA'
            4.- Take the required chars */
        ref.[row_number],
        REVERSE(SUBSTRING(REVERSE(CAST(SUM((((ref.[row_number] - ofst.offset) / ofst.divisor) % 26) * ofst.shifts) +
            CAST(CAST('AAAAAAAA' AS BINARY(8)) AS BIGINT) AS BINARY(8))),
            1, MAX(ofst.chars))) AS string
FROM    (
            VALUES(1),(2),(25),(26),(27),(28),(51),(52),(53),(54),
            (18278),(18279),(475253),(475254),(475255),
            (100000000), (CAST(217180147158 AS BIGINT))
        ) ref([row_number])
        LEFT JOIN #ExponentsLookup ofst
            ON ofst.offset <= ref.[row_number]
            AND ofst.offset_end > ref.[row_number]
GROUP BY
        ref.[row_number]
ORDER BY
        ref.[row_number];

/*  Test with huge set  */
WITH numbers AS (
    SELECT  TOP(100000000)
            ROW_NUMBER() OVER(
                ORDER BY x1.number
            ) AS [row_number]
    FROM    master.dbo.spt_values x1
            CROSS JOIN (SELECT number FROM master.dbo.spt_values WHERE [type] = 'P' AND number < 676) x2
            CROSS JOIN (SELECT number FROM master.dbo.spt_values WHERE [type] = 'P' AND number < 676) x3
    WHERE   x1.number < 219
)
SELECT  /*  1.- Get the base 26 digit and then shift it to align it to 8 bit boundaries
            2.- Sum the resulting values
            3.- Bias the value with a reference that represent the string 'AAAAAAAA'
            4.- Take the required chars */
        ref.[row_number],
        REVERSE(SUBSTRING(REVERSE(CAST(SUM((((ref.[row_number] - ofst.offset) / ofst.divisor) % 26) * ofst.shifts) +
            CAST(CAST('AAAAAAAA' AS BINARY(8)) AS BIGINT) AS BINARY(8))),
            1, MAX(ofst.chars))) AS string
FROM    numbers ref
        LEFT JOIN #ExponentsLookup ofst
            ON ofst.offset <= ref.[row_number]
            AND ofst.offset_end > ref.[row_number]
GROUP BY
        ref.[row_number]
ORDER BY
        ref.[row_number]
OPTION (QUERYTRACEON 8690);

Sztuczka polega na tym, aby wstępnie obliczyć, gdzie zaczynają się różne kombinacje:

  1. Kiedy musisz wypisać pojedynczy znak, masz 26 ^ 1 permutacji, które zaczynają się od 26 ^ 0.
  2. Kiedy musisz wypisać 2 znaki, masz 26 ^ 2 permutacji, które zaczynają się od 26 ^ 0 + 26 ^ 1
  3. Kiedy musisz wyprowadzić 3 znaki, masz 26 ^ 3 permutacji, które zaczynają się od 26 ^ 0 + 26 ^ 1 + 26 ^ 2
  4. powtórz dla n znaków

Innym stosowanym trikiem jest po prostu użycie sumy, aby uzyskać odpowiednią wartość zamiast próby konkatacji. Aby to osiągnąć, po prostu przesunęłem cyfry od bazy 26 do bazy 256 i dodałem wartość ascii „A” dla każdej cyfry. Otrzymujemy więc binarną reprezentację szukanego ciągu. Następnie niektóre manipulacje ciągami kończą proces.

Adán Bucio
źródło
-1

ok, oto mój najnowszy skrypt.

Bez pętli, bez rekurencji.

Działa tylko dla 6 znaków

Największą wadą jest to, że 1,00,00 000 zajmuje około 22 minut

Tym razem mój skrypt jest bardzo krótki.

SET NoCount on

declare @z int=26
declare @start int=@z+1 
declare @MaxLimit int=10000000

SELECT TOP (@MaxLimit) IDENTITY(int,1,1) AS N
    INTO NumbersTest1
    FROM     master.dbo.spt_values x1   
   CROSS JOIN (SELECT number FROM master.dbo.spt_values WHERE [type] = 'P' AND number < 500) x2
            CROSS JOIN (SELECT number FROM master.dbo.spt_values WHERE [type] = 'P' AND number < 500) x3
    WHERE   x1.number < 219
ALTER TABLE NumbersTest1 ADD CONSTRAINT PK_NumbersTest1 PRIMARY KEY CLUSTERED (N)


select N, strCol from NumbersTest1
cross apply
(
select 
case when IntCol6>0 then  char((IntCol6%@z)+64) else '' end 
+case when IntCol5=0 then 'Z' else isnull(char(IntCol5+64),'') end 
+case when IntCol4=0 then 'Z' else isnull(char(IntCol4+64),'') end 
+case when IntCol3=0 then 'Z' else isnull(char(IntCol3+64),'') end 
+case when IntCol2=0 then 'Z' else isnull(char(IntCol2+64),'') end 
+case when IntCol1=0 then 'Z' else isnull(char(IntCol1+64),'') end strCol
from
(
select  IntCol1,IntCol2,IntCol3,IntCol4
,case when IntCol5>0 then  IntCol5%@z else null end IntCol5

,case when IntCol5/@z>0 and  IntCol5%@z=0 then  IntCol5/@z-1 
when IntCol5/@z>0 then IntCol5/@z
else null end IntCol6
from
(
select IntCol1,IntCol2,IntCol3
,case when IntCol4>0 then  IntCol4%@z else null end IntCol4

,case when IntCol4/@z>0 and  IntCol4%@z=0 then  IntCol4/@z-1 
when IntCol4/@z>0 then IntCol4/@z
else null end IntCol5
from
(
select IntCol1,IntCol2
,case when IntCol3>0 then  IntCol3%@z else null end IntCol3
,case when IntCol3/@z>0 and  IntCol3%@z=0 then  IntCol3/@z-1 
when IntCol3/@z>0 then IntCol3/@z
else null end IntCol4

from
(
select IntCol1
,case when IntCol2>0 then  IntCol2%@z else null end IntCol2
,case when IntCol2/@z>0 and  IntCol2%@z=0 then  IntCol2/@z-1 
when IntCol2/@z>0 then IntCol2/@z
else null end IntCol3

from
(
select case when N>0 then N%@z else null end IntCol1
,case when N%@z=0 and  (N/@z)>1 then (N/@z)-1 else  (N/@z) end IntCol2 

)Lv2
)Lv3
)Lv4
)Lv5
)LV6

)ca

DROP TABLE NumbersTest1
KumarHarsh
źródło
Wygląda na to, że tabela pochodna jest konwertowana na pojedynczy skalar obliczeniowy zawierający ponad 400 000 znaków kodu. Podejrzewam, że w obliczeniach jest dużo narzutów. Możesz wypróbować coś podobnego do następującego: dbfiddle.uk/… Nie krępuj się zintegrować jego elementy z odpowiedzią.
Joe Obbish