Comment générer une plage de nombres entre deux nombres?

141

J'ai deux nombres comme entrée de l'utilisateur, comme par exemple 1000et 1050.

Comment générer les nombres entre ces deux nombres, à l'aide d'une requête SQL, en lignes séparées? Je veux ceci:

 1000
 1001
 1002
 1003
 .
 .
 1050
user3211705
la source

Réponses:

159

Sélectionnez des valeurs non persistantes avec le VALUESmot clé. Ensuite, utilisez JOINs pour générer beaucoup et beaucoup de combinaisons (peut être étendu pour créer des centaines de milliers de lignes et au-delà).

SELECT ones.n + 10*tens.n + 100*hundreds.n + 1000*thousands.n
FROM (VALUES(0),(1),(2),(3),(4),(5),(6),(7),(8),(9)) ones(n),
     (VALUES(0),(1),(2),(3),(4),(5),(6),(7),(8),(9)) tens(n),
     (VALUES(0),(1),(2),(3),(4),(5),(6),(7),(8),(9)) hundreds(n),
     (VALUES(0),(1),(2),(3),(4),(5),(6),(7),(8),(9)) thousands(n)
WHERE ones.n + 10*tens.n + 100*hundreds.n + 1000*thousands.n BETWEEN @userinput1 AND @userinput2
ORDER BY 1

Demo

Une alternative plus courte, qui n'est pas aussi simple à comprendre:

WITH x AS (SELECT n FROM (VALUES (0),(1),(2),(3),(4),(5),(6),(7),(8),(9)) v(n))
SELECT ones.n + 10*tens.n + 100*hundreds.n + 1000*thousands.n
FROM x ones,     x tens,      x hundreds,       x thousands
ORDER BY 1

Demo

slartidan
la source
13
C'est une solution incroyablement élégante
Aaron Hudon
9
Pouvez-vous expliquer la syntaxe? Qu'est-ce que le v (n)?
Rafi
2
@Rafi le v (n) et les centaines (n) etc. sont des noms / alias de table et de colonne
Twon-ha
106

une solution alternative est le CTE récursif:

DECLARE @startnum INT=1000
DECLARE @endnum INT=1050
;
WITH gen AS (
    SELECT @startnum AS num
    UNION ALL
    SELECT num+1 FROM gen WHERE num+1<=@endnum
)
SELECT * FROM gen
option (maxrecursion 10000)
Jayvee
la source
4
N'essayez pas d'utiliser l'option maxrecusion dans une définition de vue. Au lieu de cela, vous devez SELECT * FROM CTE_VIEW OPTION (MAXRECURSION 10000) - problématique, si votre application cliente veut consommer la vue telle quelle.
TvdH du
4
Il y a une maxrecursion maximale définie sur 32767 (dans SQL Server 2012).
BProv
4
Juste pour clarifier, si vous avez besoin d'une récursion de plus de 32767, elle peut être définie sur 0, ce qui signifie nomax,
Jayvee
2
Voici la démo pour cette réponse.
stomie le
7
J'ai comparé cette réponse avec les autres et le plan d'exécution montre que cette réponse ( a le moindre coût de requête et ) est la plus rapide.
stomie le
39
SELECT DISTINCT n = number 
FROM master..[spt_values] 
WHERE number BETWEEN @start AND @end

Demo

Notez que ce tableau a un maximum de 2048 car alors les nombres ont des lacunes.

Voici une approche légèrement meilleure utilisant une vue système (depuis SQL-Server 2005):

;WITH Nums AS
(
  SELECT n = ROW_NUMBER() OVER (ORDER BY [object_id]) 
  FROM sys.all_objects 

)
SELECT n FROM Nums 
WHERE n BETWEEN @start AND @end
ORDER BY n;

Demo

ou utilisez une table numérique personnalisée. Crédits à Aaron Bertrand, je propose de lire l'article en entier: Générer un ensemble ou une séquence sans boucles

Tim Schmelter
la source
2
@ user3211705: remarquez ma modification, ce tableau a un maximum de 2048. Je suggère de lire l'article en entier.
Tim Schmelter
3
Je pense que vous pourriez ajouter WHERE type = 'P'et éviterSELECT DISTINCT
Salman A
1
Votre premier lien "Démo" me dit sans cesseString index out of range: 33
slartidan
1
Vous avez raison. Mais cela semble être un problème avec SqlFiddle. Cela fonctionne-t-il dans votre base de données?
Tim Schmelter
4
Remarque rapide, les requêtes entre bases de données comme celle-ci ne fonctionnent pas avec SQL Azure
Kieren Johnstone
33

J'ai récemment écrit cette fonction de table en ligne pour résoudre ce problème. Il n'est pas limité dans la plage autre que la mémoire et le stockage. Il n'accède à aucune table, il n'est donc pas nécessaire de lire ou d'écrire sur le disque en général. Il ajoute des valeurs de jointure de manière exponentielle à chaque itération, ce qui le rend très rapide même pour de très grandes plages. Il crée dix millions d'enregistrements en cinq secondes sur mon serveur. Cela fonctionne également avec des valeurs négatives.

