sql >> Databasteknik >  >> RDS >> PostgreSQL

Hitta och summera datumintervall med överlappande poster i postgresql

demo:db<>fiol (använder den gamla datamängden med den överlappande A-B-delen)

Ansvarsfriskrivning: Detta fungerar för dagsintervall inte för tidsstämplar. Kravet på ts kom senare.

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 genererar alla datum mellan start och slut. Så varje datum en aktivitet existerar får en rad med det specifika count
  2. Grupperar alla datum, sammanställer alla befintliga aktiviteter och summan av deras antal
  3. HAVING filtrerar bort de datum där det bara finns en aktivitet
  4. Eftersom det finns olika dagar med samma aktiviteter behöver vi bara en representant:Filtrera alla dubbletter med DISTINCT ON
  5. Sätt ihop detta resultat mot den ursprungliga tabellen för att få början och slutet. (observera att "slut" är ett reserverat ord i Postgres, du borde hitta ett annat kolumnnamn!). Det var bekvämare att förlora dem tidigare men det är möjligt att få dessa data i underfrågan.
  6. Gruppera den här medlemmen för att få det senaste och senaste datumet för varje intervall.

Här är en version för tidsstämplar:

demo:db<>fiol

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

Huvudtanken är att identifiera möjliga tidsluckor. Så jag tar varje känd tid (både start och slut) och lägger dem i en sorterad lista. Så jag kan ta de första kända släptiderna (17:00 från start A och 18:00 från start B) och kolla vilket intervall som finns i den. Sedan kontrollerar jag det för 2:an och 3:an, sedan för 3:an och 4:an och så vidare.

I den första tidsluckan passar bara A. I tvåan från 18-19 passar även B. I nästa lucka 19-20 även C, från 20 till 20:30 passar inte A längre, bara B och C. Nästa är 20:30-22 där bara B passar, slutligen läggs 22-23 D till B och sist men inte minst passar bara D in 23-23:30.

Så jag tar den här tidslistan och slår ihop den mot aktivitetstabellen där intervallen skär varandra. Efter det är det bara en gruppering efter tid och summera ditt antal.

  1. detta placerar båda ts för en rad i en array vars element expanderas till en rad per element med unnest . Så jag får alla tider i en kolumn som enkelt kan beställas
  2. med hjälp av fönsterfunktionen gör det möjligt att ta värdet på nästa rad till den nuvarande. Så jag kan skapa ett tidsstämpelintervall av dessa båda värden med tsrange
  3. Detta filter är nödvändigt eftersom den sista raden inte har något "nästa värde". Detta skapar en NULL värde som tolkas av tsrange som oändlighet. Så detta skulle skapa en otroligt fel tidslucka. Så vi måste filtrera bort den här raden.
  4. Sätt ihop tidsluckorna mot det ursprungliga bordet. && operatören kontrollerar om två intervalltyper överlappar varandra.
  5. Gruppera efter enstaka tidsluckor, aggregera namnen och antalet. Filtrera bort tidsluckor med endast en aktivitet genom att använda HAVING klausul
  6. Lite knepigt att få rätt start- och slutpunkter. Så startpunkterna är antingen det maximala för aktivitetsstarten eller början av en tidslucka (vilket kan fås med lower ). T.ex. Ta luckan 20-20:30:Den börjar 20h men varken B eller C har sin startpunkt där. Liknande sluttiden.


  1. mysql_real_escape_string orsakar problem?

  2. Hur man deklarerar variabel i PostgreSQL

  3. COS() Exempel i SQL Server

  4. SQL-uppdateringsfrågesyntax med inre koppling