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

Rechercher et additionner des plages de dates avec des enregistrements qui se chevauchent dans postgresql

démo :db<>violon (utilise l'ancien jeu de données avec la partie A-B qui se chevauche)

Avis de non-responsabilité : Cela fonctionne pour les intervalles de jour et non pour les horodatages. L'exigence de ts est venue plus tard.

SELECT
    s.acts,
    s.sum,
    MIN(a.start) as start,
    MAX(a.end) as end
FROM (
    SELECT DISTINCT ON (acts)
        array_agg(name) as acts,
        SUM(count)
    FROM
        activities, generate_series(start, "end", interval '1 day') gs
    GROUP BY gs
    HAVING cardinality(array_agg(name)) > 1
) s
JOIN activities a
ON a.name = ANY(s.acts)
GROUP BY s.acts, s.sum
  1. generate_series génère toutes les dates entre le début et la fin. Ainsi, chaque date à laquelle une activité existe obtient une ligne avec le count spécifique
  2. Regroupement de toutes les dates, agrégation de toutes les activités existantes et somme de leurs décomptes
  3. HAVING filtre les dates où une seule activité existe
  4. Parce qu'il y a différents jours avec les mêmes activités, nous n'avons besoin que d'un seul représentant :filtrez tous les doublons avec DISTINCT ON
  5. Joignez ce résultat à la table d'origine pour obtenir le début et la fin. (notez que "end" est un mot réservé dans Postgres, il vaut mieux trouver un autre nom de colonne !). Il était plus confortable de les perdre auparavant, mais il est possible d'obtenir ces données dans la sous-requête.
  6. Regroupez cette jointure pour obtenir la date la plus ancienne et la plus récente de chaque intervalle.

Voici une version pour les horodatages :

démo :db<>violon

WITH timeslots AS (
    SELECT * FROM (
        SELECT
            tsrange(timepoint, lead(timepoint) OVER (ORDER BY timepoint)),
            lead(timepoint) OVER (ORDER BY timepoint)     -- 2
        FROM (
            SELECT 
                unnest(ARRAY[start, "end"]) as timepoint  -- 1 
            FROM
                activities
            ORDER BY timepoint
        ) s
    )s  WHERE lead IS NOT NULL                            -- 3
)
SELECT 
    GREATEST(MAX(start), lower(tsrange)),                 -- 6
    LEAST(MIN("end"), upper(tsrange)),
    array_agg(name),                                      -- 5
    sum(count)
FROM 
    timeslots t
JOIN activities a
ON t.tsrange && tsrange(a.start, a.end)                   -- 4
GROUP BY tsrange
HAVING cardinality(array_agg(name)) > 1

L'idée principale est d'identifier les créneaux horaires possibles. Donc, je prends chaque heure connue (début et fin) et je les mets dans une liste triée. Je peux donc prendre les premiers temps de remorquage connus (17h00 du départ A et 18h00 du départ B) et vérifier quel intervalle s'y trouve. Ensuite, je le vérifie pour le 2e et le 3e, puis pour le 3e et le 4e et ainsi de suite.

Dans le premier créneau horaire, seul A convient. Dans le second de 18-19 également B convient. Dans le prochain créneau 19-20 également C, de 20 à 20:30 A ne convient plus, seuls B et C. Le prochain est 20:30-22 où seul B convient, enfin 22-23 D est ajouté à B et last but not least seulement D s'inscrit dans 23-23:30.

Donc, je prends cette liste de temps et je la joins à la table des activités où les intervalles se croisent. Après cela, ce n'est qu'un regroupement par créneau horaire et résumez votre décompte.

  1. cela place les deux ts d'une ligne dans un tableau dont les éléments sont développés en une ligne par élément avec unnest . Je reçois donc tous les temps dans une colonne qui peut être simplement ordonnée
  2. en utilisant la fonction de fenêtre permet de prendre la valeur de la ligne suivante dans celle en cours. Je peux donc créer une plage d'horodatage à partir de ces deux valeurs avec tsrange
  3. Ce filtre est nécessaire car la dernière ligne n'a pas de "valeur suivante". Cela crée un NULL valeur interprétée par tsrange comme l'infini. Cela créerait donc un incroyable mauvais créneau horaire. Nous devons donc filtrer cette ligne.
  4. Rejoignez les créneaux horaires par rapport au tableau d'origine. Le && l'opérateur vérifie si deux types de plages se chevauchent.
  5. Regroupement par créneaux horaires uniques, agrégeant les noms et le nombre. Filtrez les plages horaires avec une seule activité en utilisant le HAVING clause
  6. C'est un peu difficile d'obtenir les bons points de départ et d'arrivée. Ainsi, les points de départ sont soit le maximum du début de l'activité, soit le début d'un créneau horaire (qui peut être obtenu en utilisant lower ). Par exemple. Prenez le créneau 20-20h30 :Il commence à 20h mais ni B ni C n'y ont leur point de départ. Semblable à l'heure de fin.