CREATE FUNCTION [dbo].[fn_ConsecutiveNumbers]
(   
    @start int,
    @end  int
) RETURNS TABLE 
RETURN 

select
    x268435456.X
    | x16777216.X
    | x1048576.X
    | x65536.X
    | x4096.X
    | x256.X
    | x16.X
    | x1.X
    + @start
     X
from
(VALUES (0),(1),(2),(3),(4),(5),(6),(7),(8),(9),(10),(11),(12),(13),(14),(15)) as x1(X)
join
(VALUES (0),(16),(32),(48),(64),(80),(96),(112),(128),(144),(160),(176),(192),(208),(224),(240)) as x16(X)
on x1.X <= @end-@start and x16.X <= @end-@start
join
(VALUES (0),(256),(512),(768),(1024),(1280),(1536),(1792),(2048),(2304),(2560),(2816),(3072),(3328),(3584),(3840)) as x256(X)
on x256.X <= @end-@start
join
(VALUES (0),(4096),(8192),(12288),(16384),(20480),(24576),(28672),(32768),(36864),(40960),(45056),(49152),(53248),(57344),(61440)) as x4096(X)
on x4096.X <= @end-@start
join
(VALUES (0),(65536),(131072),(196608),(262144),(327680),(393216),(458752),(524288),(589824),(655360),(720896),(786432),(851968),(917504),(983040)) as x65536(X)
on x65536.X <= @end-@start
join
(VALUES (0),(1048576),(2097152),(3145728),(4194304),(5242880),(6291456),(7340032),(8388608),(9437184),(10485760),(11534336),(12582912),(13631488),(14680064),(15728640)) as x1048576(X)
on x1048576.X <= @end-@start
join
(VALUES (0),(16777216),(33554432),(50331648),(67108864),(83886080),(100663296),(117440512),(134217728),(150994944),(167772160),(184549376),(201326592),(218103808),(234881024),(251658240)) as x16777216(X)
on x16777216.X <= @end-@start
join
(VALUES (0),(268435456),(536870912),(805306368),(1073741824),(1342177280),(1610612736),(1879048192)) as x268435456(X)
on x268435456.X <= @end-@start
WHERE @end >=
    x268435456.X
    | isnull(x16777216.X, 0)
    | isnull(x1048576.X, 0)
    | isnull(x65536.X, 0)
    | isnull(x4096.X, 0)
    | isnull(x256.X, 0)
    | isnull(x16.X, 0)
    | isnull(x1.X, 0)
    + @start

GO

SELECT X FROM fn_ConsecutiveNumbers(5, 500);

C'est également pratique pour les plages de dates et d'heures:

SELECT DATEADD(day,X, 0) DayX 
FROM fn_ConsecutiveNumbers(datediff(day,0,'5/8/2015'), datediff(day,0,'5/31/2015'))

SELECT DATEADD(hour,X, 0) HourX 
FROM fn_ConsecutiveNumbers(datediff(hour,0,'5/8/2015'), datediff(hour,0,'5/8/2015 12:00 PM'));

Vous pouvez utiliser une jointure à application croisée pour diviser les enregistrements en fonction des valeurs de la table. Ainsi, par exemple, pour créer un enregistrement pour chaque minute sur une plage de temps dans une table, vous pouvez faire quelque chose comme:

select TimeRanges.StartTime,
    TimeRanges.EndTime,
    DATEADD(minute,X, 0) MinuteX
FROM TimeRanges
cross apply fn_ConsecutiveNumbers(datediff(hour,0,TimeRanges.StartTime), 
        datediff(hour,0,TimeRanges.EndTime)) ConsecutiveNumbers
Brian Pressler
la source
1
Wow, cette requête initiale est RAPIDE. Beaucoup plus rapide que la solution CLR publiée ci-dessus. Merci!
Derreck Dean
1
Bien - j'ai toujours un client sur SQL Server 2008 et c'était exactement ce dont j'avais besoin! Très intelligent!
STLDev
1
cela fonctionne pour 1-100 mais échoue ensuite. Même votre exemple de génération de 5-500 ne fonctionne pas pour moi, il montre 5, 21, ... 484, 500
Rez.Net
3
Si vous voulez qu'il soit trié, vous devrez ajouter un ordre par clause:SELECT X FROM fn_ConsecutiveNumbers(5, 500) ORDER BY X;
Brian Pressler
29

La meilleure option que j'ai utilisée est la suivante:

DECLARE @min bigint, @max bigint
SELECT @Min=919859000000 ,@Max=919859999999

SELECT TOP (@Max-@Min+1) @Min-1+row_number() over(order by t1.number) as N
FROM master..spt_values t1 
    CROSS JOIN master..spt_values t2

J'ai généré des millions d'enregistrements en utilisant cela et cela fonctionne parfaitement.

