WITH RECURSIVE în SQLite

Interogări avansate SQLite 3.8.3

Un CTE care se folosește pe el însuși: generează serii de numere sau de date și parcurge ierarhii (șef–subordonat, categorii).

Sintaxă
WITH RECURSIVE nume (coloane) AS (
  SELECT ...                    -- pasul inițial
  UNION ALL
  SELECT ... FROM nume WHERE ... -- pasul recursiv
)
SELECT ... FROM nume;

Ce face WITH RECURSIVE

Un CTE recursiv are două părți legate prin UNION ALL (sau UNION). Prima parte dă rândurile de pornire. A doua parte citește din CTE-ul însuși și produce rânduri noi pornind de la cele găsite deja. SQLite repetă pasul recursiv până când acesta nu mai produce niciun rând.

SQLite ține rândurile de prelucrat într-o coadă: implicit le ia în ordinea în care au apărut (parcurgere în lățime). Dacă pasul recursiv are ORDER BY, coada devine o coadă cu priorități, așa că poți obține de exemplu o parcurgere în adâncime a unui arbore. LIMIT în pasul recursiv oprește generarea după un număr de rânduri.

Cu UNION în loc de UNION ALL, rândurile duplicate sunt eliminate, ceea ce oprește ciclurile într-un graf. În pasul recursiv nu poți folosi funcții de agregare sau funcții fereastră.

Parametri

Parametru Descriere
pasul inițial SELECT-ul care nu citește din CTE: rândurile de pornire.
pasul recursiv SELECT-ul care citește din CTE și produce rândurile următoare; trebuie să aibă o condiție de oprire.

Exemple

Exemplul 1: Numerele de la 1 la 5

Pornim de la 1 și adăugăm 1 cât timp x < 5. Când condiția devine falsă, pasul recursiv nu mai produce rânduri și generarea se oprește.

WITH RECURSIVE numere (x) AS (
  SELECT 1
  UNION ALL
  SELECT x + 1 FROM numere WHERE x < 5
)
SELECT x, x * x AS patrat FROM numere;

Rezultat

╭───┬────────╮
│ x │ patrat │
╞═══╪════════╡
│ 1 │      1 │
│ 2 │      4 │
│ 3 │      9 │
│ 4 │     16 │
│ 5 │     25 │
╰───┴────────╯

Exemplul 2: Toate zilele unei săptămâni

Seria de date pornește de luni, 26 octombrie 2026. strftime('%w') dă ziua săptămânii (0 = duminică); CASE o transformă în nume românesc.

WITH RECURSIVE zile (zi) AS (
  SELECT '2026-10-26'
  UNION ALL
  SELECT date(zi, '+1 day') FROM zile WHERE zi < '2026-11-01'
)
SELECT zi,
       CASE strftime('%w', zi)
         WHEN '0' THEN 'duminică' WHEN '1' THEN 'luni' WHEN '2' THEN 'marți'
         WHEN '3' THEN 'miercuri' WHEN '4' THEN 'joi' WHEN '5' THEN 'vineri'
         ELSE 'sâmbătă'
       END AS nume_zi
FROM zile;

Rezultat

╭────────────┬──────────╮
│     zi     │ nume_zi  │
╞════════════╪══════════╡
│ 2026-10-26 │ luni     │
│ 2026-10-27 │ marți    │
│ 2026-10-28 │ miercuri │
│ 2026-10-29 │ joi      │
│ 2026-10-30 │ vineri   │
│ 2026-10-31 │ sâmbătă  │
│ 2026-11-01 │ duminică │
╰────────────┴──────────╯

Exemplul 3: Organigrama unei firme

Pornim de la directoare (fără șef) și coborâm câte un nivel. Coloana cale păstrează drumul de la vârf; ordonând după ea, fiecare angajat apare sub șeful lui.

CREATE TABLE angajati (id INTEGER PRIMARY KEY, nume TEXT, sef_id INTEGER);
INSERT INTO angajati VALUES
  (1, 'Ana (directoare)', NULL),
  (2, 'Radu', 1), (3, 'Elena', 1),
  (4, 'Dan', 2), (5, 'Mara', 2), (6, 'Ioana', 3);

WITH RECURSIVE ierarhie (id, nume, nivel, cale) AS (
  SELECT id, nume, 0, nume FROM angajati WHERE sef_id IS NULL
  UNION ALL
  SELECT a.id, a.nume, i.nivel + 1, i.cale || ' > ' || a.nume
  FROM angajati a JOIN ierarhie i ON a.sef_id = i.id
)
SELECT nivel, cale FROM ierarhie ORDER BY cale;

Rezultat

╭───────┬──────────────────────────────────╮
│ nivel │               cale               │
╞═══════╪══════════════════════════════════╡
│     0 │ Ana (directoare)                 │
│     1 │ Ana (directoare) > Elena         │
│     2 │ Ana (directoare) > Elena > Ioana │
│     1 │ Ana (directoare) > Radu          │
│     2 │ Ana (directoare) > Radu > Dan    │
│     2 │ Ana (directoare) > Radu > Mara   │
╰───────┴──────────────────────────────────╯

Greșeli frecvente

  • Uiți condiția de oprire din pasul recursiv. Interogarea generează rânduri la nesfârșit; pune un WHERE sau, la testare, un LIMIT în interogarea principală.
  • Folosești UNION ALL pe un graf cu cicluri (A trimite la B, B la A). Rândurile se repetă la infinit; folosește UNION sau ține o cale și verifică dacă nodul apare deja în ea.
  • Pui COUNT() sau SUM() în pasul recursiv. SQLite nu permite agregări acolo; fă agregarea în interogarea principală, după CTE.

Comenzi înrudite

  • WITH (CTE): Dă un nume unei subinterogări (Common Table Expression) și o folosește apoi ca pe un tabel, ca interogarea să fie mai ușor de citit.
  • date, time, datetime: Transformă și calculează date: extrage ziua sau ora, adună zile și luni, găsește începutul lunii sau lunea următoare.
  • strftime: Formatează o dată după un șablon ales de tine (de exemplu 25.10.2026 09:05) sau extrage din ea anul, luna, ziua săptămânii.
  • json_each / json_tree: Transformă un tablou sau un obiect JSON în rânduri, ca să poți filtra, număra și lega elementele lui cu alte tabele.