sql >> Databáze >  >> RDS >> PostgreSQL

Najděte a sečtěte rozsahy dat s překrývajícími se záznamy v postgresql

demo:db<>fiddle (používá starou datovou sadu s překrývající se částí A-B)

Odmítnutí odpovědnosti: Toto funguje pro denní intervaly, nikoli pro časová razítka. Požadavek na ts přišel později.

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 generuje všechna data mezi začátkem a koncem. Takže každé datum, kdy aktivita existuje, dostane jeden řádek s konkrétním count
  2. Seskupit všechna data, agregovat všechny existující aktivity a součet jejich počtů
  3. HAVING filtruje data, kdy existuje pouze jedna aktivita
  4. Protože existují různé dny se stejnými aktivitami, potřebujeme pouze jednoho zástupce:Filtrujte všechny duplikáty pomocí DISTINCT ON
  5. Připojte tento výsledek k původní tabulce, abyste získali začátek a konec. (všimněte si, že „konec“ je v Postgresu vyhrazené slovo, měli byste si najít jiný název sloupce!). Dříve bylo pohodlnější o ně přijít, ale je možné tato data získat v rámci dílčího dotazu.
  6. Seskupit toto spojení, abyste získali co nejčasnější a nejnovější datum každého intervalu.

Zde je verze časových razítek:

demo:db<>fiddle

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

Hlavní myšlenkou je identifikovat možné časové úseky. Takže vezmu každý známý čas (jak začátek, tak konec) a dám je do seřazeného seznamu. Mohu tedy vzít první vlek známé časy (17:00 ze startu A a 18:00 ze startu B) a zkontrolovat, který interval je v něm. Pak to zkontroluji pro 2. a 3., pak pro 3. a 4. a tak dále.

V prvním časovém úseku se hodí pouze A. Ve druhém od 18-19 se hodí i B. V dalším slotu 19-20 také C, od 20 do 20:30 A už se nevejde, jen B a C. Další je 20:30-22, kde se vejde jen B, nakonec se přidá 22-23 D B a v neposlední řadě jen D se vejde do 23-23:30.

Vezmu tedy tento časový seznam a připojím ho k tabulce činností, kde se intervaly protínají. Poté je to pouze seskupení podle časového úseku a sečtení svého počtu.

  1. to umístí obě části řádku do jednoho pole, jehož prvky jsou rozbaleny do jednoho řádku na prvek pomocí unnest . Dostanu všechny časy do jednoho sloupce, který lze jednoduše seřadit
  2. pomocí hlavní funkce okna umožňuje převzít hodnotu dalšího řádku do aktuálního. Takže mohu vytvořit rozsah časových razítek z těchto obou hodnot pomocí tsrange
  3. Tento filtr je nezbytný, protože poslední řádek nemá žádnou "další hodnotu". Tím se vytvoří NULL hodnota, která je interpretována pomocí tsrange jako nekonečno. Takže by to vytvořilo neuvěřitelně špatný časový úsek. Tento řádek tedy musíme odfiltrovat.
  4. Připojte se k časovým úsekům oproti původnímu stolu. && operátor zkontroluje, zda se dva typy rozsahů překrývají.
  5. Seskupování podle jednotlivých časových úseků, agregace jmen a počtu. Odfiltrujte časové úseky pouze jednou aktivitou pomocí HAVING doložka
  6. Trochu složité najít správné počáteční a koncové body. Počáteční body jsou tedy buď maximum začátku aktivity, nebo začátek časového úseku (který lze získat pomocí lower ). Např. Vezměte si slot 20-20:30:Začíná 20h, ale ani B ani C tam nemají počáteční bod. Podobný čas ukončení.


  1. Jak generovat skripty DDL (vytvořit) z SQL Server Management Studio (SSMS) - SQL Server / Výukový program TSQL, část 17

  2. Jak ručně nakonfigurovat a spustit PostgreSQL na Windows?

  3. Závažná chyba PHP:Volání nedefinované funkce mssql_connect()

  4. PostgreSQL:Jaký je maximální počet tabulek, které lze uložit v databázi postgreSQL?