venerdì 27 giugno 2025

#ImpolyzerDataStudio© #AI #Text_to_SQL #ETL #RAG #NLP #DataScience

 #ImpolyzerDataStudio© #AI #Text_to_SQL #ETL #RAG #NLP #DataScience

POSTED BY GIULIANO PAGNINI, 27GIU 2025

Addestramento del Domain-constrained SQL Generation Framework di ImpolyzerDataStudio.

Case studios

Esempi di classiche domande:
"elenco dei contribuenti morosi", "elenco dei contribuenti che non hanno pagato", "utenti che non hanno pagato", "anagrafiche in mora", "elenco dei contribuenti che hanno pagato in ritardo", "elenco dei contribuenti con pagamenti tardivi", "utenti che hanno pagato in ritardo","per ogni contribuente il dovuto di occupazione","per ogni contribuente il dovuto di pubblicità"
Nei DB di esempio ho due comuni H231 e G911 con le tabelle interessate:
<COMUNE>UTENTI con le anagrafiche dei contribuenti 
<COMUNE>(E)REGISTRO<ANNO> con tutte le tipologie di tributo (occupazioni e pubblicità) per ogni contribuente
<COMUNE>(E)_PUB<ANNO> elenco dei bollettini emessi nell'esercizio 
<COMUNE>(E)CODIFICA<ANNO> le tipologie di tributo (variabili ogni anno) (TIPOCOD = 0 pubblicitò TIPOCOD = 1 occupazione )

Dalle domande si estraggono le chiavi semantiche e si creano le associazioni (Es. utente, contribuente, ect fanno riferimento sempre alla tabella UTENTI, Dovuto ad esempio fa riferimento sia al campo IMPORTO che alla tabella REGISTRO) 



 non hanno pagato, morosi ,in mora, hanno pagato in ritardo



pagamenti in ritardo


per ogni contribuente il dovuto di occupazione


per ogni contribuente il dovuto di pubblicità"


mappato dovuto su campo IMPORTO



Come risponde alle domande? 
Es. per ogni contribuente il dovuto di occupazione nel comune G911 anno 2025.
Per comodità escludo i campi delle tabelle

{ "tags": [ "SQL", "FIREDAC", "FIREBIRD 3.0+" ], "question": "per ogni contribuente il dovuto di occupazione nel comune G911 anno 2025", "constraints": [ "Usa solo SQL compatibile con FIREBIRD 3.0+", "Non usare alias o funzioni non supportate da FIREBIRD 3.0+", "Utilizza solo i nomi dei campi effettivamente presenti nelle tabelle indicate", "ignora il campo ENTE in tutte le tabelle" ], "tables": { "G911ECODIFICA2025": { }, "G911EREGISTRO2025": { }, "G911UTENTI": { } }, "semantic_mapping": { "CONTRIBUENTE": "UTENTI", "DOVUTO": "IMPORTO", "OCCUPAZIONE": { "REGISTRO": "REGISTRO.IDCODIFICA IN (SELECT COD FROM dynamic_codifica WHERE TIPOCOD = 1)" } }, "placeholders": { "dynamic_codifica": "[[COMUNE]]CODIFICA{{ANNO}}" } }
Es. per ogni contribuente il dovuto di occupazione nel comune G911 anno 2025.
    

il JSON estratto

{ "tags": [ "SQL", "FIREDAC", "FIREBIRD 3.0+" ], "question": "elenco dei contribuenti che non hanno pagato nel comune G911 anno 2025", "constraints": [ "Usa solo SQL compatibile con FIREBIRD 3.0+", "Non usare alias o funzioni non supportate da FIREBIRD 3.0+", "Utilizza solo i nomi dei campi effettivamente presenti nelle tabelle indicate", "ignora il campo ENTE in tutte le tabelle" ], "tables": { "G911E_PUB2025": { }, "G911E_PUBD2025": { }, "G911UTENTI": { } }, "semantic_mapping": { "CONTRIBUENTI": "UTENTI", "NON HANNO PAGATO": "IDUTENTE IN (SELECT dynamic_bollettini WHERE Pagato IS NULL or Pagato = 0)" }, "placeholders": { "dynamic_bollettini ": "[COMUNE]]_PUB{{ANNO}}" } }