Habeeb
la source
2
C'est la solution la plus élégante ici, mais je pense qu'il est difficile pour beaucoup de gens de la comprendre (j'avais fait cela avec master.sys.all_columns). @STLDeveloper, oui cela fonctionne avec 2008 et plus tard.
Cetin Basoz
13

Ça marche pour moi!

select top 50 ROW_NUMBER() over(order by a.name) + 1000 as Rcount
from sys.all_objects a
Nguyen Son Tung
la source
2
Belle ligne unique - mais sachez que le nombre maximum de lignes dépendra de sys.all_objects- pour les petites plages <2000 éléments, ce n'est pas un problème. Vous ne savez pas s'il aura des problèmes d'autorisations? parfait pour générer rapidement un lot de données de test.
Freedomn-m
@ Freedomn-m Une façon d'augmenter le nombre maximum de lignes serait d'effectuer une jointure auto-croisée. select top 50 ROW_NUMBER() over(order by a.name) + 1000 as Rcount from sys.all_objects a, sys.all_objects b. Là où je ne pouvais générer que 2384 lignes auparavant, je peux maintenant générer 5683456 lignes.
Klicker
9

Le meilleur moyen est d'utiliser des ctes récursifs.

declare @initial as int = 1000;
declare @final as int =1050;

with cte_n as (
    select @initial as contador
    union all
    select contador+1 from cte_n 
    where contador <@final
) select * from cte_n option (maxrecursion 0)

saludos.

cesargroening
la source
1
Cela a été très utile. J'ai modifié le code pour pouvoir insérer 100 000 lignes. Avec ma solution, cela a pris environ 13 minutes; en utilisant le vôtre, cela a pris cinq secondes. Muchísimas gracias.
Cthulhu
2
En fait, les CTE récursifs sont l'un des pires moyens de compter. Ils peuvent même être battus par une boucle While dans une transaction et la boucle While produira beaucoup moins de lectures. La méthode cCTE (Cascading CTEs, à l'origine par Itizik Ben-Gan) est beaucoup plus rapide et ne produit aucune lecture.
Jeff Moden
9
declare @start int = 1000
declare @end    int =1050

;with numcte  
AS  
(  
  SELECT @start [SEQUENCE]  
  UNION all  
  SELECT [SEQUENCE] + 1 FROM numcte WHERE [SEQUENCE] < @end 
)      
SELECT * FROM numcte
Sowbarani Karthikeyan
la source
1
Est-ce différent de la réponse de @Jayvee?
Noel
1
Ouais dans la condition où il est mentionné comme num + 1 <1050 qui imprimera jusqu'à 1049 seulement.
Sowbarani Karthikeyan
2
Une modification (ou un commentaire) de la réponse existante qui est essentielle la même apporterait plus de valeur qu'une réponse entièrement nouvelle.
Noel
7

Si vous ne rencontrez pas de problème pour installer un assembly CLR sur votre serveur, une bonne option consiste à écrire une fonction table dans .NET. De cette façon, vous pouvez utiliser une syntaxe simple, ce qui facilite la jonction avec d'autres requêtes et, en prime, ne gaspille pas de mémoire car le résultat est diffusé.

Créez un projet contenant la classe suivante:

using System;
using System.Collections;
using System.Data;
using System.Data.Sql;
using System.Data.SqlTypes;
using Microsoft.SqlServer.Server;

namespace YourNamespace
{
   public sealed class SequenceGenerator
    {
        [SqlFunction(FillRowMethodName = "FillRow")]
        public static IEnumerable Generate(SqlInt32 start, SqlInt32 end)
        {
            int _start = start.Value;
            int _end = end.Value;
            for (int i = _start; i <= _end; i++)
                yield return i;
        }

        public static void FillRow(Object obj, out int i)
        {
            i = (int)obj;
        }

        private SequenceGenerator() { }
    }
}

Placez l'assemblage quelque part sur le serveur et exécutez:

USE db;
CREATE ASSEMBLY SqlUtil FROM 'c:\path\to\assembly.dll'
WITH permission_set=Safe;

CREATE FUNCTION [Seq](@start int, @end int) 
RETURNS TABLE(i int)
AS EXTERNAL NAME [SqlUtil].[YourNamespace.SequenceGenerator].[Generate];

Vous pouvez maintenant exécuter:

