sql >> Base de Datos >  >> RDS >> Sqlserver

Mientras se repite en SQL Server 2008 iterando a través de un rango de fechas y luego INSERTAR

SQL es un lenguaje basado en conjuntos y los bucles deben ser el último recurso. Por lo tanto, el enfoque basado en conjuntos sería generar primero todas las fechas que necesita e insertarlas de una sola vez, en lugar de repetir e insertar una a la vez. Aaron Bertrand ha escrito una gran serie sobre la generación de un conjunto o secuencia sin bucles:

La Parte 3 es específicamente relevante ya que trata de fechas.

Suponiendo que no tiene una tabla de calendario, puede usar el método CTE apilado para generar una lista de fechas entre las fechas de inicio y finalización.

DECLARE @StartDate DATE = '2015-01-01',
        @EndDate DATE = GETDATE();

WITH N1 (N) AS (SELECT 1 FROM (VALUES (1), (1), (1), (1), (1), (1), (1), (1), (1), (1)) n (N)),
N2 (N) AS (SELECT 1 FROM N1 AS N1 CROSS JOIN N1 AS N2),
N3 (N) AS (SELECT 1 FROM N2 AS N1 CROSS JOIN N2 AS N2)
SELECT TOP (DATEDIFF(DAY, @StartDate, @EndDate) + 1)
        Date = DATEADD(DAY, ROW_NUMBER() OVER(ORDER BY N) - 1, @StartDate)
FROM N3;

Omití algunos detalles sobre cómo funciona esto, ya que se cubre en el artículo vinculado, en esencia, comienza con una tabla codificada de 10 filas, luego se une a esta tabla para obtener 100 filas (10 x 10) y luego se une a esta tabla de 100 filas para obtener 10,000 filas (me detuve en este punto, pero si necesita más filas, puede agregar más combinaciones).

En cada paso, la salida es una sola columna llamada N con un valor de 1 (para simplificar las cosas). Al mismo tiempo que defino cómo generar 10 000 filas, en realidad le digo a SQL Server que solo genere el número necesario usando TOP y la diferencia entre su fecha de inicio y finalización - TOP(DATEDIFF(DAY, @StartDate, @EndDate) + 1) . Esto evita trabajos innecesarios. Tuve que sumar 1 a la diferencia para asegurarme de que ambas fechas estuvieran incluidas.

Usando la función de clasificación ROW_NUMBER() Agrego un número incremental a cada una de las filas generadas, luego agrego este número incremental a su fecha de inicio para obtener la lista de fechas. Desde ROW_NUMBER() comienza en 1, necesito deducir 1 de esto para asegurarme de que se incluya la fecha de inicio.

Entonces solo sería un caso de excluir fechas que ya existen usando NOT EXISTS . Adjunté los resultados de la consulta anterior en su propio CTE llamado dates :

DECLARE @StartDate DATE = '2015-01-01',
        @EndDate DATE = GETDATE();

WITH N1 (N) AS (SELECT 1 FROM (VALUES (1), (1), (1), (1), (1), (1), (1), (1), (1), (1)) n (N)),
N2 (N) AS (SELECT 1 FROM N1 AS N1 CROSS JOIN N1 AS N2),
N3 (N) AS (SELECT 1 FROM N2 AS N1 CROSS JOIN N2 AS N2),
Dates AS
(   SELECT TOP (DATEDIFF(DAY, @StartDate, @EndDate) + 1)
            Date = DATEADD(DAY, ROW_NUMBER() OVER(ORDER BY N) - 1, @StartDate)
    FROM N3
)
INSERT INTO MyTable ([TimeStamp])
SELECT  Date
FROM    Dates AS d
WHERE NOT EXISTS (SELECT 1 FROM MyTable AS t WHERE d.Date = t.[TimeStamp])

Ejemplo en SQL Fiddle

