¿Conoces una manera fácil de generar un registro para cada hora de las últimas 12 horas?

12

Tengo un informe que muestra el recuento de eventos de las últimas 12 horas, agrupados por hora. Suena bastante fácil, pero con lo que estoy luchando es cómo incluir registros que cubran las brechas.

Aquí hay una tabla de ejemplo:

Event
(
  EventTime datetime,
  EventType int
)

Los datos se ven así:

  '2012-03-08 08:00:04', 1
  '2012-03-08 09:10:00', 2
  '2012-03-08 09:11:04', 2
  '2012-03-08 09:10:09', 1
  '2012-03-08 10:00:17', 4
  '2012-03-08 11:00:04', 1

Necesito crear un conjunto de resultados que tenga un registro por cada hora de las últimas 12 horas, independientemente de que haya eventos durante esa hora o no.

Suponiendo que la hora actual es '2012-03-08 11:00:00', el informe mostraría (aproximadamente):

Hour  EventCount
----  ----------
23    0
0     0
1     0
2     0
3     0
4     0
5     0
6     0
7     0
8     1
9     3
10    1

Se me ocurrió una solución que utiliza una tabla que tiene un registro para cada hora del día. Logré obtener los resultados que estaba buscando usando UNION y alguna lógica de caso complicada en la cláusula where, pero esperaba que alguien tuviera una solución más elegante.

datagod
fuente

Respuestas:

21

Para SQL Server 2005+, puede generar esos 12 registros muy fácilmente con un bucle o un CTE recursivo. Aquí hay un ejemplo de un CTE recursivo:

DECLARE @Date DATETIME
SELECT @Date = '20120308 11:00:00'

;WITH Dates AS
(
    SELECT DATEPART(HOUR,DATEADD(HOUR,-1,@Date)) [Hour], 
      DATEADD(HOUR,-1,@Date) [Date], 1 Num
    UNION ALL
    SELECT DATEPART(HOUR,DATEADD(HOUR,-1,[Date])), 
      DATEADD(HOUR,-1,[Date]), Num+1
    FROM Dates
    WHERE Num <= 11
)
SELECT [Hour], [Date]
FROM Dates

Entonces solo necesitas unirte a ella con tu tabla de eventos.

Lamak
fuente
2
Encontré esto justo después de que publicaste. explainextended.com/2009/10/21/… Indica que usar un CTE para este propósito es menos eficiente que una tabla almacenada. ¿Es esto cierto? Como dijo Nick, probablemente no importa para este caso, pero ...
Leigh Riffel
44
Creo que marcaría la diferencia con un mayor número de filas, si necesita 12 registros, entonces no habrá ningún impacto en el rendimiento
Lamak
Lamak y @swasheck. Je ... llegué un poco tarde (perdí el rastro de este hilo) para llegar a él, pero no hay problema. Vea la respuesta que finalmente publiqué para respaldar mis afirmaciones anteriores. Y recuerde, todo el código tiene un efecto acumulativo. Si todos los códigos que la gente escribiera fueran "solo" 16 veces más rápidos, la mitad de las publicaciones en foros como este ya no serían necesarias. Y, no lleva más tiempo (a veces más corto) escribir código más rápido.
Jeff Moden
10

Las tablas de conteo se pueden usar para cosas como esta. Pueden ser muy eficientes. Crea la tabla de conteo a continuación. Creé la tabla de conteo con solo 24 filas para su ejemplo, pero puede crearla con la cantidad que desee para otros fines.

SELECT TOP 24 
        IDENTITY(INT,1,1) AS N
   INTO dbo.Tally
   FROM Master.dbo.SysColumns sc1,
        Master.dbo.SysColumns sc2

--===== Add a Primary Key to maximize performance
  ALTER TABLE dbo.Tally
    ADD CONSTRAINT PK_Tally_N 
        PRIMARY KEY CLUSTERED (N) WITH FILLFACTOR = 100

Supuse que su tabla se llamaba dbo.tblEvents, ejecute la consulta a continuación. Creo que esto es lo que estás buscando:

SELECT t.n, count(e.EventTime)
FROM dbo.Tally t
LEFT JOIN dbo.tblEvent e  on t.n = datepart(hh, e.EventTime)
GROUP BY t.n
ORDER BY t.n

