mercoledì 25 giugno 2025

#ImpolyzerDataStudio© #AI #Prompt Engineering. #Text to SQL #ETL #RAG

 #ImpolyzerDataStudio© #AI #Text to SQL #ETL #RAG

POSTED BY GIULIANO PAGNINI, 24 GIU 2025

Dal RAG allo Schema-Guided SQL Generation: Evoluzione dei Sistemi per la Generazione Strutturata di Query e regole di import/export

Introduzione

I moderni sistemi di generazione di codice SQL stanno adottando tecniche avanzate di Intelligenza Artificiale per tradurre richieste in linguaggio naturale in query precise e ottimizzate. Tra questi, il Retrieval-Augmented Generation (RAG) è emerso come un approccio potente, combinando recupero di informazioni contestuali e generazione dinamica.

Tuttavia, in contesti specializzati come la generazione di query SQL per database strutturati (es. Firebird, PostgreSQL,Oracle,Interbase,ect), è possibile definire un’evoluzione più mirata del RAG: un Domain-constrained SQL generation framework (o alternativamente, Prompt-guided structured generation system).

Ma cosa distingue questo sistema da un RAG tradizionale? E perché è particolarmente efficace per l’interazione con database complessi?




1. RAG Classico vs. Schema-Guided SQL Generation

Retrieval-Augmented Generation (RAG)

Il RAG standard combina due fasi:

  1. Retrieval: Recupera informazioni da una knowledge base esterna (es. documenti, articoli).

  2. Generation: Genera una risposta basata sui dati recuperati e sul contesto della domanda.

Esempio:

  • Un chatbot che risponde a domande tecniche recuperando pezzi di manuali.

Schema-Guided SQL Generation (SGSG)

Nel nostro caso, il sistema specializzato opera in modo più strutturato:

  1. Retrieval vincolato: Invece di cercare in documenti generici, accede a uno schema di database predefinito (tabelle, campi, relazioni).

  2. Generazione guidata da regole: Produce SQL seguendo vincoli precisi (sintassi del DBMS, assenza di alias non supportati, ecc.).

  3. Mappatura semantica: Traduce termini naturali (es. "contribuente") in campi DB (es. H531UTENTI.IDUTENTE).

Esempio:

  • Data la richiesta "totale canone permanente anno 2025 per ogni contribuente del comune H531", il sistema:

    • Identifica le tabelle rilevanti (H531EREGISTRO2025H531UTENTI).

    • Applica filtri basati su TIPOCOD = 1 (per "permanente").

    • Genera una query con GROUP BY IDUTENTE e SUM(IMPORTO).


2. Perché è Più di un Semplicistico RAG?

Mentre il RAG tradizionale è general-purpose, il nostro sistema introduce:

a) Vincoli di Dominio Forti

  • Schema-aware: Non cerca informazioni in modo aperto, ma naviga una struttura DB nota.

  • DBMS-specific: Rispetta i limiti del DB attivo (es. Firebird 3.0+)

b) Prompt Engineering Specializzato

  • I "prompt" non sono domande generiche, ma includono:

    • Placeholder dinamici (es. {{dynamic_codifica}} → H531ECODIFICA2025).

    • Traduzione semantica ("canone" → campo IMPORTO).

c) Controllo Strutturato dell’Output

  • Non genera testo libero, ma solo SQL valido per lo schema fornito.

  • Evita allucinazioni (es. non inventa campi inesistenti).


3. Nomenclatura: Quale Termine Usare?

Ecco alcune definizioni alternative, con pro e contro:

TermineVantaggiSvantaggi
Domain-constrained SQL generation frameworkPrecisa il dominio (SQL) e i vincoli. Ottimo per tecnici.Un po’ lungo.
Prompt-guided structured generation systemEnfatizza il ruolo del prompt. Più generale.Non specifica il contesto SQL.
Schema-Guided SQL Generator (SGSG)Breve, tecnico, intuitivo.Meno noto.

La nostra sceltaDomain-constrained SQL generation framework cattura meglio la natura ibrida (IA + regole di dominio).


4. Caso d’Uso: Generazione di Query Complesse

Il sistema eccelle in scenari come:

  • Traduzione di richieste amministrative (es. "lista contribuenti morosi").

  • Adattamento a DB legacy (Firebird, Oracle,Interbase,SQLLite,ect) con sintassi specifiche.

  • Mantenimento della compliance: Query sempre allineate allo schema DB.

Esempio di output generato:

sql

SELECT 
    U.IDUTENTE, 
    U.RAGIONE_SOCIALE, 
    SUM(R.IMPORTO) AS TOTALE_CANONE_2025
FROM 
    H531UTENTI U
JOIN 
    H531EREGISTRO2025 R ON U.IDUTENTE = R.IDUTENTE
WHERE 
    R.IDCODIFICA IN (SELECT COD FROM H531ECODIFICA2025 WHERE TIPOCOD = 1)
    AND R.DATAINIZIO >= '2025-01-01' 
    AND R.DATAFINE <= '2025-12-31'
