generate_series() în PostgreSQL

Date, timp și generare serii PostgreSQL 8.0+ (standard în PostgreSQL 15–17)

Generează pe loc serii de numere întregi sau calendare de date/ore pentru rapoarte fără zile lipsă și date de test.

Sintaxă SQL
SELECT * FROM generate_series(start, stop [, pas]);

Ce face generate_series()

generate_series() este una dintre cele mai îndrăgite funcții returnatoare de seturi (set-returning functions) din PostgreSQL. Poate genera secvențe numerice (int, bigint, numeric) sau secvențe de momente de timp (timestamp, timestamptz) între o valoare de început și una de sfârșit, cu un pas opțional.

În practică are două utilizări majore: completarea golurilor în rapoartele de tip serie temporală (făcând LEFT JOIN între un calendar generat cu generate_series și tabelul de comenzi ca să apară cu 0 și zilele fără vânzări) și generarea rapidă a milioanelor de rânduri sintetice pentru testarea performanței și a indecșilor.

Clauze, operatori și argumente

Clauză / Operator Descriere
generate_series(start, stop) Generează numere întregi consecutive de la start până la stop inclusiv (cu pasul implicit 1).
generate_series(start, stop, pas) Generează o serie numerică folosind pasul specificat (poate fi și negativ pentru numărare inversă).
generate_series(ts_start, ts_stop, interval) Generează o serie de date/ore incrementate cu un interval (ex: '1 day', '1 hour', '1 month').

Exemple

Exemplul 1: Raport zilnic de vânzări fără zile lipsă (calendar cu LEFT JOIN)

Generăm toate zilele dintr-un interval și facem LEFT JOIN cu tabelul comenzi, astfel încât zilele cu zero comenzi să fie afișate cu 0 în loc să lipsească din grafic.

SELECT zi::date AS data_calendar,
       COUNT(c.id) AS nr_comenzi,
       COALESCE(SUM(c.total_lei), 0) AS vanzari_lei
FROM generate_series(
    '2025-03-10'::timestamptz,
    '2025-03-14'::timestamptz,
    INTERVAL '1 day'
) AS zi
LEFT JOIN comenzi c
    ON c.plasata_la >= zi
   AND c.plasata_la < zi + INTERVAL '1 day'
GROUP BY zi
ORDER BY data_calendar;

Rezultat interogare

 data_calendar | nr_comenzi | vanzari_lei 
---------------+------------+-------------
 2025-03-10    |          4 |     1420.00
 2025-03-11    |          0 |        0.00
 2025-03-12    |          2 |      530.00
 2025-03-13    |          0 |        0.00
 2025-03-14    |          5 |     2190.50
(5 rows)

Exemplul 2: Generarea rapidă a datelor de test într-un tabel cu INSERT … SELECT

Populăm un tabel de test cu 100.000 de rânduri realiste în mai puțin de o secundă folosind generate_series(1, 100000).

INSERT INTO evenimente_test (cod_eveniment, utilizator_id, creat_la)
SELECT 'EVT-' || LPAD(i::text, 6, '0'),
       (i % 500) + 1,
       NOW() - (i || ' minutes')::interval
FROM generate_series(1, 5) AS s(i)
RETURNING id, cod_eveniment, utilizator_id;

Rezultat interogare

 id | cod_eveniment | utilizator_id 
----+---------------+---------------
  1 | EVT-000001    |             2
  2 | EVT-000002    |             3
  3 | EVT-000003    |             4
  4 | EVT-000004    |             5
  5 | EVT-000005    |             6
(5 rows)

INSERT 0 5

Greșeli frecvente

  • Dacă start > stop și pasul este pozitiv (sau start < stop cu pas negativ), generate_series() nu returnează o eroare, ci 0 rânduri.
  • La LEFT JOIN pe un calendar generat, nu pune condiții pe tabelul din dreapta în clauza WHERE (ex: WHERE c.status = 'livrata'), deoarece vei anula efectul lui LEFT JOIN și vei elimina din nou zilele cu zero comenzi; pune condiția în clauza ON sau în FILTER (WHERE ...).

Comenzi și funcții înrudite

  • NOW(), INTERVAL și date_trunc(): Lucrează cu momente de timp cu fus orar (timestamptz), aritmetică de durate (INTERVAL) și rotunjire calendaristică (date_trunc).
  • to_char() și EXTRACT(): Formatează date, ore și numere în șiruri personalizate (to_char) sau extrage componente numerice din timestamp-uri (EXTRACT).
  • COALESCE, NULLIF și CASE WHEN: Gestionează valorile NULL, previne împărțirea la zero și evaluează logica condițională direct în expresiile SQL.