select * from dbo.seq(1, 1000000)
AlexDev
la source
1
J'ai essayé cette solution et cela fonctionne bien, mais pas très vite. Si vous générez seulement 1 000 numéros, ou peut-être 10 000, c'est assez rapide. Si vous êtes comme moi et que vous devez générer des milliards de chiffres, la solution de Brian Pressler ci-dessous est incroyablement rapide par rapport à SQL CLR.
Derreck Dean le
2
@DerreckDean Vous avez raison. Je pense que c'est la meilleure solution car elle est facile à créer et à utiliser (et rapide comme vous le dites). Dans mon cas, j'avais déjà un assemblage pour concaténer des chaînes, alors je l'ai juste ajouté ici.
AlexDev
1
J'avais également un assemblage existant et j'ai essayé les deux méthodes. Je génère un nombre indéterminé de nombres à ajouter aux dates (en gros, j'ai recréé le planificateur de l'agent de serveur SQL pour générer des dates pour notre application interne, et 100 niveaux de récursivité n'allaient pas le couper pour générer plusieurs années de datetimes, peut-être jusqu'à la seconde.), j'ai donc pu tester en profondeur plusieurs solutions à partir de ce fil. J'apprécie votre contribution!
Derreck Dean
7

Rien de nouveau mais j'ai réécrit la solution de Brian Pressler pour être plus agréable à l'œil, cela pourrait être utile à quelqu'un (même si c'est juste pour moi):

alter function [dbo].[fn_GenerateNumbers]
(   
    @start int,
    @end  int
) returns table
return

with 
b0 as (select n from (values (0),(0x00000001),(0x00000002),(0x00000003),(0x00000004),(0x00000005),(0x00000006),(0x00000007),(0x00000008),(0x00000009),(0x0000000A),(0x0000000B),(0x0000000C),(0x0000000D),(0x0000000E),(0x0000000F)) as b0(n)),
b1 as (select n from (values (0),(0x00000010),(0x00000020),(0x00000030),(0x00000040),(0x00000050),(0x00000060),(0x00000070),(0x00000080),(0x00000090),(0x000000A0),(0x000000B0),(0x000000C0),(0x000000D0),(0x000000E0),(0x000000F0)) as b1(n)),
b2 as (select n from (values (0),(0x00000100),(0x00000200),(0x00000300),(0x00000400),(0x00000500),(0x00000600),(0x00000700),(0x00000800),(0x00000900),(0x00000A00),(0x00000B00),(0x00000C00),(0x00000D00),(0x00000E00),(0x00000F00)) as b2(n)),
b3 as (select n from (values (0),(0x00001000),(0x00002000),(0x00003000),(0x00004000),(0x00005000),(0x00006000),(0x00007000),(0x00008000),(0x00009000),(0x0000A000),(0x0000B000),(0x0000C000),(0x0000D000),(0x0000E000),(0x0000F000)) as b3(n)),
b4 as (select n from (values (0),(0x00010000),(0x00020000),(0x00030000),(0x00040000),(0x00050000),(0x00060000),(0x00070000),(0x00080000),(0x00090000),(0x000A0000),(0x000B0000),(0x000C0000),(0x000D0000),(0x000E0000),(0x000F0000)) as b4(n)),
b5 as (select n from (values (0),(0x00100000),(0x00200000),(0x00300000),(0x00400000),(0x00500000),(0x00600000),(0x00700000),(0x00800000),(0x00900000),(0x00A00000),(0x00B00000),(0x00C00000),(0x00D00000),(0x00E00000),(0x00F00000)) as b5(n)),
b6 as (select n from (values (0),(0x01000000),(0x02000000),(0x03000000),(0x04000000),(0x05000000),(0x06000000),(0x07000000),(0x08000000),(0x09000000),(0x0A000000),(0x0B000000),(0x0C000000),(0x0D000000),(0x0E000000),(0x0F000000)) as b6(n)),
b7 as (select n from (values (0),(0x10000000),(0x20000000),(0x30000000),(0x40000000),(0x50000000),(0x60000000),(0x70000000)) as b7(n))

select s.n
from (
    select
          b7.n
        | b6.n
        | b5.n
        | b4.n
        | b3.n
        | b2.n
        | b1.n
        | b0.n
        + @start
         n
    from b0
    join b1 on b0.n <= @end-@start and b1.n <= @end-@start
    join b2 on b2.n <= @end-@start
    join b3 on b3.n <= @end-@start
    join b4 on b4.n <= @end-@start
    join b5 on b5.n <= @end-@start
    join b6 on b6.n <= @end-@start
    join b7 on b7.n <= @end-@start
) s
where @end >= s.n

GO
Guillaume86
la source
1
Je crois que vous avez distillé l'essence d'un bel algorithme dans un joli code carrément.
Clay
1
Les résultats sont classés dans un ordre étrange mais pas chaotique. Testez-le sur une plage de 5 à 500. Il renvoie 5,21,37, ..., 245,6,22, ... Savez-vous comment la commande influencerait les performances? Les solutions basées sur ROW_NUMBER()n'ont pas ce problème.
Przemyslaw Remin
1
Je ne suis pas un expert, mais intuitivement, je suppose que le serveur SQL devra mettre tous les résultats en mémoire et les ordonner avant de les renvoyer, donc plus d'utilisation de la mémoire et de réponse retardée au lieu de simplement diffuser les résultats au fur et à mesure.
Guillaume86
6

2 ans plus tard, mais j'ai découvert que j'avais le même problème. Voici comment je l'ai résolu. (modifié pour inclure les paramètres)

