SQL SELECT Convertir Min/Max en filas separadas

SQL SELECT Convertir Min/Max en filas separadas

Tengo una tabla que tiene un valor mínimo y máximo que me gustaría crear una fila para cada número válido en una instrucción SELECT.

Tabla original:

| Foobar_ID | Min_Period | Max_Period |
---------------------------------------
| 1         | 0          | 2          |
| 2         | 1          | 4          |

Me gustaría convertir eso en:

| Foobar_ID | Period_Num |
--------------------------
| 1         | 0          |
| 1         | 1          |
| 1         | 2          |
| 2         | 1          |
| 2         | 2          |
| 2         | 3          |
| 2         | 4          |

Los resultados de SELECT deben aparecer como un conjunto de resultados, por lo que no estoy seguro de si un bucle WHILE funcionaría en mi caso.

Mostrar la mejor respuesta

¿Tienes una tabla de hechos de números?

Únete a una tabla con números: ON n between Min_Period and Max_Period

Avatar Ven

La pregunta es clara y no es necesario votar negativamente, +1

Si espera solo un puñado de filas por foobar, entonces esta es una buena oportunidad para aprender sobre CTE recursivos:

with cte as (
      select foobar_id, min_period as period_num, max_period
      from original t
      union all
      select foobar_id, min_period + 1 as period_num, max_period
      from cte
      where period_num < max_period
    )
select foobar_id, period_num
from cte
order by foobar_id, period_num;

Puede extender esto a cualquier cantidad de períodos configurando la opción MAXRECURSION en 0.

¿Puedes editar los nombres de las tablas? El nombre de CTE es t pero está seleccionando de cte. Sería genial si también pudiera agregar la opción MAXRECURSION para que el OP sepa dónde editar.

Mi rango de períodos es de 0 a 11, así que supongo que simplemente repetir la instrucción SELECT funcionaría, pero esperaba una solución más dinámica. Trabajaré en este enfoque por ahora. Gracias

Esta respuesta es la que necesita @Panman, es un CTE recursivo que generará todos los registros de una sola vez.

Sí, CTE recursivo funciona muy bien. Después de revisar esta sugerencia más de cerca, conseguí que funcionara según lo previsto. ¡Gracias de nuevo!

Un método sería usar una tabla Tally, hay muchos ejemplos, pero voy a crear uno muy pequeño en este ejemplo. Luego puede JOIN sobre eso y devolver su conjunto de resultados.

--Create the Tally Table
CREATE TABLE #Tally (I int);

WITH ints AS(
    SELECT 0 AS i
    UNION ALL
    SELECT i + 1
    FROM ints
    WHERE i + 1 <= 10)
--And in the numbers go!
INSERT INTO #Tally
SELECT i
FROM ints;
GO

--Create the sample table
CREATE TABLE #Sample (ID int IDENTITY(1,1),
                      MinP int,
                      MaxP int);

--Sample data    
INSERT INTO #Sample (Minp, MaxP)
VALUES (0,2),
       (1,4);
GO

--And the solution
SELECT S.ID,
       T.I AS P
FROM #Sample S
     JOIN #Tally T ON T.I BETWEEN S.MinP AND S.MaxP
ORDER BY S.ID, T.I;
GO

--Clean up    
DROP TABLE #Sample;
DROP TABLE #Tally;

Gracias por la sugerencia, después de revisar los "CTE recursivos", pude hacer todo en un CTE.

Dependiendo del tamaño de los datos y el rango del período, la forma más fácil de hacer esto es usar una tabla de hechos de números dinámicos, de la siguiente manera:

WITH rn AS (SELECT ROW_NUMBER() OVER (ORDER BY object_id) -1 as period_num FROM sys.objects)
SELECT f.foobar_id, rn.period_num
FROM foobar f
    INNER JOIN rn ON rn.period_num BETWEEN f.min_period AND f.max_period

Sin embargo, si está trabajando con un mayor volumen de datos, valdrá la pena crear una tabla de hechos numéricos con un índice. Incluso puedes usar un TVV para esto:

-- Declare the number fact table
DECLARE @rn TABLE (period_num INT IDENTITY(0, 1) primary key, dummy int)
-- Populate the fact table so that all periods are covered
WHILE (SELECT COUNT(1) FROM @rn) < (SELECT MAX(max_period) FROM foobar)
    INSERT @rn select 1 from sys.objects

-- Select using a join to the fact table
SELECT f.foo_id, rn.period_num
FROM foobar f
    inner join @rn rn on rn.period_num between f.min_period and f.max_period

Simplemente cree una fecha de muestra de función y utilícela

CREATE FUNCTION [dbo].[Ufn_GetMInToMaxVal] (@Min_Period INT,@Max_Period INT  )
RETURNS  @OutTable TABLE
(
DATA INT
)
AS
BEGIN

;WIth cte
AS
(
SELECT @Min_Period As Min_Period
UNION ALL
SELECT Min_Period+1 FRom
cte 
WHERE Min_Period <  @Max_Period
)
INSERT INTO @OutTable
SELECT * FROM cte
RETURN
END

Obtenga el resultado ejecutando la instrucción sql

DECLARE @Temp AS TABLE(
        Foobar_ID INT,
        Min_Period INT,
        Max_Period INT
        )
INSERT INTO @Temp

SELECT  1, 0,2 UNION ALL
SELECT  2, 1,4

SELECT Foobar_ID ,
        DATA 
FROM @Temp
CROSS APPLY 
 [dbo].[Ufn_GetMInToMaxVal] (Min_Period,Max_Period)

Resultado

 Foobar_ID  DATA
 ----------------
    1        0
    1        1
    1        2
    2        1
    2        2
    2        3
    2        4