PostgreSQL
 sql >> Datenbank >  >> RDS >> PostgreSQL

Wie wähle ich mehr als 1 Datensatz pro Tag aus?

Ich möchte höchstens 3 Datensätze auswählen pro Tag ab einem bestimmten Datumsbereich.

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;

Wichtige Punkte

  • Verwenden Sie die Fensterfunktion row_number() . rank() oder dense_rank() wäre gemäß der Frage falsch - es könnten mehr als 3 Datensätze mit Zeitstempelduplikaten ausgewählt werden.

  • Da Sie welche nicht definieren Zeilen, die Sie pro Tag wünschen, ist die richtige Antwort, kein ORDER BY einzuschließen -Klausel in der Fensterfunktion. Gibt Ihnen eine beliebige Auswahl, die der Frage entspricht.

  • Ich habe Ihr WHERE geändert Klausel von

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

    zu

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

    Ihre Syntax würde für Eckfälle wie '20121130 23:59:59.123' fehlschlagen .

    Was @Craig vorgeschlagen hat:

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

    .. würde korrekt funktionieren, ist aber ein Anti-Pattern in Bezug auf die Leistung. Wenn Sie im Ausdruck eine Umwandlung oder eine Funktion auf Ihre Datenbankspalte anwenden, können keine einfachen Indizes verwendet werden.

Lösung für PostgreSQL 8.3

Beste Lösung :Upgrade auf eine neuere Version, vorzugsweise auf die aktuelle Version 9.2.

Andere Lösungen :

Nur für wenige Tage Sie könnten UNION ALL verwenden :

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
)
...

Klammern sind hier nicht optional.

Für mehr Tage es gibt Problemumgehungen mit generate_series() - etwas, wie ich es hier gepostet habe (einschließlich eines Links zu mehr).

Ich hätte es vielleicht mit einer plpgsql-Funktion gelöst zurück in die alten Tage, bevor wir Fensterfunktionen hatten:

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

Aufruf:

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