Creo que el crédito va a los siguientes enlaces, creo que aquí es donde me encontré con esto por primera vez:

http://www.sqlservercentral.com/articles/T-SQL/62867/

http://www.sqlservercentral.com/articles/T-SQL/74118/

Henry Lee
fuente
+1 pero semánticamente es una tabla de números, no una tabla de cuentas.
Aaron Bertrand
1
Una de las definiciones de "Tally" es "Contar". La "Tabla de conteo" lleva el nombre del "Tally Stick", que es un palo largo y delgado que se usa para contar.
Jeff Moden
7

Primero, mis disculpas por el retraso en mi respuesta desde mis últimos comentarios.

El tema surgió en los comentarios de que el uso de un CTE recursivo (rCTE de aquí en adelante) se ejecuta lo suficientemente rápido debido al bajo número de filas. Si bien puede parecer así, nada podría estar más lejos de la verdad.

CONSTRUYE TALLY TABLE Y TALLY FUNCTION

Antes de comenzar a probar, necesitamos construir una tabla de conteo física con el índice agrupado apropiado y una función de conteo estilo Itzik Ben-Gan. También haremos todo esto en TempDB para no dejar caer accidentalmente las golosinas de nadie.

Aquí está el código para construir la tabla de conteo y mi versión de producción actual del maravilloso código de Itzik.

--===== Do this in a nice, safe place that everyone has
    USE tempdb
;
--===== Create/Recreate a Physical Tally Table
     IF OBJECT_ID('dbo.Tally','U') IS NOT NULL
        DROP TABLE dbo.Tally
;
     -- Note that the ISNULL makes a NOT NULL column
 SELECT TOP 1000001
        N = ISNULL(ROW_NUMBER() OVER (ORDER BY (SELECT NULL))-1,0)
   INTO dbo.Tally
   FROM      sys.all_columns ac1
  CROSS JOIN sys.all_columns ac2
;
  ALTER TABLE dbo.Tally
    ADD CONSTRAINT PK_Tally PRIMARY KEY CLUSTERED (N)
;
--===== Create/Recreate a Tally Function
     IF OBJECT_ID('dbo.fnTally','IF') IS NOT NULL
        DROP FUNCTION dbo.fnTally
;
GO
 CREATE FUNCTION [dbo].[fnTally]
/**********************************************************************************************************************
 Purpose:
 Return a column of BIGINTs from @ZeroOrOne up to and including @MaxN with a max value of 1 Trillion.

 As a performance note, it takes about 00:02:10 (hh:mm:ss) to generate 1 Billion numbers to a throw-away variable.

 Usage:
--===== Syntax example (Returns BIGINT)
 SELECT t.N
   FROM dbo.fnTally(@ZeroOrOne,@MaxN) t
;

 Notes:
 1. Based on Itzik Ben-Gan's cascading CTE (cCTE) method for creating a "readless" Tally Table source of BIGINTs.
    Refer to the following URLs for how it works and introduction for how it replaces certain loops. 
    http://www.sqlservercentral.com/articles/T-SQL/62867/
    http://sqlmag.com/sql-server/virtual-auxiliary-table-numbers
 2. To start a sequence at 0, @ZeroOrOne must be 0 or NULL. Any other value that's convertable to the BIT data-type
    will cause the sequence to start at 1.
 3. If @ZeroOrOne = 1 and @MaxN = 0, no rows will be returned.
 5. If @MaxN is negative or NULL, a "TOP" error will be returned.
 6. @MaxN must be a positive number from >= the value of @ZeroOrOne up to and including 1 Billion. If a larger
    number is used, the function will silently truncate after 1 Billion. If you actually need a sequence with
    that many values, you should consider using a different tool. ;-)
 7. There will be a substantial reduction in performance if "N" is sorted in descending order.  If a descending 
    sort is required, use code similar to the following. Performance will decrease by about 27% but it's still
    very fast especially compared with just doing a simple descending sort on "N", which is about 20 times slower.
    If @ZeroOrOne is a 0, in this case, remove the "+1" from the code.

    DECLARE @MaxN BIGINT; 
     SELECT @MaxN = 1000;
     SELECT DescendingN = @MaxN-N+1 
       FROM dbo.fnTally(1,@MaxN);

 8. There is no performance penalty for sorting "N" in ascending order because the output is explicity sorted by
    ROW_NUMBER() OVER (ORDER BY (SELECT NULL))

 Revision History:
 Rev 00 - Unknown     - Jeff Moden 
        - Initial creation with error handling for @MaxN.
 Rev 01 - 09 Feb 2013 - Jeff Moden 
        - Modified to start at 0 or 1.
 Rev 02 - 16 May 2013 - Jeff Moden 
        - Removed error handling for @MaxN because of exceptional cases.
 Rev 03 - 22 Apr 2015 - Jeff Moden
        - Modify to handle 1 Trillion rows for experimental purposes.
**********************************************************************************************************************/
        (@ZeroOrOne BIT, @MaxN BIGINT)
