Eso fue interesante.
Mi objetivo era generar un número dado de niveles con un número aleatorio de filas secundarias por cada nivel en una estructura jerárquica correctamente vinculada. Una vez que esta estructura está lista, es fácil agregarle información adicional, como nombres de archivos y carpetas.
Entonces, quería generar una tabla clásica para almacenar un árbol:
ID int NOT NULL
ParentID int NULL
Lvl int NOT NULL
Como estamos lidiando con la recursividad, el CTE recursivo parece una opción natural.
Necesitaré una tabla de números . Los números en la tabla debe comenzar desde 1. Debe haber por lo menos 20 números en la tabla: MAX(LvlMax)
.
CREATE TABLE [dbo].[Numbers](
[Number] [int] NOT NULL,
CONSTRAINT [PK_Numbers] PRIMARY KEY CLUSTERED
(
[Number] ASC
));
INSERT INTO Numbers(Number)
SELECT TOP(1000)
ROW_NUMBER() OVER(ORDER BY S.object_id) AS Number
FROM
sys.all_objects AS S
ORDER BY Number;
Los parámetros para la generación de datos deben almacenarse en una tabla:
DECLARE @Intervals TABLE (Lvl int, LvlMin int, LvlMax int);
INSERT INTO @Intervals (Lvl, LvlMin, LvlMax) VALUES
(1, 5, 20),
(2, 1, 10),
(3, 1, 5);
Tenga en cuenta que la consulta es bastante flexible y todos los parámetros están separados en un solo lugar. Puede agregar más niveles si es necesario, solo agregue una fila adicional de parámetros.
Para realizar dicha generación dinámica posible que tuve que recordar el número al azar de filas para el siguiente nivel, así que tengo una columna adicional ChildRowCount
.
Generar únicos IDs
también es algo complicado. Codifiqué el límite de 100 filas secundarias por 1 fila principal para garantizar que IDs
no se repita. De esto POWER(100, CTE.Lvl)
se trata. Como resultado, hay grandes brechas en IDs
. Ese número podría ser un MAX(LvlMax)
, pero puse constante 100 en la consulta por simplicidad. El número de niveles no está codificado, pero está determinado por @Intervals
.
Esta formula
CAST(CRYPT_GEN_RANDOM(4) as int) / 4294967295.0 + 0.5
genera un número aleatorio de coma flotante en el rango [0..1)
, que luego se escala al intervalo requerido.
La lógica de consulta es simple. Es recursivo. El primer paso genera un conjunto de filas del primer nivel. El número de filas está determinado por un número aleatorio en TOP
. Además, para cada fila hay un número aleatorio separado de filas secundarias almacenadas ChildRowCount
.
La parte recursiva se utiliza CROSS APPLY
para generar un número determinado de filas secundarias por cada fila principal. Tuve que usar en WHERE Numbers.Number <= CTE.ChildRowCount
lugar de TOP(CTE.ChildRowCount)
, porque TOP
no está permitido en la parte recursiva de CTE. No sabía sobre esta limitación de SQL Server antes.
WHERE CTE.ChildRowCount IS NOT NULL
detiene la recursividad
Violín de SQL
WITH
CTE
AS
(
SELECT
TOP(CAST(
(CAST(CRYPT_GEN_RANDOM(4) as int) / 4294967295.0 + 0.5) *
(
1 + (SELECT I.LvlMax FROM @Intervals AS I WHERE I.Lvl = 1)
- (SELECT I.LvlMin FROM @Intervals AS I WHERE I.Lvl = 1)
)
+ (SELECT I.LvlMin FROM @Intervals AS I WHERE I.Lvl = 1)
AS int))
Numbers.Number AS ID
,NULL AS ParentID
,1 AS Lvl
,CAST(
(CAST(CRYPT_GEN_RANDOM(4) as int) / 4294967295.0 + 0.5) *
(
1 + (SELECT I.LvlMax FROM @Intervals AS I WHERE I.Lvl = 2)
- (SELECT I.LvlMin FROM @Intervals AS I WHERE I.Lvl = 2)
)
+ (SELECT I.LvlMin FROM @Intervals AS I WHERE I.Lvl = 2)
AS int) AS ChildRowCount
FROM Numbers
ORDER BY Numbers.Number
UNION ALL
SELECT
CA.Number + CTE.ID * POWER(100, CTE.Lvl) AS ID
,CTE.ID AS ParentID
,CTE.Lvl + 1 AS Lvl
,CA.ChildRowCount
FROM
CTE
CROSS APPLY
(
SELECT
Numbers.Number
,CAST(
(CAST(CRYPT_GEN_RANDOM(4) as int) / 4294967295.0 + 0.5) *
(
1 + (SELECT I.LvlMax FROM @Intervals AS I WHERE I.Lvl = CTE.Lvl + 2)
- (SELECT I.LvlMin FROM @Intervals AS I WHERE I.Lvl = CTE.Lvl + 2)
)
+ (SELECT I.LvlMin FROM @Intervals AS I WHERE I.Lvl = CTE.Lvl + 2)
AS int) AS ChildRowCount
FROM Numbers
WHERE Numbers.Number <= CTE.ChildRowCount
) AS CA
WHERE
CTE.ChildRowCount IS NOT NULL
)
SELECT *
FROM CTE
ORDER BY Lvl, ParentID, ID;
Resultado (puede haber hasta 20 + 20 * 10 + 200 * 5 = 1220 filas si tiene suerte)
+---------+----------+-----+-------------------+
| ID | ParentID | Lvl | ChildRowCount |
+---------+----------+-----+-------------------+
| 1 | NULL | 1 | 3 |
| 2 | NULL | 1 | 1 |
| 3 | NULL | 1 | 6 |
| 4 | NULL | 1 | 5 |
| 5 | NULL | 1 | 3 |
| 6 | NULL | 1 | 7 |
| 7 | NULL | 1 | 1 |
| 8 | NULL | 1 | 6 |
| 101 | 1 | 2 | 3 |
| 102 | 1 | 2 | 5 |
| 103 | 1 | 2 | 1 |
| 201 | 2 | 2 | 5 |
| 301 | 3 | 2 | 4 |
| 302 | 3 | 2 | 5 |
| 303 | 3 | 2 | 1 |
| 304 | 3 | 2 | 2 |
| 305 | 3 | 2 | 4 |
| 306 | 3 | 2 | 3 |
| 401 | 4 | 2 | 3 |
| 402 | 4 | 2 | 1 |
| 403 | 4 | 2 | 2 |
| 404 | 4 | 2 | 2 |
| 405 | 4 | 2 | 4 |
| 501 | 5 | 2 | 1 |
| 502 | 5 | 2 | 3 |
| 503 | 5 | 2 | 5 |
| 601 | 6 | 2 | 2 |
| 602 | 6 | 2 | 5 |
| 603 | 6 | 2 | 3 |
| 604 | 6 | 2 | 3 |
| 605 | 6 | 2 | 4 |
| 606 | 6 | 2 | 5 |
| 607 | 6 | 2 | 4 |
| 701 | 7 | 2 | 2 |
| 801 | 8 | 2 | 2 |
| 802 | 8 | 2 | 3 |
| 803 | 8 | 2 | 3 |
| 804 | 8 | 2 | 3 |
| 805 | 8 | 2 | 5 |
| 806 | 8 | 2 | 2 |
| 1010001 | 101 | 3 | NULL |
| 1010002 | 101 | 3 | NULL |
| 1010003 | 101 | 3 | NULL |
| 1020001 | 102 | 3 | NULL |
| 1020002 | 102 | 3 | NULL |
| 1020003 | 102 | 3 | NULL |
| 1020004 | 102 | 3 | NULL |
| 1020005 | 102 | 3 | NULL |
| 1030001 | 103 | 3 | NULL |
| 2010001 | 201 | 3 | NULL |
| 2010002 | 201 | 3 | NULL |
| 2010003 | 201 | 3 | NULL |
| 2010004 | 201 | 3 | NULL |
| 2010005 | 201 | 3 | NULL |
| 3010001 | 301 | 3 | NULL |
| 3010002 | 301 | 3 | NULL |
| 3010003 | 301 | 3 | NULL |
| 3010004 | 301 | 3 | NULL |
| 3020001 | 302 | 3 | NULL |
| 3020002 | 302 | 3 | NULL |
| 3020003 | 302 | 3 | NULL |
| 3020004 | 302 | 3 | NULL |
| 3020005 | 302 | 3 | NULL |
| 3030001 | 303 | 3 | NULL |
| 3040001 | 304 | 3 | NULL |
| 3040002 | 304 | 3 | NULL |
| 3050001 | 305 | 3 | NULL |
| 3050002 | 305 | 3 | NULL |
| 3050003 | 305 | 3 | NULL |
| 3050004 | 305 | 3 | NULL |
| 3060001 | 306 | 3 | NULL |
| 3060002 | 306 | 3 | NULL |
| 3060003 | 306 | 3 | NULL |
| 4010001 | 401 | 3 | NULL |
| 4010002 | 401 | 3 | NULL |
| 4010003 | 401 | 3 | NULL |
| 4020001 | 402 | 3 | NULL |
| 4030001 | 403 | 3 | NULL |
| 4030002 | 403 | 3 | NULL |
| 4040001 | 404 | 3 | NULL |
| 4040002 | 404 | 3 | NULL |
| 4050001 | 405 | 3 | NULL |
| 4050002 | 405 | 3 | NULL |
| 4050003 | 405 | 3 | NULL |
| 4050004 | 405 | 3 | NULL |
| 5010001 | 501 | 3 | NULL |
| 5020001 | 502 | 3 | NULL |
| 5020002 | 502 | 3 | NULL |
| 5020003 | 502 | 3 | NULL |
| 5030001 | 503 | 3 | NULL |
| 5030002 | 503 | 3 | NULL |
| 5030003 | 503 | 3 | NULL |
| 5030004 | 503 | 3 | NULL |
| 5030005 | 503 | 3 | NULL |
| 6010001 | 601 | 3 | NULL |
| 6010002 | 601 | 3 | NULL |
| 6020001 | 602 | 3 | NULL |
| 6020002 | 602 | 3 | NULL |
| 6020003 | 602 | 3 | NULL |
| 6020004 | 602 | 3 | NULL |
| 6020005 | 602 | 3 | NULL |
| 6030001 | 603 | 3 | NULL |
| 6030002 | 603 | 3 | NULL |
| 6030003 | 603 | 3 | NULL |
| 6040001 | 604 | 3 | NULL |
| 6040002 | 604 | 3 | NULL |
| 6040003 | 604 | 3 | NULL |
| 6050001 | 605 | 3 | NULL |
| 6050002 | 605 | 3 | NULL |
| 6050003 | 605 | 3 | NULL |
| 6050004 | 605 | 3 | NULL |
| 6060001 | 606 | 3 | NULL |
| 6060002 | 606 | 3 | NULL |
| 6060003 | 606 | 3 | NULL |
| 6060004 | 606 | 3 | NULL |
| 6060005 | 606 | 3 | NULL |
| 6070001 | 607 | 3 | NULL |
| 6070002 | 607 | 3 | NULL |
| 6070003 | 607 | 3 | NULL |
| 6070004 | 607 | 3 | NULL |
| 7010001 | 701 | 3 | NULL |
| 7010002 | 701 | 3 | NULL |
| 8010001 | 801 | 3 | NULL |
| 8010002 | 801 | 3 | NULL |
| 8020001 | 802 | 3 | NULL |
| 8020002 | 802 | 3 | NULL |
| 8020003 | 802 | 3 | NULL |
| 8030001 | 803 | 3 | NULL |
| 8030002 | 803 | 3 | NULL |
| 8030003 | 803 | 3 | NULL |
| 8040001 | 804 | 3 | NULL |
| 8040002 | 804 | 3 | NULL |
| 8040003 | 804 | 3 | NULL |
| 8050001 | 805 | 3 | NULL |
| 8050002 | 805 | 3 | NULL |
| 8050003 | 805 | 3 | NULL |
| 8050004 | 805 | 3 | NULL |
| 8050005 | 805 | 3 | NULL |
| 8060001 | 806 | 3 | NULL |
| 8060002 | 806 | 3 | NULL |
+---------+----------+-----+-------------------+
Generando ruta completa en lugar de jerarquía vinculada
Si estamos interesados solo en los N
niveles de ruta completos profundos, podemos omitir ID
y ParentID
desde el CTE. Si tenemos una lista de posibles nombres en la tabla complementaria Names
, es fácil elegirlos de esta tabla en CTE. La Names
tabla debe tener suficientes filas para cada nivel: 20 para el nivel 1, 10 para el nivel 2, 5 para el nivel 3; 20 + 10 + 5 = 35 en total. No es necesario tener diferentes conjuntos de filas para cada nivel, pero es fácil configurarlo correctamente, así que lo hice.
DECLARE @Names TABLE (Lvl int, Name nvarchar(4000), SeqNumber int);
-- First level: AAA, BBB, CCC, etc.
INSERT INTO @Names (Lvl, Name, SeqNumber)
SELECT 1, REPLICATE(CHAR(Number+64), 3) AS Name, Number AS SeqNumber
FROM Numbers
WHERE Number <= 20;
-- Second level: 001, 002, 003, etc.
INSERT INTO @Names (Lvl, Name, SeqNumber)
SELECT 2, REPLACE(STR(Number, 3), ' ', '0') AS Name, Number AS SeqNumber
FROM Numbers
WHERE Number <= 10;
-- Third level: I, II, III, IV, V
INSERT INTO @Names (Lvl, Name, SeqNumber) VALUES
(3, 'I', 1),
(3, 'II', 2),
(3, 'III', 3),
(3, 'IV', 4),
(3, 'V', 5);
SQL Fiddle Aquí está la consulta final. Lo dividí FullPath
en FilePath
y FileName
.
WITH
CTE
AS
(
SELECT
TOP(CAST(
(CAST(CRYPT_GEN_RANDOM(4) as int) / 4294967295.0 + 0.5) *
(
1 + (SELECT I.LvlMax FROM @Intervals AS I WHERE I.Lvl = 1)
- (SELECT I.LvlMin FROM @Intervals AS I WHERE I.Lvl = 1)
)
+ (SELECT I.LvlMin FROM @Intervals AS I WHERE I.Lvl = 1)
AS int))
1 AS Lvl
,CAST(
(CAST(CRYPT_GEN_RANDOM(4) as int) / 4294967295.0 + 0.5) *
(
1 + (SELECT I.LvlMax FROM @Intervals AS I WHERE I.Lvl = 2)
- (SELECT I.LvlMin FROM @Intervals AS I WHERE I.Lvl = 2)
)
+ (SELECT I.LvlMin FROM @Intervals AS I WHERE I.Lvl = 2)
AS int) AS ChildRowCount
,N.Name AS FullPath
,N.Name AS [FilePath]
,CAST(N'' AS nvarchar(4000)) AS [FileName]
FROM
Numbers
INNER JOIN @Names AS N ON
N.SeqNumber = Numbers.Number AND N.Lvl = 1
ORDER BY Numbers.Number
UNION ALL
SELECT
CTE.Lvl + 1 AS Lvl
,CA.ChildRowCount
,CTE.FullPath + '\' + CA.Name AS FullPath
,CASE WHEN CA.ChildRowCount IS NOT NULL
THEN CTE.FullPath + '\' + CA.Name
ELSE CTE.FullPath END AS [FilePath]
,CASE WHEN CA.ChildRowCount IS NULL
THEN CA.Name
ELSE N'' END AS [FileName]
FROM
CTE
CROSS APPLY
(
SELECT
Numbers.Number
,CAST(
(CAST(CRYPT_GEN_RANDOM(4) as int) / 4294967295.0 + 0.5) *
(
1 + (SELECT I.LvlMax FROM @Intervals AS I WHERE I.Lvl = CTE.Lvl + 2)
- (SELECT I.LvlMin FROM @Intervals AS I WHERE I.Lvl = CTE.Lvl + 2)
)
+ (SELECT I.LvlMin FROM @Intervals AS I WHERE I.Lvl = CTE.Lvl + 2)
AS int) AS ChildRowCount
,N.Name
FROM
Numbers
INNER JOIN @Names AS N ON
N.SeqNumber = Numbers.Number AND N.Lvl = CTE.Lvl + 1
WHERE Numbers.Number <= CTE.ChildRowCount
) AS CA
WHERE
CTE.ChildRowCount IS NOT NULL
)
SELECT
CTE.FullPath
,CTE.[FilePath]
,CTE.[FileName]
FROM CTE
WHERE CTE.ChildRowCount IS NULL
ORDER BY FullPath;
Resultado
+-------------+----------+----------+
| FullPath | FilePath | FileName |
+-------------+----------+----------+
| AAA\001\I | AAA\001 | I |
| AAA\001\II | AAA\001 | II |
| AAA\002\I | AAA\002 | I |
| AAA\002\II | AAA\002 | II |
| AAA\002\III | AAA\002 | III |
| AAA\002\IV | AAA\002 | IV |
| AAA\002\V | AAA\002 | V |
| AAA\003\I | AAA\003 | I |
| AAA\003\II | AAA\003 | II |
| AAA\003\III | AAA\003 | III |
| AAA\004\I | AAA\004 | I |
| AAA\004\II | AAA\004 | II |
| AAA\004\III | AAA\004 | III |
| AAA\004\IV | AAA\004 | IV |
| BBB\001\I | BBB\001 | I |
| BBB\001\II | BBB\001 | II |
| CCC\001\I | CCC\001 | I |
| CCC\001\II | CCC\001 | II |
| CCC\001\III | CCC\001 | III |
| CCC\001\IV | CCC\001 | IV |
| CCC\001\V | CCC\001 | V |
| CCC\002\I | CCC\002 | I |
| CCC\003\I | CCC\003 | I |
| CCC\003\II | CCC\003 | II |
| CCC\004\I | CCC\004 | I |
| CCC\004\II | CCC\004 | II |
| CCC\005\I | CCC\005 | I |
| CCC\005\II | CCC\005 | II |
| CCC\005\III | CCC\005 | III |
| CCC\006\I | CCC\006 | I |
| CCC\006\II | CCC\006 | II |
| CCC\006\III | CCC\006 | III |
| CCC\006\IV | CCC\006 | IV |
| CCC\007\I | CCC\007 | I |
| CCC\007\II | CCC\007 | II |
| CCC\007\III | CCC\007 | III |
| CCC\007\IV | CCC\007 | IV |
| CCC\008\I | CCC\008 | I |
| CCC\008\II | CCC\008 | II |
| CCC\008\III | CCC\008 | III |
| CCC\009\I | CCC\009 | I |
| CCC\009\II | CCC\009 | II |
| CCC\009\III | CCC\009 | III |
| CCC\009\IV | CCC\009 | IV |
| CCC\010\I | CCC\010 | I |
| CCC\010\II | CCC\010 | II |
| CCC\010\III | CCC\010 | III |
| DDD\001\I | DDD\001 | I |
| DDD\001\II | DDD\001 | II |
| DDD\001\III | DDD\001 | III |
| DDD\001\IV | DDD\001 | IV |
| DDD\002\I | DDD\002 | I |
| DDD\003\I | DDD\003 | I |
| DDD\003\II | DDD\003 | II |
| DDD\003\III | DDD\003 | III |
| DDD\003\IV | DDD\003 | IV |
| DDD\004\I | DDD\004 | I |
| DDD\004\II | DDD\004 | II |
| DDD\004\III | DDD\004 | III |
| DDD\005\I | DDD\005 | I |
| DDD\006\I | DDD\006 | I |
| DDD\006\II | DDD\006 | II |
| DDD\006\III | DDD\006 | III |
| DDD\007\I | DDD\007 | I |
| DDD\007\II | DDD\007 | II |
| DDD\008\I | DDD\008 | I |
| DDD\008\II | DDD\008 | II |
| DDD\008\III | DDD\008 | III |
| DDD\009\I | DDD\009 | I |
| DDD\009\II | DDD\009 | II |
| DDD\010\I | DDD\010 | I |
| DDD\010\II | DDD\010 | II |
| DDD\010\III | DDD\010 | III |
| DDD\010\IV | DDD\010 | IV |
| DDD\010\V | DDD\010 | V |
| EEE\001\I | EEE\001 | I |
| EEE\001\II | EEE\001 | II |
| FFF\001\I | FFF\001 | I |
| FFF\002\I | FFF\002 | I |
| FFF\002\II | FFF\002 | II |
| FFF\003\I | FFF\003 | I |
| FFF\003\II | FFF\003 | II |
| FFF\003\III | FFF\003 | III |
| FFF\003\IV | FFF\003 | IV |
| FFF\003\V | FFF\003 | V |
| FFF\004\I | FFF\004 | I |
| FFF\004\II | FFF\004 | II |
| FFF\004\III | FFF\004 | III |
| FFF\004\IV | FFF\004 | IV |
| FFF\005\I | FFF\005 | I |
| FFF\006\I | FFF\006 | I |
| FFF\007\I | FFF\007 | I |
| FFF\007\II | FFF\007 | II |
| FFF\007\III | FFF\007 | III |
| GGG\001\I | GGG\001 | I |
| GGG\001\II | GGG\001 | II |
| GGG\001\III | GGG\001 | III |
| GGG\002\I | GGG\002 | I |
| GGG\003\I | GGG\003 | I |
| GGG\003\II | GGG\003 | II |
| GGG\003\III | GGG\003 | III |
| GGG\004\I | GGG\004 | I |
| GGG\004\II | GGG\004 | II |
| HHH\001\I | HHH\001 | I |
| HHH\001\II | HHH\001 | II |
| HHH\001\III | HHH\001 | III |
| HHH\002\I | HHH\002 | I |
| HHH\002\II | HHH\002 | II |
| HHH\002\III | HHH\002 | III |
| HHH\002\IV | HHH\002 | IV |
| HHH\002\V | HHH\002 | V |
| HHH\003\I | HHH\003 | I |
| HHH\003\II | HHH\003 | II |
| HHH\003\III | HHH\003 | III |
| HHH\003\IV | HHH\003 | IV |
| HHH\003\V | HHH\003 | V |
| HHH\004\I | HHH\004 | I |
| HHH\004\II | HHH\004 | II |
| HHH\004\III | HHH\004 | III |
| HHH\004\IV | HHH\004 | IV |
| HHH\004\V | HHH\004 | V |
| HHH\005\I | HHH\005 | I |
| HHH\005\II | HHH\005 | II |
| HHH\005\III | HHH\005 | III |
| HHH\005\IV | HHH\005 | IV |
| HHH\005\V | HHH\005 | V |
| HHH\006\I | HHH\006 | I |
| HHH\007\I | HHH\007 | I |
| HHH\007\II | HHH\007 | II |
| HHH\007\III | HHH\007 | III |
| HHH\008\I | HHH\008 | I |
| HHH\008\II | HHH\008 | II |
| HHH\008\III | HHH\008 | III |
| HHH\008\IV | HHH\008 | IV |
| HHH\008\V | HHH\008 | V |
+-------------+----------+----------+
TOP(n)
que funcionara correctamente en los 2CROSS APPLY
s. No estoy seguro de lo que hice de manera diferente / incorrecta ya que me deshice de ese código una vez que algo más funcionó. Lo publicaré pronto, ahora que ha proporcionado esta actualización. Y he limpiado la mayoría de mis comentarios anteriores.