Con questo risultato:

sql
SELECT 
    u.IDUTENTE,
    u.RAGIONE_SOCIALE,
    u.RAGIONE_SOCIALE1,
    u.INDIRIZZO,
    u.NUMERO,
    u.CAP,
    u.P_IVA,
    u.COD_F,
    u.TEL,
    u.EMAIL,
    u.PEC,
    u.CATEGORIA
FROM G911UTENTI u
WHERE EXISTS (
    SELECT 1
    FROM G911E_PUB2025 p
    WHERE p.IDUTENTE = u.IDUTENTE
      AND (p.PAGATO IS NULL OR p.PAGATO = 0)
      AND p.ANNO = 2025
)
ORDER BY u.RAGIONE_SOCIALE

Buon lavoro!!

ImpolyzerDataStudio© #AI #Text_to_SQL #ETL #RAG #NLP #DataScience

#ImpolyzerDataStudio© #AI #Text_to_SQL #ETL #RAG #NLP #DataScience

POSTED BY GIULIANO PAGNINI, 27 GIU 2025

Addestrare un Domain-constrained SQL Generation Framework è Facilissimo! Basta un File e ImpolyzerDataStudio

Introduzione

Hai mai pensato a quanto sarebbe bello generare query SQL complesse in modo automatico, senza dover scrivere manualmente ogni condizione? Con un Domain-constrained SQL generation framework (o un Prompt-guided structured generation system), è possibile!

E la parte migliore? Non serve essere un mago del machine learning o un esperto di NLP. Basta salvare le regole semantiche in un file e condividerlo con altri utenti, che potranno riutilizzarle semplicemente caricando il progetto su ImpolyzerDataStudio.

Come Funziona?

  1. Definisci le Regole Semantiche. Il placeholders viene creato in automatico se nelle regole sono definite delle dynamic_table.


  2. Addestri il Modello (Facilmente!)

    • Usi un framework come ImpolyzerDataStudio per "insegnare" al sistema come tradurre richieste in linguaggio naturale (es.: "Mostrami i clienti con ordini superiori a 100€") in SQL.

    • Il modello impara dalle tue regole semantiche e le applica automaticamente

    • genera un context JSON compatto da inviare al modello AI configurato

      il risultato

      sql
      SELECT 
          u.RAGIONE_SOCIALE,
          SUM(r.IMPORTO) AS TOTALE_OCCUPAZIONE
      FROM 
          H231UTENTI u
          INNER JOIN H231EREGISTRO2025 r ON u.IDUTENTE = r.IDUTENTE
          INNER JOIN H231ECODIFICA2025 c ON r.IDCODIFICA = c.COD AND c.TIPOCOD = 1
      GROUP BY 
          u.RAGIONE_SOCIALE
      ORDER BY 
          u.RAGIONE_SOCIALE;


  3. Condividi il File e Riusalo

    • Altri utenti possono importare il tuo file di regole nel loro progetto e generare query senza dover riaddestrare nulla!


Perché è una Bomba?

✅ Niente codice complesso: Basta un file di configurazione.
✅ Condivisione immediata: Altri utenti possono usare le tue regole in pochi click.
✅ Adattabile: Puoi aggiornare le regole e migliorare il modello nel tempo.

Conclusione

Se vuoi un sistema che generi SQL in modo intelligente senza perdere mesi in sviluppo, un Domain-constrained SQL generation framework con regole semantiche salvate in un file è la soluzione perfetta.

E con ImpolyzerDataStudio, il gioco è fatto! 

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).