RETURNS TABLE WITH SCHEMABINDING AS 
 RETURN WITH
  E1(N) AS (SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL 
            SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL 
            SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL 
            SELECT 1)                                  --10E1 or 10 rows
, E4(N) AS (SELECT 1 FROM E1 a, E1 b, E1 c, E1 d)      --10E4 or 10 Thousand rows
,E12(N) AS (SELECT 1 FROM E4 a, E4 b, E4 c)            --10E12 or 1 Trillion rows                 
            SELECT N = 0 WHERE ISNULL(@ZeroOrOne,0)= 0 --Conditionally start at 0.
             UNION ALL 
            SELECT TOP(@MaxN) N = ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) FROM E12 -- Values from 1 to @MaxN
;
GO

Por cierto ... observe que construyó una tabla de conteo de un millón y una fila y le agregó un índice agrupado en aproximadamente un segundo más o menos. ¡Intente ESO con un rCTE y vea cuánto tarda! ;-)

CONSTRUIR ALGUNOS DATOS DE PRUEBA

También necesitamos algunos datos de prueba. Sí, estoy de acuerdo en que todas las funciones que vamos a probar, incluido el rCTE, se ejecutan en un milisegundo o menos durante solo 12 filas, pero esa es la trampa en la que muchas personas caen. Hablaremos más sobre esa trampa más tarde, pero, por ahora, simulemos llamar a cada función 40,000 veces, que es aproximadamente cuántas veces se llaman ciertas funciones en mi tienda en un día de 8 horas. Imagínense cuántas veces se podrían llamar tales funciones en un gran negocio minorista en línea.

Entonces, aquí está el código para construir 40,000 filas con fechas aleatorias, cada una con un Número de fila solo para fines de seguimiento. No me tomé el tiempo para hacer las horas enteras porque no importa aquí.

--===== Do this in a nice, safe place that everyone has
    USE tempdb
;
--===== Create/Recreate a Test Date table
     IF OBJECT_ID('dbo.TestDate','U') IS NOT NULL
        DROP TABLE dbo.TestDate
;
DECLARE  @StartDate DATETIME
        ,@EndDate   DATETIME
        ,@Rows      INT
;
 SELECT  @StartDate = '2010' --Inclusive
        ,@EndDate   = '2020' --Exclusive
        ,@Rows      = 40000  --Enough to simulate an 8 hour day where I work
;
 SELECT  RowNum       = IDENTITY(INT,1,1)
        ,SomeDateTime = RAND(CHECKSUM(NEWID()))*DATEDIFF(dd,@StartDate,@EndDate)+@StartDate
   INTO dbo.TestDate
   FROM dbo.fnTally(1,@Rows)
;

CONSTRUYA ALGUNAS FUNCIONES PARA HACER LA COSA DE 12 HORAS DE FILA

A continuación, convertí el código rCTE en una función y creé otras 3 funciones. Todos han sido creados como iTVF de alto rendimiento (funciones de valor de tabla en línea). Siempre se puede saber porque los iTVF nunca tienen un COMIENZO en ellos como Scalar o mTVF (funciones con valores de tabla de múltiples declaraciones).

Aquí está el código para construir esas 4 funciones ... Las nombré por el método que usan y no por lo que hacen solo para que sea más fácil identificarlas.