GROUP BY 
    U.IDUTENTE, U.RAGIONE_SOCIALE
  • Chiavi disponibili


    1. Chiavi Principali

    ChiaveTipoDescrizioneEsempio
    tagsArray di stringheTecnologie/DBMS compatibili["SQL", "FIREDAC", "FIREBIRD 3.0+"]
    questionStringaDomanda in linguaggio naturale da tradurre in SQL"totale canone permanente anno 2025..."
    constraintsArray di stringheVincoli per la generazione della query["Usa solo SQL compatibile con FIREBIRD 3.0+"]
    placeholdersOggetto (key-value)Sostituzioni dinamiche nello schema/tabelle"[[COMUNE]]CODIFICA{{ANNO}}" → "H531ECODIFICA2025"
    tablesOggetto annidatoSchema completo del database (tabelle + campi)Vedi sotto
    semantic_mappingOggetto (key-value)Mappatura termini naturali → strutture DB"canone" → "REGISTRO.IMPORTO"

    2. Sottochiavi di tables

    Ogni tabella definita in tables deve includere:

    SottochiaveTipoDescrizione
    fieldsArray di stringheLista di campi della tabella
    keyfieldsArray di stringheCampi che compongono la chiave primaria

    Esempio:

    json

    "H531EREGISTRO2025": {
      "fields": ["IDMASTER", "ID", ...],
      "keyfields": ["IDMASTER", "ID"]
    }

    3. Sottochiavi Speciali

    • In placeholders:

      • Supporta sintassi con [[ ]] (obbligatori) e {{ }} (opzionali).

        • "[[COMUNE]]CODIFICA{{ANNO}}" → Se ANNO=2025, diventa "H531ECODIFICA2025".

    • In semantic_mapping:

      • Può riferirsi a:

        • Tabelle (es. "contribuente" → "UTENTI").

        • Campi (es. "totale" → "IMPORTO").

        • Condizioni SQL (es. "occupazione" → "IDCODIFICA IN (SELECT COD FROM ...)").


    4. Chiavi Opzionali

    ChiaveScopo
    aliasesMappatura di alias per tabelle/campi (se supportati dal DBMS).
    custom_functionsDefinizione di funzioni SQL personalizzate ammesse.

    Esempio con aliases

    Utile per semplificare query complesse con riferimenti a tabelle/campi lunghi:

    json

    {
      "tags": ["FIREBIRD 3.0+"],
      "question": "elenco ragione sociale e totale canone 2025 per contribuente",
      "constraints": ["Usa alias solo se specificato esplicitamente"],
      "aliases": {
        "tabelle": {
          "H531EREGISTRO2025": "R",
          "H531UTENTI": "U"
        },
        "campi": {
          "RAGIONE_SOCIALE": "RAG_SOC",
          "IMPORTO": "TOTALE"
        }
      },
      "tables": { ... },
      "semantic_mapping": { ... }
    }

    SQL Generato:

    sql

    SELECT 
      U.RAGIONE_SOCIALE AS RAG_SOC, 
      SUM(R.IMPORTO) AS TOTALE
    FROM 
      H531EREGISTRO2025 R
    JOIN 
      H531UTENTI U ON R.IDUTENTE = U.IDUTENTE
    GROUP BY 
      U.RAGIONE_SOCIALE

    Esempio con custom_functions

    Per integrare funzioni specifiche del DBMS o personalizzate:

    json

    {
      "tags": ["FIREBIRD 3.0+"],
      "question": "differenza giorni tra data inizio e fine accertamento",
      "constraints": ["Usa solo funzioni dichiarate in custom_functions"],
      "custom_functions": {
        "DIFF_GIORNI": "DATEDIFF(day, ?1, ?2)",
        "FORMATO_DATA": "CAST(?1 AS VARCHAR(10))"
      },
      "tables": {
        "H531EREGISTRO2025": {
          "fields": ["ID", "DATAINIZIO", "DATAFINE"],
          "keyfields": ["ID"]
        }
      }
    }

    SQL Generato:

    sql

    SELECT 
      ID,
      DIFF_GIORNI(DATAINIZIO, DATAFINE) AS GIORNI_DIFF
    FROM 
      H531EREGISTRO2025

    Combinazione di aliases + custom_functions

    json

    {
      "aliases": {
        "tabelle": {
          "H531EREGISTRO2025": "REG"
        }
      },
      "custom_functions": {
        "IS_MOROSO": "CASE WHEN REG.PAGATO = 0 AND REG.SCADENZA < CURRENT_DATE THEN 1 ELSE 0 END"
      },
      "question": "cerca contribuenti morosi con canone > 1000",
      "tables": { ... }
    }

    SQL Generato:

    sql

    SELECT 
      REG.ID,
      U.RAGIONE_SOCIALE
    FROM 
      H531EREGISTRO2025 REG
    JOIN 
      H531UTENTI U ON REG.IDUTENTE = U.IDUTENTE
    WHERE 
      IS_MOROSO(REG.ID) = 1
      AND REG.IMPORTO > 1000

    Note Importanti:

    1. Compatibilità:

      • Le custom_functions devono essere supportate dal DBMS.

      • Gli aliases sono opzionali e disattivabili via constraints (es. ["NO alias"]).

    2. Sicurezza:

      • Validare sempre le custom_functions per evitare SQL injection.

    3. Parametri:

      • Usare ?1?2... per segnaposto di parametri nelle funzioni.


    5. Vincoli di Utilizzo

    1. Obbligatorie:

      • tablesquestionconstraints.

    2. Consigliate:

      • semantic_mapping (migliora l'accuratezza).

      • placeholders (per query dinamiche).

    3. Ignorate se assenti:

      • aliasescustom_functions.


    Esempio Completo Minimo:

    json

    {
      "tags": ["SQL", "FIREBIRD"],
      "question": "contare i contribuenti attivi",
      "constraints": ["NO alias"],
      "tables": {
        "UTENTI": {
          "fields": ["IDUTENTE", "STATO"],
          "keyfields": ["IDUTENTE"]
        }
      },
      "semantic_mapping": {
        "contribuenti": "UTENTI",
        "attivi": "STATO = 'ATTIVO'"
      }
    }

    Output SQL Generato:

    sql

    SELECT COUNT(*) 
    FROM UTENTI 
    WHERE STATO = 'ATTIVO'


Nel sistema che hai presentato, la chiave filters non è inclusa tra quelle standard, ma può essere implementata in due modi, a seconda delle esigenze:

Uso Alternativo di semantic_mapping per Filtri

Se per "filters" intendi condizioni SQL predefinite, queste possono essere inserite direttamente nel semantic_mapping:

json

"semantic_mapping": {
  "occupazione": "REGISTRO.IDCODIFICA IN (SELECT COD FROM {{dynamic_codifica}} WHERE TIPOCOD = 1)",
  "attivo": "STATO = 'ATTIVO'"
},
"placeholders": {
    "dynamic_codifica": "[[COMUNE]]CODIFICA{{ANNO}}",
    
  },

Esempio di utilizzo:
Se la domanda è "mostra i contribuenti attivi", il sistema tradurrà "attivo" nella condizione SQL associata.


La Chiave Custom filters

Per filtri complessi, si può estender lo schema JSON:

json

{
  "filters": {
    "filtro_occupazione": "TIPOCOD = 1 AND DATAFINE >= CURRENT_DATE",
    "filtro_moroso": "PAGATO = 0 AND SCADENZA < CURRENT_DATE"
  }
}

Vantaggi:

  • Separazione netta tra mappatura semantica e condizioni riutilizzabili.

  • Possibilità di riferirsi ai filtri con alias (es. "applica filtro_moroso").


 Differenza tra filters e constraints

  • constraints: Vincoli tecnici sulla generazione SQL (es. sintassi Firebird).

    json

    "constraints": ["NO WITH clause", "Usa solo JOIN espliciti"]
  • filters (se aggiunta): Vincoli logici sui dati (es. filtri temporali o di stato).


Esempio Completo con filters Personalizzata

json

{
  "question": "totale canoni occupazione non pagati nel 2025",
  "constraints": ["NO alias", "Usa sintassi Firebird 3.0+"],
  "tables": { ... },
  "semantic_mapping": {
    "canone": "REGISTRO.IMPORTO",
    "occupazione": "[[FILTRO_OCCUPAZIONE]]"
}, "filters": { "FILTRO_OCCUPAZIONE": "IDCODIFICA IN (SELECT COD FROM H531ECODIFICA2025 WHERE TIPOCOD = 1)", "FILTRO_NON_PAGATO": "ID NOT IN (SELECT IDPAGAMENTO FROM PAGAMENTI)" } }

SQL Generato:

sql

SELECT SUM(IMPORTO) 
FROM H531EREGISTRO2025 
WHERE IDCODIFICA IN (SELECT COD FROM H531ECODIFICA2025 WHERE TIPOCOD = 1)
  AND ID NOT IN (SELECT IDPAGAMENTO FROM PAGAMENTI)

Se vuoi standardizzare il sistema

  1. Approccio Conservativo: Usa solo semantic_mapping per i filtri (come nell'esempio iniziale).

  2. Approccio Esteso: Aggiungi filters come chiave opzionale per condizioni riutilizzabili.

Raccomandazione: Se lavori in un contesto con molti filtri ricorrenti (es. "moroso", "storico", "in scadenza"), la chiave filters migliora la manutenibilità.


Conclusioni: ImpolyzerDataStudio verso Sistemi Ibridi AI + Rules

Questo framework rappresenta un ponte tra NLP e database strutturati, superando i limiti del RAG generico:

  • Maggiore accuratezza: Niente allucinazioni su schemi DB.

  • Mantenibilità: Aggiornando lo schema, si aggiorna il generatore.

  • Adattabilità: Può essere esteso a altri DBMS (tutti i DB Firedac e CDATA,CSV, Access, Excel,memTable,ect).

Nessun commento:

Posta un commento