Comment puis-je convertir les 100 premiers millions d'entiers positifs en chaînes?

13

C'est un peu une diversion du vrai problème. Si le contexte est utile, la génération de ces données pourrait être utile pour tester les performances des méthodes de traitement des chaînes, pour générer des chaînes qui doivent être appliquées à un curseur ou pour générer des remplacements de noms uniques et anonymes pour les données sensibles. Je suis simplement intéressé par des moyens efficaces de générer les données dans les serveurs SQL, veuillez ne pas demander pourquoi j'ai besoin de générer ces données.

Je vais essayer de commencer par une définition quelque peu formelle. Une chaîne est incluse dans la série si elle se compose uniquement de lettres majuscules de A à Z. Le premier terme de la série est "A". La série se compose de toutes les chaînes valides triées par longueur en premier et par ordre alphabétique typique en second. Si les chaînes étaient dans un tableau dans une colonne appeléeSTRING_COL , l'ordre pourrait être défini dans T-SQL comme ORDER BY LEN(STRING_COL) ASC, STRING_COL ASC.

Pour donner une définition moins formelle, jetez un œil aux en-têtes de colonne alphabétiques dans Excel. La série est le même modèle. Considérez comment vous pouvez convertir un entier en nombre de base 26:

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

L'analogie n'est pas tout à fait parfaite car "A" se comporte différemment de 0 en base dix. Voici un tableau des valeurs sélectionnées qui, nous l'espérons, le rendra plus clair:

╔════════════╦════════╗
 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 
╚════════════╩════════╝

Le but est d'écrire une SELECTrequête qui renvoie les premières 100000000 chaînes dans l'ordre défini ci-dessus. J'ai fait mes tests en exécutant des requêtes dans SSMS avec le jeu de résultats rejeté au lieu de l'enregistrer dans une table:

ignorer le jeu de résultats

Idéalement, la requête sera raisonnablement efficace. Ici, je définis efficace comme temps cpu pour une requête série et temps écoulé pour une requête parallèle. Vous pouvez utiliser toutes les astuces non documentées que vous aimez. S'appuyer sur un comportement indéfini ou non garanti est également acceptable, mais il serait apprécié que vous l'appeliez dans votre réponse.

Quelles sont certaines méthodes pour générer efficacement l'ensemble de données décrit ci-dessus? Martin Smith a souligné qu'une procédure stockée CLR n'est probablement pas une bonne approche en raison de la surcharge de traitement de tant de lignes.

Joe Obbish
la source

Réponses:

7

Votre solution fonctionne pendant 35 secondes sur mon ordinateur portable. Le code suivant prend 26 secondes (y compris la création et le remplissage des tables temporaires):

Tables temporaires

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;

L'idée est de préremplir des combinaisons ordonnées de jusqu'à quatre caractères.

Code principal

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);

Il s'agit d'une simple union préservant l'ordre * des quatre tables précalculées, avec des chaînes de 5 et 6 caractères dérivées selon les besoins. La séparation du préfixe du suffixe évite le tri.

Plan d'exécution

100 millions de lignes


* Il n'y a rien dans le SQL ci-dessus qui spécifie une union préservant l'ordre directement une . L'optimiseur choisit des opérateurs physiques dont les propriétés correspondent à la spécification de requête SQL, y compris l'ordre de niveau supérieur par. Ici, il choisit la concaténation implémentée par l'opérateur physique de fusion fusion pour éviter le tri.

La garantie est que le plan d'exécution délivre la requête sémantique et l'ordre de niveau supérieur par spécification. Sachant que la fusion jointure concat préserve l'ordre permet au rédacteur de requêtes d'anticiper un plan d'exécution, mais l'optimiseur ne livrera que si l'attente est valide.

Paul White 9
la source
6

Je posterai une réponse pour commencer. Ma première pensée a été qu'il devrait être possible de profiter de la nature préservant l'ordre d'une jointure en boucle imbriquée avec quelques tables auxiliaires qui ont une ligne pour chaque lettre. La partie délicate allait boucler de manière à ce que les résultats soient classés par longueur tout en évitant les doublons. Par exemple, lors de l'adhésion croisée à un CTE qui comprend les 26 majuscules avec '', vous pouvez finir par générer 'A' + '' + 'A'et'' + 'A' + 'A' qui est bien sûr la même chaîne.