--=====  CREATE THE iTVFs
--===== Do this in a nice, safe place that everyone has
    USE tempdb
;
-----------------------------------------------------------------------------------------
     IF OBJECT_ID('dbo.OriginalrCTE','IF') IS NOT NULL
        DROP FUNCTION dbo.OriginalrCTE
;
GO
 CREATE FUNCTION dbo.OriginalrCTE
        (@Date DATETIME)
RETURNS TABLE WITH SCHEMABINDING AS
 RETURN
WITH Dates AS
(
    SELECT DATEPART(HOUR,DATEADD(HOUR,-1,@Date)) [Hour], 
      DATEADD(HOUR,-1,@Date) [Date], 1 Num
    UNION ALL
    SELECT DATEPART(HOUR,DATEADD(HOUR,-1,[Date])), 
      DATEADD(HOUR,-1,[Date]), Num+1
    FROM Dates
    WHERE Num <= 11
)
SELECT [Hour], [Date]
FROM Dates
GO
-----------------------------------------------------------------------------------------
     IF OBJECT_ID('dbo.MicroTally','IF') IS NOT NULL
        DROP FUNCTION dbo.MicroTally
;
GO
 CREATE FUNCTION dbo.MicroTally
        (@Date DATETIME)
RETURNS TABLE WITH SCHEMABINDING AS
 RETURN
 SELECT  [Hour] = DATEPART(HOUR,DATEADD(HOUR,t.N,@Date))
        ,[DATE] = DATEADD(HOUR,t.N,@Date)
   FROM (VALUES (-1),(-2),(-3),(-4),(-5),(-6),(-7),(-8),(-9),(-10),(-11),(-12))t(N)
;
GO
-----------------------------------------------------------------------------------------
     IF OBJECT_ID('dbo.PhysicalTally','IF') IS NOT NULL
        DROP FUNCTION dbo.PhysicalTally
;
GO
 CREATE FUNCTION dbo.PhysicalTally
        (@Date DATETIME)
RETURNS TABLE WITH SCHEMABINDING AS
 RETURN
 SELECT  [Hour] = DATEPART(HOUR,DATEADD(HOUR,-t.N,@Date))
        ,[DATE] = DATEADD(HOUR,-t.N,@Date)
   FROM dbo.Tally t
  WHERE N BETWEEN 1 AND 12
;
GO
-----------------------------------------------------------------------------------------
     IF OBJECT_ID('dbo.TallyFunction','IF') IS NOT NULL
        DROP FUNCTION dbo.TallyFunction
;
GO
 CREATE FUNCTION dbo.TallyFunction
        (@Date DATETIME)
RETURNS TABLE WITH SCHEMABINDING AS
 RETURN
 SELECT  [Hour] = DATEPART(HOUR,DATEADD(HOUR,-t.N,@Date))
        ,[DATE] = DATEADD(HOUR,-t.N,@Date)
   FROM dbo.fnTally(1,12) t
;
GO

CONSTRUIR EL ARNÉS DE PRUEBA PARA PROBAR LAS FUNCIONES

Por último, pero no menos importante, necesitamos un arnés de prueba. Hago una verificación de línea de base y luego pruebo cada función de manera idéntica.

Aquí está el código para el arnés de prueba ...

PRINT '--========== Baseline Select =================================';
DECLARE @Hour INT, @Date DATETIME
;
    SET STATISTICS TIME,IO ON;
 SELECT  @Hour = RowNum
        ,@Date = SomeDateTime
   FROM dbo.TestDate
  CROSS APPLY dbo.fnTally(1,12);
    SET STATISTICS TIME,IO OFF;
GO
PRINT '--========== Orginal Recursive CTE ===========================';
DECLARE @Hour INT, @Date DATETIME
;

    SET STATISTICS TIME,IO ON;
 SELECT  @Hour = fn.[Hour]
        ,@Date = fn.[Date]
   FROM dbo.TestDate td
  CROSS APPLY dbo.OriginalrCTE(td.SomeDateTime) fn;
    SET STATISTICS TIME,IO OFF;
