PostgreSQL
 sql >> Base de Dados >  >> RDS >> PostgreSQL

Como selecionar mais de 1 registro por dia?


Quero selecionar no máximo 3 registros por dia a partir de um intervalo de datas 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;

Pontos principais


  • Use a função de janela row_number() . rank() ou dense_rank() estaria errado de acordo com a pergunta - mais de 3 registros podem ser selecionados com duplicatas de timestamp.

  • Como você não define qual linhas que você deseja por dia, a resposta correta é não incluir um ORDER BY cláusula na função de janela. Dá-lhe uma seleção arbitrária, que corresponde à pergunta.

  • Mudei seu WHERE cláusula de
    WHERE  date_time >= '20121101 00:00:00'  
    AND    date_time <= '20121130 23:59:59'
    

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

    Sua sintaxe falharia para casos de canto como '20121130 23:59:59.123' .

    O que @Craig sugeriu:
    date_time::date BETWEEN '2012-11-02' AND '2012-11-05'
    

    .. funcionaria corretamente, mas é um antipadrão em relação ao desempenho. Se você aplicar uma conversão ou uma função à coluna do banco de dados na expressão, os índices simples não poderão ser usados.

Solução para PostgreSQL 8.3


Melhor solução :Atualize para uma versão mais recente, preferencialmente para a versão atual 9.2.

Outras soluções :

Por apenas alguns dias você pode empregar 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
)
...

Os parênteses não são opcionais aqui.

Por mais dias existem soluções alternativas com generate_series() - algo como eu postei aqui (incluindo um link para mais).

Eu poderia ter resolvido com uma função plpgsql nos velhos tempos, antes de termos funções de janela:
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';

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