La première décision a été de savoir où stocker les données d'assistance. J'ai essayé d'utiliser une table temporaire mais cela a eu un impact étonnamment négatif sur les performances, même si les données tiennent sur une seule page. Le tableau temporaire contient les données ci-dessous:

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

Par rapport à l'utilisation d'un CTE, la requête a pris 3 fois plus de temps avec une table en cluster et 4 fois plus avec un tas. Je ne crois pas que le problème soit que les données soient sur disque. Il doit être lu en mémoire sur une seule page et traité en mémoire pour l'ensemble du plan. Peut-être que SQL Server peut travailler avec les données d'un opérateur Constant Scan plus efficacement qu'avec des données stockées dans des pages de magasin de lignes typiques.

Fait intéressant, SQL Server choisit de placer les résultats ordonnés d'une table tempdb d'une seule page avec les données ordonnées dans une bobine de table:

mauvais spoool

SQL Server place souvent les résultats de la table interne d'une jointure croisée dans une bobine de table, même si cela semble absurde de le faire. Je pense que l'optimiseur a besoin d'un peu de travail dans ce domaine. J'ai exécuté la requête avec leNO_PERFORMANCE_SPOOL pour éviter la perte de performances.

Un problème avec l'utilisation d'un CTE pour stocker les données d'assistance est que les données ne sont pas garanties d'être ordonnées. Je ne vois pas pourquoi l'optimiseur choisirait de ne pas le commander et dans tous mes tests, les données ont été traitées dans l'ordre où j'ai écrit le CTE:

ordre de balayage constant

Cependant, mieux vaut ne pas prendre de risques, surtout s'il existe un moyen de le faire sans une surcharge de performances importante. Il est possible de classer les données dans une table dérivée en ajoutant un TOPopérateur superflu . Par exemple:

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

Cet ajout à la requête devrait garantir que les résultats seront renvoyés dans le bon ordre. Je m'attendais à ce que tous les types aient un grand impact négatif sur les performances. L'optimiseur de requêtes s'y attendait également en fonction des coûts estimés:

sortes chères

De manière très surprenante, je n'ai pu observer aucune différence statistiquement significative dans le temps ou le temps d'exécution du processeur avec ou sans classement explicite. Si quoi que ce soit, la requête semble s'exécuter plus rapidement avec leORDER BY ! Je n'ai aucune explication à ce comportement.

La partie délicate du problème était de savoir comment insérer des caractères vides aux bons endroits. Comme mentionné précédemment, un simple CROSS JOINentraînerait la duplication des données. Nous savons que la chaîne 100000000th aura une longueur de six caractères car:

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

mais

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

Par conséquent, nous devons seulement adhérer à la lettre CTE six fois. Supposons que nous nous joignions au CTE six fois, prenions une lettre de chaque CTE et les concaténions tous ensemble. Supposons que la lettre la plus à gauche ne soit pas vide. Si l'une des lettres suivantes est vide, cela signifie que la chaîne comporte moins de six caractères, il s'agit donc d'un doublon. Par conséquent, nous pouvons éviter les doublons en trouvant le premier caractère non vide et en exigeant que tous les caractères après lui ne soient pas non plus vides. J'ai choisi de suivre cela en attribuant une FLAGcolonne à l'un des CTE et en ajoutant une vérification à la WHEREclause. Cela devrait être plus clair après avoir examiné la requête. La dernière requête est la suivante:

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);

Les CTE sont tels que décrits ci-dessus. ALL_CHARest joint à cinq fois car il comprend une ligne pour un caractère vide. Le caractère final de la chaîne ne doit jamais être vide si on définit un CTE séparé pour elle, FIRST_CHAR. La colonne d'indicateur supplémentaire dans ALL_CHARest utilisée pour éviter les doublons comme décrit ci-dessus. Il existe peut-être un moyen plus efficace d'effectuer cette vérification, mais il existe certainement des moyens plus inefficaces de le faire. Une tentative de ma part avec LEN()etPOWER() fait exécuter la requête six fois plus lentement que la version actuelle.

