# Rapoarte SQL (meniu Rapoarte → „Rapoarte SQL") Mecanism de raportare **fără `.frx`**: SQL-ul stă în baza de date, aplicația îl execută și afișează rezultatul într-un grid (cu grupare/ordonare/export Excel). Se instalează printr-un script de migrare, nu prin VFP IDE. ## Lansare și drepturi - `Meniuri\rapoarte.mn2:36-37` → `DO alege_raport WITH 2, .T. IN orapoarte_dinamice.prg`. - `alege_raport` (`COMUN\programe\orapoarte_dinamice.prg:5-79`): al doilea parametru `tlDrepturi=.T.` ⇒ lista se face cu **`join rapoarte_utilizatori ru on ... and ru.id_utilizator = gnIdUtil`**. **Un raport fără linii în `RAPOARTE_UTILIZATORI` nu apare deloc în meniu**, oricât de corect ar fi. - `executa_raport(tnIdFormularRaport, tnIdRaport)` (`:85-107`) deschide direct un raport după id, fără lista de alegere. `ID_FORMULAR_RAPORT` alege formularul: | valoare | formular | ce e | |---|---|---| | 1 | `frm_date_rapoarte_rulaje` | rapoarte bazate pe rulaje | | **2** | **`frm_date_rapoarte_sql`** (`COMUN\clase\orapoarte.vc2:4076`) | **rapoarte SQL** | | 3 | `frm_date_rapoarte_balpart` | balanță parteneri | ## Tabele - **`RAPOARTE`** — `CSQL` (CLOB, textul SQL), `CSQL_PARAMETRI` (XML, definiția parametrilor), `DENUMIRE` (numele din listă, VARCHAR2(1000)), `TITLU` (titlul listării, 150), `ID_FORMULAR_RAPORT`, `STERS`. Secvență: `SEQ_RAPOARTE`. View de listare: `VRAPOARTE`. - **`RAPOARTE_FILTRE`** — o linie per raport, `VALOARE` = șir concatenat `titlu;camp;pozitie_semn;;valoare|` (ex. `AN;AN;1;;0|LUNA;LUNA;1;;0|`). Generat de `parametri_rapoarte_sql` (`orapoarte_dinamice.prg:186-206`), citit la deschidere (`orapoarte.vc2:2709`). Marcajul `luna_curenta|` e înlocuit la citire cu luna/anul de lucru. - **`RAPOARTE_UTILIZATORI`** — `(ID_RAPORT, ID_UTILIZATOR)`, drepturile. FK-uri către `RAPOARTE`: doar `RAPOARTE_FILTRE` și `RAPOARTE_UTILIZATORI`. ## Parametri `<%=NUME%>` În `CSQL` parametrii se scriu `<%=AN%>`, `<%=LUNA%>`. La rulare, **`ct_selectie_sql.do_instructiune_sql`** (`COMUN\clase\orapoarte.vc2:1552-1637`) înlocuiește **textual** (`STUFFC`) fiecare marcaj cu valoarea din criteriile de selecție — **nu sunt bind variables**, deci valoarea intră brut în SQL. Cuvinte rezervate, completate automat din globale (`orapoarte.vc2:1578-1586`): | parametru | sursă | |---|---| | `AN` | `gnAn` | | `LUNA` | `gnLuna` | | `SUCURSALA` | `gcCondSucursala` | Acestea **trebuie declarate cu `tipcamp = 'E'`**, iar `campid` = numele parametrului; altfel valoarea nu se rezolvă și apare „Nu este completata valoarea parametrului X!". Valoarea tastată de utilizator pentru un parametru rezervat este **ignorată** — se pune global. Deci un interval (ex. lunile 1..7) cere doi parametri `tipcamp = 'N'` cu **alt nume**, iar numele nu are voie să înceapă cu `AN`/`LUNA`/`SUCURSALA`: potrivirea din `do_instructiune_sql` e `=` VFP, iar cu `SET EXACT OFF` un `LUNA_DE_LA` s-ar potrivi cu `LUNA`. Ex. `DE_LA_LUNA` / `PANA_LA_LUNA`, folosite în SQL ca `luna between <%=DE_LA_LUNA%> and <%=PANA_LA_LUNA%>`. Parametrul e citit din expresia de filtru spartă pe `AND` și apoi pe `=`, deci **merge doar operatorul „egal"** (implicit la `N`) și numele nu poate conține `AND`. `CSQL_PARAMETRI` = XML `VFPData` (encoding `Windows-1252`), câte un nod `crsparametri` cu `parametru / titlu / camp / tipcamp / campid` (+ opțional `campcautare`); citit cu `XMLTOCURSOR` în `criterii_rapoarte_sql` și `parametri_rapoarte_sql` (`orapoarte_dinamice.prg:109-251`). `tipcamp`: `N` numeric, `C` caracter, `D` dată, `D1` perioadă (se traduce în `AN*12+LUNA`), `E` cuvânt rezervat. ## Instalare printr-un script de migrare Șablon de urmat: `D:\ROA\DATABASE\SCRIPTURI_CLAR\2026\07\ff_2026_07_23_01_GESTIUNE_RAPOARTE.sql` (raportul `CHELTUIELI CUMULAT - REG JURNAL + RULAJE`, peste view-ul `VRUL_ACT_CHELTUIELI`) și `...\2026\08\ff_2026_08_24_03_GESTIUNE_RAPOARTE.sql` (`VENITURI SI CHELTUIELI - REG JURNAL + RULAJE`, view-urile `VRUL_ACT_VEN_CHELT_DET` + `VRUL_ACT_VEN_CHELT_TOT`). Structura: 1. `CREATE OR REPLACE VIEW` cu logica raportului (SQL-ul din `CSQL` rămâne un `select` simplu peste view — mai ușor de depanat și de refolosit). 2. Bloc anonim idempotent: `IF (select count(*) from rapoarte where upper(denumire) = '...') = 0` → `SEQ_RAPOARTE.nextval` + `MERGE` în `RAPOARTE` + `MERGE` în `RAPOARTE_FILTRE` + `INSERT` în `RAPOARTE_UTILIZATORI` din `syn_vutilizatori` ⋈ `syn_vdef_util_firme` ⋈ `syn_nom_firme where schema = user and nvl(sters,0) = 0`, cu `inactiv = 0`. 3. `exec pack_migrare.UpdateVersiune(''); commit;` ### Capcane - **Commit.** Blocurile anonime nu comit singure. Rulat dintr-un client care nu comite la ieșire, view-urile rămân create (DDL = commit implicit) dar raportul lipsește din `RAPOARTE` — arată exact ca o problemă de drepturi. Verificare rapidă: `SEQ_RAPOARTE.last_number` a avansat, dar `select * from rapoarte` nu are linia ⇒ lipsește `commit`. - Drepturile nu sunt opționale (vezi mai sus). - **`ACT.ID_PARTD` / `ACT.ID_PARTC` sunt `0` când nu există partener, niciodată `NULL`.** Un `MIN(ID_PARTD)` peste notele unui document întoarce `0` și numele partenerului iese gol; corect e `MIN(NULLIF(ID_PARTD, 0))`. Numele se iau din `NOM_PARTENERI.NUME` (în `VACT_TOT` sunt deja rezolvate ca `PARTD`/`PARTC`). - Ordinea coloanelor din `select`-ul din `CSQL` = ordinea coloanelor din grid. - `ORDER BY` se scrie în `CSQL`; coloanele tehnice de sortare pot fi selectate sau nu, dar trebuie să existe în view. ### Linii de total în același raport Pentru totaluri la finalul listării (fără al doilea raport): `UNION ALL` cu rânduri sintetice care au coloana de grupare **`NULL`** (Oracle sortează `NULLS LAST` la `ASC`, deci ajung ultimele) și un `R_TIP` mai mare decât cele de detaliu; eticheta se pune într-o coloană text lată (`EXPLICATIA`), valoarea în coloana de sumă. Exemplu: `VRUL_ACT_VEN_CHELT_TOT`, `R_TIP` 9-15. Când raportul merge pe un **interval** de perioade, totalurile nu pot sta în view: view-ul le poate da doar pe perioadă (altfel rândul agregat are `an`/`luna` `NULL` și cade la filtrul exterior). Deci view-ul dă totalurile **pe lună**, iar `UNION ALL`-ul din `CSQL` le însumează pe intervalul cerut (`group by r_tip, tip, explicatia`) — toate liniile fiind sume, adunarea pe luni e validă. Ramura de totaluri pune `NULL` pe `an`/`luna`, deci blocul iese o singură dată, la finalul listării. ### Performanță: `WITH` materializat blochează filtrul de perioadă Într-un view de raport filtrat pe `an`/`luna`, o subinterogare `WITH` **referită de două ori** e materializată de Oracle într-un tabel temporar (`TEMP TABLE TRANSFORMATION` în plan). O subinterogare materializată **nu mai primește predicatul exterior**: se construiește tot istoricul și abia la final se aplică `an = ... and luna = ...`. Pe date de producție asta a însemnat 7.4M rânduri și un plan de 3h15m pentru o lună (`VRUL_ACT_VEN_CHELT`, versiunea inițială). - `/*+ INLINE */` în CTE **nu** a rezolvat-o când CTE-ul e un `UNION ALL`. - Ce funcționează: **view-uri separate în loc de CTE-uri referite de mai multe ori** — view-urile se îmbină (view merging) și primesc predicatul. Vezi `VRUL_ACT_VEN_CHELT_DET` (detaliul) + `VRUL_ACT_VEN_CHELT_TOT` (totalurile pe lună, agregă `_DET`) în `SCRIPTURI_CLAR\2026\08\ff_2026_08_24_03_GESTIUNE_RAPOARTE.sql`. - Subinterogările auxiliare (ancore de dată, teste de existență, totaluri de control) citesc **tabelele de bază `ACT` / `RUL`**, nu `VACT_TOT` / `VRUL_TOT`: view-urile astea nu filtrează rânduri (doar left join-uri de nomenclator), dar `VRUL_TOT` are `FDOC`/`ID_FDOC` ca **subinterogări scalare pe `ACT` per rând**. Indecși utili: `ACT` `IDX_ACT_006 (AN,LUNA,STERS,…)`, `IDX_ACT_002 (LUNA,AN,SCD,SCC,STERS,…)`; `RUL` `IDX_RUL_003 (AN,LUNA,COD)`. - Verificare rapidă înainte de livrare: `EXPLAIN PLAN` pe interogarea din `CSQL` cu an/luna concrete — dacă apare `TEMP TABLE TRANSFORMATION` sau predicatul `AN=`/`LUNA=` nu se vede la accesele pe `ACT`/`RUL`, raportul va scana tot istoricul.