DECLARE @Start INT, @End INT
SET @Start = 1000
SET @End = 1050

SELECT  TOP (@End - @Start+1) ROW_NUMBER() OVER (ORDER BY S.[object_id])+(@Start - 1) [Numbers]
FROM    sys.all_objects S WITH (NOLOCK)
scarabée
la source
5

La réponse de slartidan peut être améliorée, en termes de performances, en éliminant toutes les références au produit cartésien et en utilisant à la ROW_NUMBER()place ( plan d'exécution comparé ):

SELECT ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) AS n FROM 
(VALUES(0),(1),(2),(3),(4),(5),(6),(7),(8),(9)) x1(x),
(VALUES(0),(1),(2),(3),(4),(5),(6),(7),(8),(9)) x2(x),
(VALUES(0),(1),(2),(3),(4),(5),(6),(7),(8),(9)) x3(x),
(VALUES(0),(1),(2),(3),(4),(5),(6),(7),(8),(9)) x4(x),
(VALUES(0),(1),(2),(3),(4),(5),(6),(7),(8),(9)) x5(x)
ORDER BY n

Enveloppez-le dans un CTE et ajoutez une clause where pour sélectionner les nombres souhaités:

DECLARE @n1 AS INT = 100;
DECLARE @n2 AS INT = 40099;
WITH numbers AS (
    SELECT ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) AS n FROM 
    (VALUES(0),(1),(2),(3),(4),(5),(6),(7),(8),(9)) x1(x),
    (VALUES(0),(1),(2),(3),(4),(5),(6),(7),(8),(9)) x2(x),
    (VALUES(0),(1),(2),(3),(4),(5),(6),(7),(8),(9)) x3(x),
    (VALUES(0),(1),(2),(3),(4),(5),(6),(7),(8),(9)) x4(x),
    (VALUES(0),(1),(2),(3),(4),(5),(6),(7),(8),(9)) x5(x)
)
SELECT numbers.n
FROM numbers
WHERE n BETWEEN @n1 and @n2
ORDER BY n
Salman A
la source
1
ROW_NUMBER ne commence qu'à 1. Comment pouvons-nous partir de zéro avec votre méthode?
stomie le
2
@stomy SELECT ROW_NUMBER() OVER (...) - 1 AS n. Dans certains cas, cela peut nuire aux performances.
Salman A
4

Voici quelques solutions tout à fait optimales et compatibles:

USE master;

declare @min as int;    set @min = 1000;
declare @max as int;    set @max = 1050;    --null returns all

--  Up to 256 - 2 048 rows depending on SQL Server version
select  isnull(@min,0)+number.number  as  number
FROM    dbo.spt_values  AS  number
WHERE   number."type"                   =   'P'     --integers
    and (   @max                            is null     --return all
        or  isnull(@min,0)+number.number    <=  @max    --return up to max
    )
order by    number
;

--  Up to 65 536 - 4 194 303 rows depending on SQL Server version
select  isnull(@min,0)+value1.number+(value2.number*numberCount.numbers)  as  number
FROM  dbo.spt_values            AS  value1
  cross join  dbo.spt_values    AS  value2
  cross join (  --get the number of numbers (depends on version)
    select  sum(1)  as  numbers
    from    dbo.spt_values
    where   spt_values."type"   =   'P' --integers
  )                             as  numberCount
WHERE   value1."type" = 'P'   --integers
    and value2."type" = 'P'   --integers
    and (   @max    is null     --return all
        or  isnull(@min,0)+value1.number+(value2.number*numberCount.numbers)    
            <=  @max            --return up to max
    )
order by    number
;
jumxozizi
la source
1
Cette méthode est-elle en quelque sorte meilleure que la simple selectingénierie where spt_values.number between @min and @max?
underscore_d
2
Le filtre Type = 'P' est requis pour éviter les doublons. Avec ce filtre, la table renverra les nombres 0 - 2047. Ainsi, le filtre "nombre entre @min et @max" fonctionnera tant que les variables sont dans cette plage. Ma solution vous permettra d'obtenir jusqu'à 2048 lignes dans la plage entière (-2 147 483 648) - (2 147 483 647).
jumxozizi le
1
la logique ci-dessus n'est utile que lorsque la différence entre le nombre maximum et minimum inférieur à 2048 et une fois peut un maximum de 2048 enregistrements à un moment donné
Smart003
4

Je sais que j'ai 4 ans trop tard, mais je suis tombé sur une autre solution alternative à ce problème. Le problème de la vitesse n'est pas seulement le pré-filtrage, mais aussi la prévention du tri. Il est possible de forcer l'ordre de jointure à s'exécuter d'une manière que le produit cartésien compte réellement à la suite de la jointure. En utilisant la réponse de slartidan comme point de départ:

    WITH x AS (SELECT n FROM (VALUES (0),(1),(2),(3),(4),(5),(6),(7),(8),(9)) v(n))
