sql >> Base de Datos >  >> RDS >> PostgreSQL

¿Cómo seleccionar más de 1 registro por día?

Quiero seleccionar como máximo 3 registros por día de un intervalo de fechas específico.

SELECT date_time, other_column
FROM  (
   SELECT *, row_number() OVER (PARTITION BY date_time::date) AS rn
   FROM   tbl
   WHERE  date_time >= '2012-11-01 0:0'
   AND    date_time <  '2012-12-01 0:0'
   ) x
WHERE  rn < 4;

Puntos principales

  • Use la función de ventana row_number() . rank() o dense_rank() sería incorrecto de acuerdo con la pregunta:se pueden seleccionar más de 3 registros con duplicados de marca de tiempo.

  • Ya que no defines cuál filas que desea por día, la respuesta correcta es no incluir un ORDER BY cláusula en la función de ventana. Le da una selección arbitraria, que coincide con la pregunta.

  • Cambié tu WHERE cláusula de

    WHERE  date_time >= '20121101 00:00:00'  
    AND    date_time <= '20121130 23:59:59'
    

    a

    WHERE  date_time >=  '2012-11-01 0:0'  
    AND    date_time <   '2012-12-01 0:0'
    

    Su sintaxis fallaría para casos de esquina como '20121130 23:59:59.123' .

    Qué sugirió @Craig:

    date_time::date BETWEEN '2012-11-02' AND '2012-11-05'
    

    .. funcionaría correctamente, pero es un antipatrón en cuanto al rendimiento. Si aplica una conversión o una función a la columna de su base de datos en la expresión, no se pueden usar índices simples.

Solución para PostgreSQL 8.3

La mejor solución :Actualice a una versión más reciente, preferiblemente a la versión actual 9.2.

Otras soluciones :

Por solo pocos días podría emplear UNION ALL :

SELECT date_time, other_column
FROM   tbl t1
WHERE  date_time >= '2012-11-01 0:0'
AND    date_time <  '2012-11-02 0:0'
LIMIT  3
)
UNION ALL 
(
SELECT date_time, other_column
FROM   tbl t1
WHERE  date_time >= '2012-11-02 0:0'
AND    date_time <  '2012-11-03 0:0'
LIMIT  3
)
...

Los paréntesis no son opcionales aquí.

Por más días hay soluciones con generate_series() - algo como lo que publiqué aquí (incluyendo un enlace a más).

Podría haberlo resuelto con una función plpgsql en los viejos tiempos antes de que tuviéramos funciones de ventana:

CREATE OR REPLACE FUNCTION x.f_foo (date, date, integer
                         , OUT date_time timestamp, OUT other_column text)
  RETURNS SETOF record AS
$BODY$
DECLARE
    _last_day date;          -- remember last day
    _ct       integer := 1;  -- count
BEGIN

FOR date_time, other_column IN
   SELECT t.date_time, t.other_column
   FROM   tbl t
   WHERE  t.date_time >= $1::timestamp
   AND    t.date_time <  ($2 + 1)::timestamp
   ORDER  BY t.date_time::date
LOOP
   IF date_time::date = _last_day THEN
      _ct := _ct + 1;
   ELSE
      _ct := 1;
   END IF;

   IF _ct <= $3 THEN
      RETURN NEXT;
   END IF;

   _last_day := date_time::date;
END LOOP;

END;
$BODY$ LANGUAGE plpgsql STABLE STRICT;

COMMENT ON FUNCTION f_foo(date3, date, integer) IS 'Return n rows per day
$1 .. date_from (incl.)
$2 .. date_to  (incl.)
$3 .. maximim rows per day';

Llamar:

SELECT * FROM f_foo('2012-11-01', '2012-11-05', 3);