Les astuces MAXDOP 1et FORCE ORDERsont essentielles pour garantir que l'ordre est conservé dans la requête. Un plan estimé annoté peut être utile pour voir pourquoi les jointures sont dans leur ordre actuel:

estimé annoté

Les plans de requête sont souvent lus de droite à gauche, mais les demandes de ligne se produisent de gauche à droite. Idéalement, SQL Server demandera exactement 100 millions de lignes à l' d1opérateur d'analyse constante. Lorsque vous vous déplacez de gauche à droite, je m'attends à ce que moins de lignes soient demandées à chaque opérateur. Nous pouvons voir cela dans le plan d'exécution réel . En outre, ci-dessous est une capture d'écran de SQL Sentry Plan Explorer:

explorateur

Nous avons obtenu exactement 100 millions de lignes de d1, ce qui est une bonne chose. Notez que le rapport des lignes entre d2 et d3 est presque exactement 27: 1 (165336 * 27 = 4464072), ce qui est logique si vous pensez au fonctionnement de la jointure croisée. Le rapport des rangées entre d1 et d2 est de 22,4, ce qui représente du travail gaspillé. Je crois que les lignes supplémentaires proviennent de doublons (en raison des caractères vides au milieu des chaînes) qui ne dépassent pas l'opérateur de jointure de boucle imbriquée qui effectue le filtrage.

L' LOOP JOINastuce est techniquement inutile car a CROSS JOINne peut être implémenté qu'en tant que jointure en boucle dans SQL Server. leNO_PERFORMANCE_SPOOL est d'empêcher la mise en file d'attente de table inutile. L'omission de l'indicateur de bobine a rendu la requête 3 fois plus longue sur ma machine.

La requête finale a un temps processeur d'environ 17 secondes et un temps total écoulé de 18 secondes. C'était lors de l'exécution de la requête via SSMS et de la suppression du jeu de résultats. Je suis très intéressé à voir d'autres méthodes de génération des données.

Joe Obbish
la source
2

J'ai une solution optimisée pour obtenir le code de chaîne pour tout nombre spécifique jusqu'à 217 180, 147 158 (8 caractères). Mais je ne peux pas battre votre temps:

Sur ma machine, avec SQL Server 2014, votre requête prend 18 secondes, tandis que la mienne prend 3m 46s. Les deux requêtes utilisent l'indicateur de trace non documenté 8690 car 2014 ne prend pas en charge leNO_PERFORMANCE_SPOOL indice.

Voici le code:

/* 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);

L'astuce consiste à précalculer le point de départ des différentes permutations:

  1. Lorsque vous devez générer un seul caractère, vous disposez de 26 ^ 1 permutations qui commencent à 26 ^ 0.
  2. Lorsque vous devez sortir 2 caractères, vous avez 26 ^ 2 permutations qui commencent à 26 ^ 0 + 26 ^ 1
  3. Lorsque vous devez produire 3 caractères, vous avez 26 ^ 3 permutations qui commencent à 26 ^ 0 + 26 ^ 1 + 26 ^ 2
  4. répéter pour n caractères

L'autre astuce utilisée est d'utiliser simplement sum pour obtenir la bonne valeur au lieu d'essayer de concaténer. Pour ce faire, je décale simplement les chiffres de la base 26 à la base 256 et ajoute la valeur ascii de «A» pour chaque chiffre. Nous obtenons donc la représentation binaire de la chaîne que nous recherchons. Après cela, certaines manipulations de chaînes terminent le processus.

Adán Bucio
la source
-1

ok, voici mon dernier script.

Pas de boucle, pas de récursivité.

Cela ne fonctionne que pour 6 caractères

Le plus gros inconvénient est qu'il faut environ 22 minutes pour 1,00,00,000

Cette fois, mon script est très court.

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
la source
Il semble que la table dérivée soit convertie en un seul scalaire de calcul de plus de 400 000 caractères de code. Je suppose qu'il y a beaucoup de frais généraux dans ce calcul. Vous voudrez peut-être essayer quelque chose de semblable au suivant: dbfiddle.uk/… N'hésitez pas à intégrer des composants de cela dans votre réponse.
Joe Obbish