SELECT ones.n + 10*tens.n + 100*hundreds.n + 1000*thousands.n
FROM x ones,     x tens,      x hundreds,       x thousands
ORDER BY 1

Si nous connaissons la plage que nous voulons, nous pouvons la spécifier via @Upper et @Lower. En combinant l'indicateur de jointure REMOTE avec TOP, nous pouvons calculer uniquement le sous-ensemble de valeurs que nous voulons sans rien perdre.

WITH x AS (SELECT n FROM (VALUES (0),(1),(2),(3),(4),(5),(6),(7),(8),(9)) v(n))
SELECT TOP (1+@Upper-@Lower) @Lower + ones.n + 10*tens.n + 100*hundreds.n + 1000*thousands.n
FROM x thousands
INNER REMOTE JOIN x hundreds on 1=1
INNER REMOTE JOIN x tens on 1=1
INNER REMOTE JOIN x ones on 1=1

L'indication de jointure REMOTE force l'optimiseur à comparer en premier sur le côté droit de la jointure. En spécifiant chaque jointure comme REMOTE de la valeur la plus importante à la valeur la moins significative, la jointure elle-même comptera correctement de un. Pas besoin de filtrer avec un WHERE ou de trier avec un ORDER BY.

Si vous souhaitez augmenter la plage, vous pouvez continuer à ajouter des jointures supplémentaires avec des ordres de grandeur progressivement plus élevés, à condition qu'elles soient classées du plus important au moins significatif dans la clause FROM.

Notez qu'il s'agit d'une requête spécifique à SQL Server 2008 ou supérieur.

méchoïde
la source
1
Très beau effectivement. La même technique peut être appliquée à l'excellente réponse de Brian Pressler et à la belle réécriture de Guillaume86.
Clay
3

Cela fera également

DECLARE @startNum INT = 1000;
DECLARE @endNum INT = 1050;
INSERT  INTO dbo.Numbers
        ( Num
        )
        SELECT  CASE WHEN MAX(Num) IS NULL  THEN @startNum
                     ELSE MAX(Num) + 1
                END AS Num
        FROM    dbo.Numbers
GO 51
BICube
la source
3

La meilleure vitesse lors de l'exécution de la requête

DECLARE @num INT = 1000
WHILE(@num<1050)
begin
 INSERT  INTO [dbo].[Codes]
    (   Code
    ) 
    VALUES (@num)
    SET @num = @num + 1
end
Farhad Manafi
la source
3

CTE récursif en taille exponentielle (même pour la récursivité par défaut de 100, cela peut générer jusqu'à 2 ^ 100 nombres):

DECLARE @startnum INT=1000
DECLARE @endnum INT=1050
DECLARE @size INT=@endnum-@startnum+1
;
WITH numrange (num) AS (
    SELECT 1 AS num
    UNION ALL
    SELECT num*2 FROM numrange WHERE num*2<=@size
    UNION ALL
    SELECT num*2+1 FROM numrange WHERE num*2+1<=@size
)
SELECT num+@startnum-1 FROM numrange order by num
user7555577
la source
D'après l'OP, je pense @startnum et endnumdevrait être saisi par l'utilisateur?
JC
2

J'ai dû insérer le chemin du fichier image dans la base de données en utilisant une méthode similaire. La requête ci-dessous a bien fonctionné:

DECLARE @num INT = 8270058
WHILE(@num<8270284)
begin
    INSERT  INTO [dbo].[Galleries]
    (ImagePath) 
    VALUES 
    ('~/Content/Galeria/P'+CONVERT(varchar(10), @num)+'.JPG')

    SET @num = @num + 1
end

Le code pour vous serait:

DECLARE @num INT = 1000
WHILE(@num<1051)
begin
    SELECT @num

    SET @num = @num + 1
end
Maludasek
la source
2

C'est ce que je fais, c'est assez rapide et flexible et peu de code.

DECLARE @count  int =   65536;
DECLARE @start  int =   11;
DECLARE @xml    xml =   REPLICATE(CAST('<x/>' AS nvarchar(max)), @count);

; WITH GenerateNumbers(Num) AS
(
    SELECT  ROW_NUMBER() OVER (ORDER BY @count) + @start - 1
    FROM    @xml.nodes('/x') X(T)
)
SELECT  Num
FROM    GenerateNumbers;

Notez que (ORDER BY @count) est un mannequin. Il ne fait rien mais ROW_NUMBER () nécessite un ORDER BY.

Edit : J'ai réalisé que la question initiale était d'obtenir une plage de x à y. Mon script peut être modifié comme ceci pour obtenir une plage:

DECLARE @start  int =   5;
DECLARE @end    int =   21;
DECLARE @xml    xml =   REPLICATE(CAST('<x/>' AS nvarchar(max)), @end - @start + 1);

; WITH GenerateNumbers(Num) AS
(
    SELECT  ROW_NUMBER() OVER (ORDER BY @end) + @start - 1
    FROM    @xml.nodes('/x') X(T)
)
SELECT  Num
FROM    GenerateNumbers;
JohnnyIV
la source
1
C'était très rapide - et flexible. A bien fonctionné pour mes besoins.
AndrewBanjo1968
1
-- Generate Numeric Range
-- Source: http://www.sqlservercentral.com/scripts/Miscellaneous/30397/

CREATE TABLE #NumRange(
    n int
)