GO
PRINT '--========== Dedicated Micro-Tally Table =====================';
DECLARE @Hour INT, @Date DATETIME
;

    SET STATISTICS TIME,IO ON;
 SELECT  @Hour = fn.[Hour]
        ,@Date = fn.[Date]
   FROM dbo.TestDate td
  CROSS APPLY dbo.MicroTally(td.SomeDateTime) fn;
    SET STATISTICS TIME,IO OFF;
GO
PRINT'--========== Physical Tally Table =============================';
DECLARE @Hour INT, @Date DATETIME
;
    SET STATISTICS TIME,IO ON;
 SELECT  @Hour = fn.[Hour]
        ,@Date = fn.[Date]
   FROM dbo.TestDate td
  CROSS APPLY dbo.PhysicalTally(td.SomeDateTime) fn;
    SET STATISTICS TIME,IO OFF;
GO
PRINT'--========== Tally Function ===================================';
DECLARE @Hour INT, @Date DATETIME
;
    SET STATISTICS TIME,IO ON;
 SELECT  @Hour = fn.[Hour]
        ,@Date = fn.[Date]
   FROM dbo.TestDate td
  CROSS APPLY dbo.TallyFunction(td.SomeDateTime) fn;
    SET STATISTICS TIME,IO OFF;
GO

Una cosa a tener en cuenta en el arnés de prueba anterior es que desvío toda la salida a variables "desechables". Eso es para tratar de mantener las mediciones de rendimiento tan puras como sea posible sin ninguna salida al disco o resultados de inclinación de la pantalla.

UNA PALABRA DE PRECAUCIÓN SOBRE LAS ESTADÍSTICAS FIJAS

Además, una advertencia para los posibles evaluadores ... NO DEBE usar SET STATISTICS cuando pruebe las funciones Scalar o mTVF. Solo se puede usar de forma segura en funciones iTVF como las de esta prueba. Se ha demostrado que SET STATISTICS hace que las funciones SCALAR se ejecuten cientos de veces más lento de lo que realmente lo hacen sin él. Sí, estoy tratando de inclinar otro molino de viento, pero eso sería una publicación más larga y no tengo tiempo para eso. Tengo un artículo en SQLServerCentral.com que habla sobre eso, pero no tiene sentido publicar el enlace aquí porque alguien se volverá loco al respecto.

LOS RESULTADOS DE LA PRUEBA

Entonces, aquí están los resultados de la prueba cuando ejecuto el arnés de prueba en mi pequeña computadora portátil i5 con 6GB de RAM.

--========== Baseline Select =================================
Table 'Worktable'. Scan count 1, logical reads 82309, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
Table 'TestDate'. Scan count 1, logical reads 105, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.

 SQL Server Execution Times:
   CPU time = 203 ms,  elapsed time = 206 ms.
--========== Orginal Recursive CTE ===========================
Table 'Worktable'. Scan count 40001, logical reads 2960000, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
Table 'TestDate'. Scan count 1, logical reads 105, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.

 SQL Server Execution Times:
   CPU time = 4258 ms,  elapsed time = 4415 ms.
--========== Dedicated Micro-Tally Table =====================
Table 'Worktable'. Scan count 1, logical reads 81989, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
Table 'TestDate'. Scan count 1, logical reads 105, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.

 SQL Server Execution Times:
   CPU time = 234 ms,  elapsed time = 235 ms.
--========== Physical Tally Table =============================
Table 'Worktable'. Scan count 1, logical reads 81989, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
Table 'TestDate'. Scan count 1, logical reads 105, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
Table 'Tally'. Scan count 1, logical reads 3, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.

 SQL Server Execution Times:
   CPU time = 250 ms,  elapsed time = 252 ms.
--========== Tally Function ===================================
Table 'Worktable'. Scan count 1, logical reads 81989, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
Table 'TestDate'. Scan count 1, logical reads 105, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.

 SQL Server Execution Times:
   CPU time = 250 ms,  elapsed time = 253 ms.

El "SELECCIÓN DE BASE", que solo selecciona datos (cada fila creada 12 veces para simular el mismo volumen de retorno), llegó a la derecha aproximadamente 1/5 de segundo. Todo lo demás llegó aproximadamente a un cuarto de segundo. Bueno, todo excepto esa maldita función rCTE. Tomó 4 y 1/4 segundos o 16 veces más (1,600% más lento).