Si tuviera que crear una tabla de calendario (como se describe en los artículos vinculados), entonces puede que no sea necesario insertar estas filas adicionales, simplemente podría generar su conjunto de resultados sobre la marcha, algo como:

SELECT  [Timestamp] = c.Date,
        t.[FruitType],
        t.[NumOffered],
        t.[NumTaken],
        t.[NumAbandoned],
        t.[NumSpoiled]
FROM    dbo.Calendar AS c
        LEFT JOIN dbo.MyTable AS t
            ON t.[Timestamp] = c.[Date]
WHERE   c.Date >= @StartDate
AND     c.Date < @EndDate;

ANEXO

Para responder a su pregunta real, su ciclo se escribiría de la siguiente manera:

DECLARE @StartDate AS DATETIME
DECLARE @EndDate AS DATETIME
DECLARE @CurrentDate AS DATETIME

SET @StartDate = '2015-01-01'
SET @EndDate = GETDATE()
SET @CurrentDate = @StartDate

WHILE (@CurrentDate < @EndDate)
BEGIN
    IF NOT EXISTS (SELECT 1 FROM myTable WHERE myTable.Timestamp = @CurrentDate)
    BEGIN
        INSERT INTO MyTable ([Timestamp])
        VALUES (@CurrentDate);
    END

    SET @CurrentDate = DATEADD(DAY, 1, @CurrentDate); /*increment current date*/
END

Ejemplo en SQL Fiddle

No defiendo este enfoque, el hecho de que algo solo se haga una vez no significa que no deba demostrar la forma correcta de hacerlo.

EXPLICACIÓN ADICIONAL

Dado que el método CTE apilado puede haber complicado demasiado el enfoque basado en conjuntos, lo simplificaré utilizando la tabla de sistema no documentada master..spt_values . Si ejecuta:

SELECT Number
FROM master..spt_values
WHERE Type = 'P';

Verás que obtienes todos los números del 0 al 2047.

Ahora si ejecutas:

DECLARE @StartDate DATE = '2015-01-01',
        @EndDate DATE = GETDATE();


SELECT Date = DATEADD(DAY, number, @StartDate)
FROM master..spt_values
WHERE type = 'P';

Obtiene todas las fechas desde su fecha de inicio hasta 2047 días en el futuro. Si agrega una cláusula where adicional, puede limitar esto a fechas anteriores a la fecha de finalización:

DECLARE @StartDate DATE = '2015-01-01',
        @EndDate DATE = GETDATE();


SELECT Date = DATEADD(DAY, number, @StartDate)
FROM master..spt_values
WHERE type = 'P'
AND DATEADD(DAY, number, @StartDate) <= @EndDate;

Ahora que tiene todas las fechas que necesita en una sola consulta basada en conjuntos, puede eliminar las filas que ya existen en su tabla usando NOT EXISTS

DECLARE @StartDate DATE = '2015-01-01',
        @EndDate DATE = GETDATE();


SELECT Date = DATEADD(DAY, number, @StartDate)
FROM master..spt_values
WHERE type = 'P'
AND DATEADD(DAY, number, @StartDate) <= @EndDate
AND NOT EXISTS (SELECT 1 FROM MyTable AS t WHERE t.[Timestamp] = DATEADD(DAY, number, @StartDate));

Finalmente puedes insertar estas fechas en tu tabla usando INSERT

DECLARE @StartDate DATE = '2015-01-01',
        @EndDate DATE = GETDATE();

INSERT YourTable ([Timestamp])
SELECT Date = DATEADD(DAY, number, @StartDate)
FROM master..spt_values
WHERE type = 'P'
AND DATEADD(DAY, number, @StartDate) <= @EndDate
AND NOT EXISTS (SELECT 1 FROM MyTable AS t WHERE t.[Timestamp] = DATEADD(DAY, number, @StartDate));

Con suerte, esto demuestra de alguna manera que el enfoque basado en conjuntos no solo es mucho más eficiente, sino que también es más simple.