DECLARE @MinNum int
DECLARE @MaxNum int
DECLARE @I int

SET NOCOUNT ON

SET @I = 0
WHILE @I <= 9 BEGIN
    INSERT INTO #NumRange VALUES(@I)
    SET @I = @I + 1
END


SET @MinNum = 1
SET @MaxNum = 1000000

SELECT  num = a.n +
    (b.n * 10) +
    (c.n * 100) +
    (d.n * 1000) +
    (e.n * 10000)
FROM    #NumRange a
CROSS JOIN #NumRange b
CROSS JOIN #NumRange c
CROSS JOIN #NumRange d
CROSS JOIN #NumRange e
WHERE   a.n +
    (b.n * 10) +
    (c.n * 100) +
    (d.n * 1000) +
    (e.n * 10000) BETWEEN @MinNum AND @MaxNum
ORDER BY a.n +
    (b.n * 10) +
    (c.n * 100) +
    (d.n * 1000) +
    (e.n * 10000) 

DROP TABLE #NumRange
Elexsandro Rangel dos Santos
la source
1

Cela ne fonctionne que pour les séquences tant que certaines tables d'application ont des lignes. Supposons que je veux une séquence de 1 à 100 et que la table d'application dbo.foo avec la colonne (de type numérique ou chaîne) foo.bar:

select 
top 100
row_number() over (order by dbo.foo.bar) as seq
from dbo.foo

Malgré sa présence dans une clause order by, dbo.foo.bar n'a pas besoin d'avoir des valeurs distinctes ou même non nulles.

Bien sûr, SQL Server 2012 a des objets de séquence, il existe donc une solution naturelle dans ce produit.

BobHy
la source
1

Voici ce que j'ai trouvé:

create or alter function dbo.fn_range(@start int, @end int)  returns table
return
with u2(n) as (
    select n 
    from (VALUES (0),(1),(2),(3)) v(n)
), 
u8(n) as (
    select
        x0.n | x1.n * 4 | x2.n * 16 | x3.n * 64 as n
    from u2 x0, u2 x1, u2 x2, u2 x3
)
select 
    @start + s.n as n
from (
    select
        x0.n | isnull(x1.n, 0) * 256 | isnull(x2.n, 0) * 65536 as n
    from u8 x0 
    left join u8 x1 on @end-@start > 256
    left join u8 x2 on @end-@start > 65536
) s
where s.n < @end - @start

Génère jusqu'à 2 ^ 24 valeurs. Les conditions de jointure le maintiennent rapide pour les petites valeurs.

τεκ
la source
1

Cela s'est terminé pour moi en 36 secondes sur notre serveur DEV. Comme la réponse de Brian, il est important de se concentrer sur le filtrage de la plage depuis la requête; a BETWEEN essaie toujours de générer tous les enregistrements initiaux avant la limite inférieure même s'il n'en a pas besoin.

declare @s bigint = 10000000
    ,   @e bigint = 20000000

;WITH 
Z AS (SELECT 0 z FROM (VALUES (0),(1),(2),(3),(4),(5),(6),(7),(8),(9),(10),(11),(12),(13),(14),(15)) T(n)),
Y AS (SELECT 0 z FROM Z a, Z b, Z c, Z d, Z e, Z f, Z g, Z h, Z i, Z j, Z k, Z l, Z m, Z n, Z o, Z p),
N AS (SELECT ROW_NUMBER() OVER (PARTITION BY 0 ORDER BY z) n FROM Y)

SELECT TOP (1+@e-@s) @s + n - 1 FROM N

Notez que ROW_NUMBER est un bigint , donc nous ne pouvons pas parcourir 2 ^^ 64 (== 16 ^^ 16) enregistrements générés avec une méthode qui l'utilise. Cette requête respecte donc la même limite supérieure sur les valeurs générées.

Prix ​​de J Bryan
la source
1

Cela utilise un code procédural et une fonction table. Lent, mais facile et prévisible.

CREATE FUNCTION [dbo].[Sequence] (@start int, @end int)
RETURNS
@Result TABLE(ID int)
AS
begin
declare @i int;
set @i = @start;
while @i <= @end 
    begin
        insert into @result values (@i);
        set @i = @i+1;
    end
return;
end

Usage:

SELECT * FROM dbo.Sequence (3,7);
ID
3
4
5
6
7

C'est une table, vous pouvez donc l'utiliser en jointure avec d'autres données. J'utilise le plus souvent cette fonction comme côté gauche d'une jointure contre une heure, un jour, etc. GROUP BY pour assurer une séquence contiguë de valeurs de temps.