Y mire las lecturas lógicas (memoria IO) ... El rCTE consumió la friolera de 2,960,000 (casi 3 MILLONES de lecturas) mientras que las otras funciones solo consumieron aproximadamente 82,100. Eso significa que el rCTE consumió más de 34.3 veces más memoria IO que cualquiera de las otras funciones.

PENSAMIENTOS DE CIERRE

Resumamos El método rCTE para hacer esta cosa "pequeña" de 12 filas usó 16 VECES (1,600%) más CPU (y duración) y 34,3 VECES (3,430%) más memoria IO que cualquiera de las otras funciones.

Je ... Sé lo que estás pensando. "¡Gran cosa! Es solo una función".

Sí, de acuerdo, pero ¿cuántas otras funciones tienes? ¿Cuántos otros lugares fuera de las funciones tiene? ¿Y tiene alguno de esos que funcionan con más de 12 filas cada ejecución? Y, ¿hay alguna posibilidad de que alguien en una sacudida por un método pueda copiar ese código rCTE para algo mucho más grande?

Ok, es hora de ser franco. No tiene ningún sentido que las personas justifiquen el código de rendimiento desafiado solo por el supuesto recuento o uso de filas limitadas. Excepto cuando compra una caja MPP por quizás millones de dólares (sin mencionar el costo de reescribir el código para que funcione en una máquina de este tipo), no puede comprar una máquina que ejecute su código 16 veces más rápido (SSD ganó tampoco lo hagas ... todo esto estaba en la memoria de alta velocidad cuando lo probamos). El rendimiento está en el código. El buen rendimiento está en buen código.

¿Te imaginas si todo tu código se ejecutó "solo" 16 veces más rápido?

Nunca justifique código malo o de rendimiento desafiado en recuentos bajos o incluso bajo uso. Si lo hace, es posible que tenga que pedir prestado uno de los molinos de viento que me acusaron de inclinar para mantener sus CPU y discos lo suficientemente frescos. ;-)

UNA PALABRA EN LA PALABRA "TALLY"

Si estoy de acuerdo. Hablando semánticamente, la tabla de conteo contiene números, no "recuentos". En mi artículo original sobre el tema (no era el artículo original sobre la técnica, pero fue el primero), lo llamé "Tally" no por lo que contiene, sino por lo que hace ... es solía "contar" en lugar de repetir y "contar" algo es "contar" algo. ;-) Llámalo como quieras ... Tabla de números, Tabla de conteo, Tabla de secuencia, lo que sea. No me importa Para mí, "Tally" tiene más significado completo y, al ser un buen DBA perezoso, contiene solo 5 letras (2 son idénticas) en lugar de 7 y es más fácil de decir para la mayoría de las personas. También es "singular", que sigue mi convención de nomenclatura para tablas. ;-) Eso' También se llama el artículo que contenía una página de un libro de los años 60. Siempre me referiré a ella como una "Tabla de conteo" y aún sabrá lo que yo u otra persona queremos decir. También evito la notación húngara como la peste, pero llamé a la función "fnTally" para poder decir "Bueno, si usaras la función de conteo ef-en que te mostré, no tendrías un problema de rendimiento" sin que realmente sea Violación de recursos humanos. ;-) sin que en realidad sea una violación de recursos humanos. ;-) sin que en realidad sea una violación de recursos humanos. ;-)

Lo que más me preocupa es que las personas aprendan a usarlo correctamente en lugar de recurrir a cosas como los rCTE con problemas de rendimiento y otras formas de RBAR oculto.

Jeff Moden
fuente
2

Necesitará RIGHT JOINsus datos con una consulta que devuelva un registro por cada hora que necesite.

Vea esto para ver un par de formas de obtener números de fila que luego podría restar como horas de la hora actual.

En Oracle, una consulta jerárquica en dual generará filas:

SELECT to_char(sysdate-level/24,'HH24') FROM dual CONNECT BY Level <=24;
Leigh Riffel
fuente
Es la "consulta que devuelve un registro por cada hora" con la que tengo problemas. Solo trato de encontrar una manera de generar 12 (o 24) registros con cada hora de las últimas 12 (o 24) horas.
datagod