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

Obtenir la différence d'un autre champ entre le premier et le dernier horodatage du groupement

Étape 1 :desserrez les freins à main

SELECT to_char(MIN(ts)::timestamptz, 'YYYY-MM-DD HH24:MI:SS TZ') AS min_time
      ,SUM(CASE WHEN sensor_id = 572 THEN value ELSE 0.0 END) AS nickname1
      ,SUM(CASE WHEN sensor_id = 542 THEN value ELSE 0.0 END) AS nickname2
      ,SUM(CASE WHEN sensor_id = 571 THEN value ELSE 0.0 END) AS nickname3
FROM   sensor_values
-- LEFT JOIN sensor_values_cleaned s2 USING (sensor_id, ts)
WHERE  ts >= '2013-10-14T00:00:00+00:00'::timestamptz::timestamp
AND    ts <  '2013-10-18T00:00:00+00:00'::timestamptz::timestamp
AND    sensor_id IN (572, 542, 571, 540, 541, 573)
GROUP  BY ts::date AS day
ORDER  BY 1;

Points majeurs

  • Remplacer mots réservés (en SQL standard) dans vos identifiants.
    timestamp -> ts
    time -> min_time

  • Puisque la jointure est sur des noms de colonnes identiques, vous pouvez utiliser le plus simple USING clause dans la condition de jointure :USING (sensor_id, ts)
    Cependant, depuis la deuxième table sensor_values_cleaned est 100 % sans rapport avec cette requête, je l'ai entièrement supprimée.

  • Comme @joop l'a déjà conseillé, changez min() et to_char() dans votre première colonne de sortie. De cette façon, Postgres peut déterminer le minimum à partir de la valeur de la colonne d'origine , qui est généralement plus rapide et peut utiliser un index. Dans ce cas précis, commander par date est également moins cher que de commander par un text , qui devrait également tenir compte des règles de classement.

  • Une considération similaire s'applique à votre WHERE condition :
    WHERE ts::timestamptz>='2013-10-14T00:00:00+00:00'::timestamptz

    WHERE  ts >= '2013-10-14T00:00:00+00:00'::timestamptz::timestamp
    

    Le second est sargable et peut utiliser un index simple sur ts - au grand effet sur les performances dans les grandes tables !

  • Utilisation de ts::date au lieu de date_trunc('day', ts) . Plus simple, plus rapide, même résultat.

  • Très probablement, votre deuxième condition WHERE est légèrement incorrecte. Généralement, vous devez exclure la bordure supérieure :

    AND    ts <=  '2013-10-18T00:00:00+00:00' ...

    AND    ts <   '2013-10-18T00:00:00+00:00' ...
  • Lors du mélange de timestamp et timestamptz il faut être conscient des effets. Par exemple, votre WHERE condition ne coupe pas à 00:00 heure locale (sauf si l'heure locale coïncide avec UTC). Détails ici :
    Ignorer complètement les fuseaux horaires dans Rails et PostgreSQL

Étape 2 :Votre demande

Et par là, je suppose que vous voulez dire :
...la différence entre la valeur de les horodatages les plus récents et les plus anciens...
Sinon ce serait beaucoup plus simple.

Utilisez fonctions de fenêtre pour cela, en particulier first_value() et last_value() . Attention à la combinaison, vous voulez un non -cadre de fenêtre standard pour last_value() dans ce cas. Comparez :
Fonction d'agrégat ou de fenêtre PostgreSQL pour renvoyer uniquement la dernière valeur

Je combine cela avec DISTINCT ON , ce qui est plus pratique dans ce cas que GROUP BY (ce qui nécessiterait un autre niveau de sous-requête) :

SELECT DISTINCT ON (ts::date, sensor_id)
       ts::date AS day
      ,to_char((min(ts)  OVER (PARTITION BY ts::date))::timestamptz
              ,'YYYY-MM-DD HH24:MI:SS TZ') AS min_time
      ,sensor_id
      ,last_value(value)    OVER (PARTITION BY ts::date, sensor_id ORDER BY ts
                     RANGE BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING)
       - first_value(value) OVER (PARTITION BY ts::date, sensor_id ORDER BY ts)
                                                                   AS val_range
FROM   sensor_values
WHERE  ts >= '2013-10-14T00:00:00+0'::timestamptz::timestamp
AND    ts <  '2013-10-18T00:00:00+0'::timestamptz::timestamp
AND    sensor_id IN (540, 541, 542, 571, 572, 573)
ORDER  BY ts::date, sensor_id;

-> Démo SQLfiddle.

Étape 3 :tableau croisé dynamique

En m'appuyant sur la requête ci-dessus, j'utilise crosstab() depuis le module complémentaire tablefunc :

SELECT * FROM crosstab(
   $$SELECT DISTINCT ON (1,3)
            ts::date AS day
           ,to_char((min(ts) OVER (PARTITION BY ts::date))::timestamptz,'YYYY-MM-DD HH24:MI:SS TZ') AS min_time
           ,sensor_id
           ,last_value(value)    OVER (PARTITION BY ts::date, sensor_id ORDER BY ts RANGE BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING)
            - first_value(value) OVER (PARTITION BY ts::date, sensor_id ORDER BY ts) AS val_range
     FROM   sensor_values
     WHERE  ts >= '2013-10-14T00:00:00+0'::timestamptz::timestamp
     AND    ts <  '2013-10-18T00:00:00+0'::timestamptz::timestamp
     AND    sensor_id IN (540, 541, 542, 571, 572, 573)
     ORDER  BY 1, 3$$

   ,$$VALUES (540), (541), (542), (571), (572), (573)$$
   )
AS ct (day date, min_time text, s540 numeric, s541 numeric, s542 numeric, s571 numeric, s572 numeric, s573 numeric);

Renvoie (et beaucoup plus vite qu'avant):

    day     |         min_time         | s540  | s541  | s542  | s571  | s572  | s573
------------+--------------------------+-------+-------+-------+-------+-------+-------
 2013-10-14 | 2013-10-14 03:00:00 CEST | 18.82 | 18.98 | 19.97 | 19.47 | 17.56 | 21.27
 2013-10-15 | 2013-10-15 00:15:00 CEST | 22.59 | 24.20 | 22.90 | 21.27 | 22.75 | 22.23
 2013-10-16 | 2013-10-16 00:16:00 CEST | 23.74 | 22.52 | 22.23 | 23.22 | 23.03 | 22.98
 2013-10-17 | 2013-10-17 00:17:00 CEST | 21.68 | 24.54 | 21.15 | 23.58 | 23.04 | 21.94