SELECT DateAdd(hh,ID,'2018-06-20 00:00:00') as HoursInTheDay FROM dbo.Sequence (0,23) ;

HoursInTheDay
2018-06-20 00:00:00.000
2018-06-20 01:00:00.000
2018-06-20 02:00:00.000
2018-06-20 03:00:00.000
2018-06-20 04:00:00.000
(...)

Les performances sont sans intérêt (16 secondes pour un million de lignes) mais suffisantes pour de nombreuses raisons.

SELECT count(1) FROM [dbo].[Sequence] (
   1000001
  ,2000000)
GO
Robert Calhoun
la source
1

Oracle 12c; Rapide mais limité:

select rownum+1000 from all_objects fetch first 50 rows only;

Remarque : limité au nombre de lignes de la vue all_objects;

Witold Kaczurba
la source
1

La solution que j'ai développée et utilisée depuis un certain temps maintenant (chevauchant certains sur les œuvres partagées d'autres) est légèrement similaire à au moins une publiée. Il ne fait référence à aucune table et renvoie une plage non triée allant jusqu'à 1048576 valeurs (2 ^ 20) et peut inclure des négatifs si vous le souhaitez. Vous pouvez bien sûr trier le résultat si nécessaire. Il fonctionne assez rapidement, en particulier sur les plus petites plages.

Select value from dbo.intRange(-500, 1500) order by value  -- returns 2001 values

create function dbo.intRange 
(   
    @Starting as int,
    @Ending as int
)
returns table
as
return (
    select value
    from (
        select @Starting +
            ( bit00.v | bit01.v | bit02.v | bit03.v
            | bit04.v | bit05.v | bit06.v | bit07.v
            | bit08.v | bit09.v | bit10.v | bit11.v
            | bit12.v | bit13.v | bit14.v | bit15.v
            | bit16.v | bit17.v | bit18.v | bit19.v
            ) as value
        from       (select 0 as v union ALL select 0x00001 as v) as bit00
        cross join (select 0 as v union ALL select 0x00002 as v) as bit01
        cross join (select 0 as v union ALL select 0x00004 as v) as bit02
        cross join (select 0 as v union ALL select 0x00008 as v) as bit03
        cross join (select 0 as v union ALL select 0x00010 as v) as bit04
        cross join (select 0 as v union ALL select 0x00020 as v) as bit05
        cross join (select 0 as v union ALL select 0x00040 as v) as bit06
        cross join (select 0 as v union ALL select 0x00080 as v) as bit07
        cross join (select 0 as v union ALL select 0x00100 as v) as bit08
        cross join (select 0 as v union ALL select 0x00200 as v) as bit09
        cross join (select 0 as v union ALL select 0x00400 as v) as bit10
        cross join (select 0 as v union ALL select 0x00800 as v) as bit11
        cross join (select 0 as v union ALL select 0x01000 as v) as bit12
        cross join (select 0 as v union ALL select 0x02000 as v) as bit13
        cross join (select 0 as v union ALL select 0x04000 as v) as bit14
        cross join (select 0 as v union ALL select 0x08000 as v) as bit15
        cross join (select 0 as v union ALL select 0x10000 as v) as bit16
        cross join (select 0 as v union ALL select 0x20000 as v) as bit17
        cross join (select 0 as v union ALL select 0x40000 as v) as bit18
        cross join (select 0 as v union ALL select 0x80000 as v) as bit19
    ) intList
    where @Ending - @Starting < 0x100000
        and intList.value between @Starting and @Ending
)
rlhane
la source
1
;WITH u AS (
    SELECT Unit FROM (VALUES (0),(1),(2),(3),(4),(5),(6),(7),(8),(9)) v(Unit)
),
d AS (
    SELECT 
        (Thousands+Hundreds+Tens+Units) V
    FROM 
           (SELECT Thousands = Unit * 1000 FROM u) Thousands 
           ,(SELECT Hundreds = Unit * 100 FROM u) Hundreds 
           ,(SELECT Tens = Unit * 10 FROM u) Tens 
           ,(SELECT Units = Unit FROM u) Units
    WHERE
           (Thousands+Hundreds+Tens+Units) <= 10000
)

SELECT * FROM d ORDER BY v
Chriz
la source
1

J'ai créé la fonction ci-dessous après avoir lu ce fil. Simple et rapide:

go
create function numbers(@begin int, @len int)
returns table as return
with d as (
    select 1 v from (values(0),(0),(0),(0),(0),(0),(0),(0),(0),(0),(0),(0),(0),(0),(0),(0)) d(v)
)
select top (@len) @begin -1 + row_number() over(order by (select null)) v
from d d0
cross join d d1
cross join d d2
cross join d d3
cross join d d4
cross join d d5
cross join d d6
cross join d d7
go

select * from numbers(987654321,500000)
Lanjing Jin
la source