PostgreSQL
 sql >> Base de données >  >> RDS >> PostgreSQL

Comment sélectionner plus d'un enregistrement par jour ?

Je veux sélectionner au plus 3 enregistrements par jour à partir d'une plage de dates spécifique.

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;

Points majeurs

  • Utilisez la fonction de fenêtre row_number() . rank() ou dense_rank() serait erroné selon la question - plus de 3 enregistrements pourraient être sélectionnés avec des doublons d'horodatage.

  • Puisque vous ne définissez pas quel lignes que vous voulez par jour, la bonne réponse est de ne pas inclure un ORDER BY clause dans la fonction de fenêtre. Vous donne une sélection arbitraire, qui correspond à la question.

  • J'ai changé votre WHERE clause de

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

    à

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

    Votre syntaxe échouerait pour les cas extrêmes comme '20121130 23:59:59.123' .

    Ce que @Craig a suggéré :

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

    .. fonctionnerait correctement, mais est un anti-modèle concernant les performances. Si vous appliquez un cast ou une fonction à votre colonne de base de données dans l'expression, les index simples ne peuvent pas être utilisés.

Solution pour PostgreSQL 8.3

Meilleure solution :Mettez à niveau vers une version plus récente, de préférence vers la version actuelle 9.2.

Autres solutions :

Pour seulement quelques jours vous pouvez utiliser 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
)
...

Les parenthèses ne sont pas facultatives ici.

Pendant plus de jours il existe des solutions de contournement avec generate_series() - quelque chose comme j'ai posté ici (y compris un lien vers plus).

J'aurais peut-être résolu le problème avec une fonction plpgsql à l'époque avant que nous ayons des fonctions de fenêtre :

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';

Appel :

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