sql >> Database >  >> RDS >> PostgreSQL

Datumbereiken zoeken en optellen met overlappende records in postgresql

demo:db<>fiddle (gebruikt de oude dataset met het overlappende A-B-deel)

Disclaimer: Dit werkt voor dagintervallen, niet voor tijdstempels. De vereiste voor ts kwam later.

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 genereert alle datums tussen begin en einde. Dus elke datum waarop een activiteit bestaat krijgt één rij met het specifieke count
  2. Alle datums groeperen, alle bestaande activiteiten samenvoegen en optellen
  3. HAVING filtert de datums uit waar slechts één activiteit bestaat
  4. Omdat er verschillende dagen zijn met dezelfde activiteiten hebben we maar één vertegenwoordiger nodig:Filter alle duplicaten met DISTINCT ON
  5. Voeg dit resultaat samen met de originele tabel om het begin en einde te krijgen. (merk op dat "end" een gereserveerd woord is in Postgres, u kunt beter een andere kolomnaam zoeken!). Vroeger was het comfortabeler om ze kwijt te raken, maar het is mogelijk om deze gegevens binnen de subquery te krijgen.
  6. Groepeer deze deelname om de vroegste en laatste datum van elk interval te krijgen.

Hier is een versie voor tijdstempels:

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

Het belangrijkste idee is om mogelijke tijdsloten te identificeren. Dus ik neem elke bekende tijd (zowel begin als einde) en zet ze in een gesorteerde lijst. Zo kan ik de eerste bekende tijden nemen (17:00 van start A en 18:00 van start B) en kijken welke interval erin zit. Dan controleer ik het voor de 2e en 3e, dan voor de 3e een 4e enzovoort.

In het eerste tijdslot past alleen A. In de tweede van 18-19 past ook B. In het volgende slot 19-20 ook C, van 20 tot 20:30 past A niet meer, alleen B en C. De volgende is 20:30-22 waar alleen B past, tenslotte komt 22-23 D bij B en last but not least alleen D past in 23-23:30.

Dus ik neem deze tijdlijst en voeg deze samen met de activiteitentabel waar de intervallen elkaar kruisen. Daarna is het slechts een groepering op tijdslot en tel je je telling op.

  1. dit plaatst beide ts van een rij in één array waarvan de elementen worden uitgebreid tot één rij per element met unnest . Dus ik krijg alle tijden in één kolom die eenvoudig kan worden besteld
  2. met behulp van de lead vensterfunctie maakt het mogelijk om de waarde van de volgende rij in de huidige op te nemen. Dus ik kan een tijdstempelbereik maken van deze beide waarden met tsrange
  3. Dit filter is nodig omdat de laatste rij geen "volgende waarde" heeft. Dit creëert een NULL waarde die wordt geïnterpreteerd door tsrange als oneindig. Dus dit zou een ongelooflijk verkeerd tijdslot creëren. Dus we moeten deze rij eruit filteren.
  4. Doe mee met de tijdvakken tegen de originele tafel. De && operator controleert of twee bereiktypes elkaar overlappen.
  5. Groeperen op afzonderlijke tijdvakken, de namen en het aantal bij elkaar optellen. Filter de tijdvakken met slechts één activiteit door de HAVING . te gebruiken clausule
  6. Een beetje lastig om de juiste start- en eindpunten te vinden. De startpunten zijn dus ofwel het maximum van de start van de activiteit of het begin van een tijdvak (dat kan worden verkregen met lower ). bijv. Neem het slot van 20-20:30:het begint om 20u, maar noch B noch C heeft daar zijn startpunt. Vergelijkbaar met de eindtijd.


  1. MySQL-opgeslagen procedure die problemen veroorzaakt?

  2. gebruik zoeken in volledige tekst om onvolledige woorden in mysql te zoeken

  3. Ik wil machtigingen toewijzen aan een gebruiker om de EMP-tabel te zien

  4. Kan het schema niet verwijderen, omdat het niet bestaat of omdat je geen toestemming hebt. - SQL Server / TSQL-zelfstudie, deel 29