Passa al contenuto principale

HR Analytics

Versione documentazione: 1.0beta
Data: 7 ottobre 2026

Questo manuale descrive la struttura del database HR Analytics. L’obiettivo è spiegare in modo operativo cosa rappresentano le tabelle, quale sia il livello di dettaglio di ciascuna riga, come interpretare i campi e come collegare correttamente le tabelle tra loro.

Le query possono utilizzare le funzionalità standard di PostgreSQL, inclusi operatori e funzioni JSON/JSONB, LATERAL, funzioni di aggregazione e Window Functions.

Modulo HR Analytics​

Le funzionalità descritte in questa sezione sono disponibili esclusivamente per i clienti che hanno attivato il modulo HR Analytics. Il modulo consente di utilizzare i dati dell’applicativo a fini di analisi e Business Intelligence, offrendo:

  • Accesso diretto al database (in sola lettura) da parte di strumenti di Business Intelligence (BI).
  • Database denormalizzato, con una struttura stabile nel tempo e ottimizzata per l’analisi dei dati.
  • Disponibilità dello storico dei dati su un ampio intervallo temporale.
  • Aggiornamento giornaliero dei dati, per mantenere le informazioni disponibili per l’analisi costantemente aggiornate.

Indicazioni generali per le query​

Durate​

Le colonne che rappresentano una durata sono da considerarsi in minuti, per ottenere le ore usare una unità operativa decimale, ad esempio duration / 60.0. Evitare la unità operativa intera se servono frazioni di ora.

Date e timestamp​

  • Le colonne di tipo date usano il formato logico YYYY-MM-DD.
  • Le colonne di tipo timestamp contengono data e ora.
  • Le colonne start e end di kpi sono di tipo time, quindi rappresentano solo l’orario.

Campi JSONB​

Diverse informazioni sono memorizzate in jsonb, soprattutto gli attributi storicizzati dei dipendenti. In PostgreSQL sono utili in particolare:

  • -> per estrarre un valore JSON;
  • ->> per estrarre un valore come testo;
  • @> per verificare il contenimento JSONB;
  • jsonb_array_elements(...) per espandere un array JSONB in righe;
  • CROSS JOIN LATERAL quando l’espansione JSONB dipende dalla riga corrente.

Pattern tipico per espandere le unità operative di un dipendente:

SELECT
e.id,
d.costcenter,
d.name
FROM public.employee AS e
CROSS JOIN LATERAL jsonb_array_elements(e.divisions) AS ed(division_item)
JOIN public.division AS d ON d.costcenter = ed.division_item ->> 'id';

Window Functions​

Le Window Functions sono adatte a confronti tra giornate, ranking e valori progressivi senza perdere il dettaglio delle righe. Ad esempio, LAG, LEAD, ROW_NUMBER e SUM(...) OVER (...) possono essere usate direttamente sulle tabelle di questo schema.

Attenzione ai join uno-a-molti​

attenzione

Per esempio shift contiene normalmente una riga giornaliera per dipendente, mentre justification, clocking, breakdown e activity possono contenere più righe per lo stesso dipendente e giorno. Un join diretto può quindi duplicare le misure di shift. Se la query deve sommare minuti o ore, pre-aggregare le tabelle di dettaglio oppure calcolare separatamente le misure di shift.

Mappa tabelle​

TabellaRuoloLivello di dettaglio
shiftDettaglio turniUna riga per turno lavorativo per dipendente per giorno. Rappresenta il consuntivo giornaliero di un dipendente: ore ordinarie, assenze, straordinari. È la fact table principale del sistema.
kpiKPIUna riga per KPI per centro di costo per giorno. Rappresenta il valore giornaliero di un indicatore configurato per uno specifico centro di costo, eventualmente associato a un intervallo orario.
justificationDettaglio giustificativiUna riga per giustificativo. Ogni riga rappresenta una singola istanza di assenza o presenza per un dipendente in un orario specifico. Un dipendente può avere più giustificativi nello stesso giorno.
employeeDipendentiUna riga per dipendente (addetto). Ogni riga rappresenta un singolo lavoratore registrato nel sistema.
divisionStruttura AziendaleUna riga per unità operativa organizzativa. Ogni riga rappresenta un nodo della gerarchia aziendale a tre livelli: azienda radice, unità produttiva, area operativa.
clockingTimbratureUna riga per timbratura. Ogni riga rappresenta un singolo evento di timbratura di un dipendente: entrata, uscita, inizio pausa o fine pausa. Un dipendente ha più timbrature per giorno.
breakdownComponenti OrarieUna riga per componente di breakdown giornaliero per dipendente. Rappresenta la scomposizione delle ore di una giornata lavorativa nelle sue componenti: ore ordinarie, assenze, aggiunte, anomalie. Un dipendente può avere più righe per lo stesso giorno.
balanceDettaglio PianificazioneUna riga per combinazione di costcenter, division, shift, giorno, step (fascia oraria), skill e position, che rappresenta il fabbisogno e la disponibilità di personale in quell'intervallo.
activityAttivitàUna riga per attività svolta da un dipendente in un giorno. Ogni riga rappresenta una singola attività con fascia oraria precisa. Un dipendente può avere più attività nello stesso giorno.

Relazioni principali​

DaACondizioneNote
shiftemployeeshift.employee = employee.idDipendente a cui appartiene il turno.
shiftdivisionshift.division = division.costcenterunità operativa associata al turno.
shiftjustificationshift.employee = justification.employee AND shift.day = justification.dayGiustificativi dello stesso dipendente e giorno; relazione potenzialmente uno-a-molti.
shiftbreakdownshift.employee = breakdown.employee AND shift.day = breakdown.dayComponenti di dettaglio dello stesso dipendente e giorno; relazione potenzialmente uno-a-molti.
shiftclockingshift.employee = clocking.employee AND shift.day = clocking.dayTimbrature dello stesso dipendente e giorno; relazione potenzialmente uno-a-molti.
shiftactivityactivity.employee = shift.employee AND activity.day = shift.dayAttività dello stesso dipendente e giorno; relazione potenzialmente uno-a-molti.
kpidivisionkpi.costcenter = division.costcenterunità operativa/centro di costo a cui appartiene il KPI.
justificationdivisionjustification.costcenter @> to_jsonb(division.costcenter)Centro di costo contenuto nel JSONB del giustificativo.
employee.divisionsdivisionjsonb_array_elements(employee.divisions)->>'id' = division.costcenterStorico delle unità operative del dipendente.
employee.sectionsdivision.sectionsjsonb_array_elements(employee.sections)->>'id' = jsonb_array_elements(division.sections)->>'costCenter'Collegamento tra sezione del dipendente e sezione della unità operativa.
breakdownjustificationbreakdown.employee = justification.employee AND breakdown.day = justification.day AND breakdown.cause = justification.causeGiustificativo con la stessa causale.
balancedivisionbalance.costcenter = division.costcenterunità operativa/centro di costo del dato di bilanciamento.
informazioni

Nei casi JSONB il join può richiedere l’espansione dell’array con jsonb_array_elements(...) o un operatore di contenimento come @>.

shift​

Consuntivo giornaliero del lavoro per dipendente. Riassume, per giornata, il turno effettivo e le principali quantità di tempo: ore ordinarie pianificate, assenze, straordinari e componenti produttive.

Granularità: Una riga per turno lavorativo per dipendente per giorno. Rappresenta il consuntivo giornaliero di un dipendente: ore ordinarie, assenze, straordinari. È la fact table principale del sistema.

Quando usarla: È la tabella di riferimento per analisi aggregate su ore pianificate, assenze, straordinari e produttività giornaliera. Le misure di durata sono espresse in minuti.

Relazioni​

Tabella collegataCondizione di joinSignificato
employeeshift.employee = employee.idDipendente a cui appartiene il turno
justificationshift.employee = justification.employee AND shift.day = justification.dayGiustificativi del dipendente nello stesso giorno
breakdownshift.employee = breakdown.employee AND shift.day = breakdown.dayBreakdown del dipendente nello stesso giorno
clockingshift.employee = clocking.employee AND shift.day = clocking.dayTimbrature del dipendente nello stesso giorno
divisionshift.division = division.costcenterunità operativa/costcenter del turno

Campi​

CampoTipoNullableDescrizioneDettagli
employeevarcharSìMatricola del dipendente a cui appartiene il consuntivo giornaliero. Si collega a employee.id. Può contenere anche identificativi di lavoratori esterni.—
daydateSìData della giornata lavorativa a cui si riferisce il turno. È il riferimento temporale principale per filtri e aggregazioni.Formato: YYYY-MM-DD
namevarcharSìCodice che descrive il tipo di giornata. Tra i Valori: WRK per giornata lavorativa e L per giornata libera/riposo.Valori: WRK — giornata lavorativa
L — giornata libera / riposo
divisionvarcharSìIdentificativo della unità operativa associata al turno. La relazione con l’anagrafica organizzativa è shift.division = division.costcenter.—
costcentervarcharSìCentro di costo riportato direttamente sul turno come stringa. È distinto dal campo JSONB omonimo presente in alcune tabelle di dettaglio.Esempio: 128, 1726
starttimestampSìTimestamp di inizio effettivo del turno.Formato: YYYY-MM-DD HH:MM:SS.000
endtimestampSìTimestamp di fine effettivo del turno.Formato: YYYY-MM-DD HH:MM:SS.000
bstarttimestampSìTimestamp di inizio della pausa registrata nel turno.Formato: YYYY-MM-DD HH:MM:SS.000
bendtimestampSìTimestamp di fine della pausa registrata nel turno.Formato: YYYY-MM-DD HH:MM:SS.000
bdurationint2SìDurata della pausa, espressa in minuti.Unità: minuti
bpaidboolSìIndica se la pausa è retribuita (true) oppure no (false).—
ordint2SìMinuti ordinari pianificati per la giornata, cioè il monte ore contrattuale previsto. Esempio: 480 corrisponde a 8 ore.Unità: minuti
Esempio: 0 (giorno libero), 480 (8 ore)
absint2SìMinuti di assenza registrati nella giornata.Unità: minuti
ovtint2SìMinuti di straordinario registrati nella giornata.Unità: minuti
ovtprodint2SìQuota di straordinario considerata produttiva, in minuti. È un sottoinsieme di ovt.Unità: minuti
ordprodint2SìMinuti ordinari considerati produttivi nella giornata.Unità: minuti
totprodint2SìTotale dei minuti produttivi della giornata.Unità: minuti
extbooleanSìIndica se il turno è classificato come esterno.—

Note operative​

attenzione

Quando viene collegata a tabelle con più righe per dipendente e giorno, come justification, breakdown o clocking, le righe di shift possono essere replicate dal join. Per sommare misure di shift, aggregare prima le tabelle di dettaglio oppure calcolare i totali di shift separatamente.

  • Le misure ord, abs, ovt, ovtprod, ordprod e totprod sono espresse in minuti.
  • Per analisi su periodi, aggregare le misure in base al livello desiderato, ad esempio per day, employee, division o costcenter.
  • La relazione con division usa shift.division, non shift.costcenter che può far riferimento anche a una sezione.

kpi​

Valori giornalieri degli indicatori configurati dal cliente, associati a un centro di costo. Un KPI può riferirsi all’intera giornata oppure a una fascia oraria tramite start ed end.

Granularità: Una riga per KPI per centro di costo per giorno. Rappresenta il valore giornaliero di un indicatore configurato per uno specifico centro di costo, eventualmente associato a un intervallo orario.

Quando usarla: Usare questa tabella per analizzare indicatori di business o operativi configurati nell’installazione. Il significato di value dipende dal KPI indicato in name.

Relazioni​

Tabella collegataCondizione di joinSignificato
divisionkpi.costcenter = division.costcenterCentro di costo della unità operativa a cui è associato il KPI

Campi​

CampoTipoNullableDescrizioneDettagli
costcentervarcharSìCentro di costo a cui è associato il KPI. Si collega a division.costcenter.Esempio: 128, 1726
namevarcharSìNome o codice del KPI. È configurabile per cliente e identifica il significato del valore memorizzato nella riga.—
daydateSìData a cui si riferisce il valore del KPI.Formato: YYYY-MM-DD
starttimeSìOra di inizio dell’eventuale intervallo temporale associato al KPI.Formato: HH:MM:SS
endtimeSìOra di fine dell’eventuale intervallo temporale associato al KPI.Formato: HH:MM:SS
valuefloat8SìValore numerico dell’indicatore. L’unità di misura e la corretta aggregazione dipendono dalla configurazione del KPI.—

Note operative​

attenzione

I nomi dei KPI e la semantica dei valori non sono standard: verificare i valori presenti in name e la configurazione del cliente prima di scegliere funzioni di aggregazione come SUM, AVG, MIN o MAX.

  • name è configurabile: prima di costruire query specifiche è utile esaminare i valori disponibili con SELECT DISTINCT name FROM public.kpi ORDER BY name.
  • L’aggregazione corretta di value dipende dal significato del KPI; non tutti gli indicatori sono necessariamente sommabili.

justification​

Registro dei giustificativi del personale. Ogni riga descrive una singola assenza, uno straordinario o un’informazione statistica riferita a un dipendente, con causale, durata, fascia oraria e stato di autorizzazione.

Granularità: Una riga per giustificativo. Ogni riga rappresenta una singola istanza di assenza o presenza per un dipendente in un orario specifico. Un dipendente può avere più giustificativi nello stesso giorno.

Quando usarla: È la tabella da usare per analisi di dettaglio su assenze, straordinari, permessi, causali e relativo workflow autorizzativo.

Relazioni​

Tabella collegataCondizione di joinSignificato
employeejustification.employee = employee.idDipendente a cui appartiene il giustificativo
shiftjustification.employee = shift.employee AND justification.day = shift.dayGiustificativi del dipendente nello stesso giorno
divisionjustification.costcenter @> to_jsonb(division.costcenter)I giustificativi possono essere associati a un centro di costo corrispondente a una unità operativa

Campi​

CampoTipoNullableDescrizioneDettagli
employeevarcharSìMatricola del dipendente a cui appartiene il giustificativo. Si collega a employee.id.—
daydateSìData a cui si riferisce il giustificativo.Formato: YYYY-MM-DD
starttimestampSìTimestamp di inizio della fascia coperta dal giustificativo.Formato: YYYY-MM-DD HH:MM:SS.000
endtimestampSìTimestamp di fine della fascia coperta dal giustificativo.Formato: YYYY-MM-DD HH:MM:SS.000
durationint2SìDurata del giustificativo, in minuti.Unità: minuti
typevarcharSìCategoria generale del giustificativo: ABSENCE, OVERTIME oppure STAT secondo i Valori.Valori: ABSENCE — assenza
OVERTIME — straordinario
STAT — Statistico: qualifica che non modifica il conteggio delle ore lavorate del dipendente, ma fornisce un'informazione aggiuntiva (es. che le ore sono state lavorate da remoto).
causevarcharSìCausale di dettaglio. I codici disponibili sono configurabili e possono cambiare da cliente a cliente.—
hourlybooleanSìIndica se il giustificativo è orario (true) o giornaliero (false).—
prodbooleanSìIndica se le ore del giustificativo sono considerate produttive.Valori: true
false
zmbooleanSìFlag “zero minute”. Quando è true, il giustificativo non modifica il conteggio delle ore, anche se duration può essere maggiore di zero.—
createdtimestampSìTimestamp di creazione del record nel sistema.Formato: YYYY-MM-DD HH:MM:SS.0000
statusvarchar(32)SìStato del processo di autorizzazione: AUTHORIZED, PENDING o REJECTED secondo i Valori.Valori: AUTHORIZED — autorizzato
PENDING — in attesa di autorizzazione
REJECTED — rifiutato
last_managervarcharSìIdentificativo dell’ultimo responsabile o servizio che ha gestito il giustificativo, ad esempio per autorizzazione o modifica.—
costcenterjsonbSìArray JSONB di codici di centro di costo associati al giustificativo.—
minutesjsonbSìOggetto JSONB con una coppia chiave/valore. Il valore numerico rappresenta la durata in minuti e corrisponde a duration.Esempio: {"803": 360}

Struttura dei campi JSONB​

Struttura di costcenter Array di stringhe. Centri di costo associati al giustificativo. Stringa identificativa del centro di costo

Struttura di minutes Oggetto con una singola coppia chiave-valore: il secondo valore corrisponde alla durata in minuti (uguale al campo duration).

ProprietàSignificato
chiaveCentro di costo
valoreDurata in minuti (intero)

Esempio:

{
"803": 360
}

Note operative​

attenzione

Un dipendente può avere più giustificativi nello stesso giorno. Un join diretto con shift su dipendente e data può quindi produrre più righe per lo stesso turno.

  • type = 'STAT' rappresenta un’informazione statistica che non modifica il conteggio delle ore.
  • cause causale.
  • costcenter è un array JSONB; la relazione con division usa justification.costcenter @> to_jsonb(division.costcenter).

employee​

Anagrafica dei dipendenti. Contiene i dati identificativi di base e diversi attributi storicizzati in JSONB: unità operative, unità operative esterne, posizioni, competenze, sezioni e contratti.

Granularità: Una riga per dipendente (addetto). Ogni riga rappresenta un singolo lavoratore registrato nel sistema.

Quando usarla: Usare questa tabella come dimensione anagrafica per arricchire turni, giustificativi, timbrature, breakdown e attività. Per gli attributi storicizzati, considerare sempre l’intervallo di validità start/end.

Relazioni​

Tabella collegataCondizione di joinSignificato
shiftemployee.id = shift.employeeTutti i turni lavorati da un dipendente
activityemployee.id = activity.employeeAttività associate al dipendente
justificationemployee.id = justification.employeeGiustificativi di assenza o straordinario
breakdownemployee.id = breakdown.employeeDipendente a cui appartiene il breakdown
clockingemployee.id = clocking.employeeDipendente che ha effettuato la timbratura
divisionjsonb_array_elements(employee.divisions)->>'id' = division.costcenterLe unità operative di appartenenza dei dipendenti in employee.divisions referenziano costcenter
divisionjsonb_array_elements(employee.sections)->>'id' = jsonb_array_elements(division.sections)->>'costCenter'Le sezioni dei dipendenti referenziano le unità operative tramite employee.sections.id = division.sections.costCenter

Campi​

CampoTipoNullableDescrizioneDettagli
idvarcharNoMatricola univoca del dipendente. È la chiave primaria dell’anagrafica e il riferimento usato dalle principali tabelle operative.Chiave primaria
first_namevarcharSìNome del dipendente.—
last_namevarcharSìCognome del dipendente.—
birthdateSìData di nascita del dipendente.Formato: YYYY-MM-DD
picvarcharSìCodice fiscale del dipendente (Personal Identification Code).—
sexvarcharSìValore anagrafico del sesso. I Valori sono m e f.Valori: m — maschio
f — femmina
aclvarcharSìRuolo/livello di accesso dell’utente nel sistema. Tra gli esempi documentati: Addetto e Responsabile.Esempi: Addetto, Responsabile
supervisorbooleanSìIndica se il dipendente ha il ruolo di supervisore.Valori: true
false
enabledbooleanSìIndica se il dipendente è abilitato nel sistema.Valori: true — attivo
false — disattivo
propertiesjsonbSìOggetto JSONB con attributi aggiuntivi configurati per il cliente. Chiavi e significato non sono standard.—
divisionsjsonbSìArray JSONB con lo storico delle unità operative di appartenenza. Ogni elemento contiene id, start, end e data; end = "" indica un’appartenenza attiva.Esempio: [{"id": "87", "end": "2023-09-18", "start": "2020-07-20", "data": {}}]
external_divisionsjsonbSìArray JSONB con lo storico delle unità operative esterne. La struttura è analoga a divisions e lo stesso id può ricomparire in periodi diversi.Esempio: [{"id": "41", "end": "2022-11-01", "start": "2022-06-01", "data": {}}]
positionsjsonbSìArray JSONB con lo storico delle posizioni o ruoli lavorativi del dipendente. end = "" indica una posizione attiva.—
skillsjsonbSìArray JSONB con competenze, abilitazioni o gruppi associati al dipendente nel tempo. Più skill possono essere attive contemporaneamente.—
sectionsjsonbSìArray JSONB con lo storico delle sezioni di appartenenza del dipendente.—
contractsjsonbSìArray JSONB con lo storico dei contratti. Oltre alle date di validità, data può contenere costo orario, giorni lavorativi settimanali, minuti settimanali e percentuale part-time.Esempio: [{"id": "C_40.00h_5gg", "end": "", "start": "2020-02-14", "data": {"hourly_cost": null, "working_days": 5, "minutes_per_week": 2400, "parttime_percentage": null}}]

Struttura dei campi JSONB​

properties

Oggetto con proprietà custom configurate per cliente. Le chiavi principali variano in base alla configurazione del cliente.

Struttura di divisions Array di oggetti. unità operative di appartenenza del dipendente nel tempo. end = '' indica appartenenza attiva.

ProprietàSignificato
idID della unità operativa (riferimento alla tabella division)
startData inizio appartenenza (YYYY-MM-DD)
endData fine. Stringa vuota '' indica attivo.
dataOggetto dati aggiuntivi (può essere vuoto )

Esempio:

[
{
"id": "87",
"end": "2023-09-18",
"start": "2020-07-20",
"data": {}
}
]

Struttura di external_divisions Array di oggetti. unità operative esterne di appartenenza del dipendente. Struttura identica a divisions. Può contenere più record con lo stesso id in periodi diversi.

ProprietàSignificato
idID della unità operativa esterna
startData inizio (YYYY-MM-DD)
endData fine. Stringa vuota '' indica attivo.
dataOggetto dati aggiuntivi (può essere vuoto )

Esempio:

[
{
"id": "41",
"end": "2022-11-01",
"start": "2022-06-01",
"data": {}
}
]

Struttura di positions Array di oggetti. Posizioni o ruoli lavorativi ricoperti dal dipendente nel tempo. end = '' indica posizione attiva.

ProprietàSignificato
idNome della posizione
startData inizio (YYYY-MM-DD)
endData fine. Stringa vuota '' indica attivo.

Struttura di skills Array di oggetti. Competenze, abilitazioni o gruppi di appartenenza del dipendente. end = '' indica skill attiva. Un dipendente può avere più skill attive contemporaneamente.

ProprietàSignificato
idNome della skill o gruppo
startData inizio (YYYY-MM-DD)
endData fine. Stringa vuota '' indica attivo.

Struttura di sections Array di oggetti. Sezioni di appartenenza del dipendente.

ProprietàSignificato
idCodice sezione
startData inizio (YYYY-MM-DD)
endData fine. Stringa vuota '' indica attivo.

Struttura di contracts Array di oggetti. Storico dei contratti del dipendente nel tempo.

ProprietàSignificato
idIdentificativo/codice del contratto (es. 'C_40.00h_5gg')
startData inizio (YYYY-MM-DD)
endData fine (stringa vuota oppure '7999-01-01' se indeterminato)
data.hourly_costCosto orario associato al contratto
data.working_daysGiorni lavorativi settimanali previsti
data.minutes_per_weekMinuti settimanali previsti dal contratto
data.parttime_percentagePercentuale di part-time (null se full-time o non specificato, può essere null anche se il contratto è part time.)

Esempio:

[
{
"id": "C_40.00h_5gg",
"end": "",
"start": "2020-02-14",
"data": {
"hourly_cost": null,
"working_days": 5,
"minutes_per_week": 2400,
"parttime_percentage": null
}
}
]

Note operative​

attenzione

I campi JSONB contengono storico. Se una query estrae tutti gli elementi senza filtrare l’intervallo di validità, lo stesso dipendente può comparire più volte.

  • divisions, external_divisions, positions, skills, sections e contracts sono storicizzati all’interno di array JSONB.
  • Per divisions, external_divisions, positions, skills e sections,si usa end = '' per indicare un’associazione attiva.
  • Per contracts, una data finale vuota oppure 7999-01-01 può rappresentare un contratto senza una fine ordinaria, dipende dal sistema con cui StaffRoster è integrato.
  • properties e i valori delle skill sono client-specific.

division​

Anagrafica della struttura organizzativa. Ogni riga rappresenta una unità operativa identificata dal proprio centro di costo e collegabile alla unità operativa madre tramite parent.

Granularità: Una riga per unità operativa organizzativa. Ogni riga rappresenta un nodo della gerarchia aziendale a tre livelli: azienda radice, unità produttiva, area operativa.

Quando usarla: Usare questa tabella per tradurre i codici di unità operativa/centro di costo in nomi e per ricostruire la gerarchia organizzativa. Le sezioni interne sono memorizzate nel campo JSONB sections.

Relazioni​

Tabella collegataCondizione di joinSignificato
employee (divisions JSONB)jsonb_array_elements(employee.divisions)->>'id' = division.costcenterLe unità operative di appartenenza dei dipendenti in employee.divisions referenziano costcenter
employee (sections JSONB)jsonb_array_elements(division.sections)->>'costCenter' = jsonb_array_elements(employee.sections)->>'id'Le sezioni dei dipendenti referenziano le unità operative tramite employee.sections.id = division.sections.costCenter
shiftshift.division = division.costcenterI turni sono associati a una unità operativa tramite costcenter
justificationjustification.costcenter @> to_jsonb(division.costcenter)I giustificativi possono essere associati a un centro di costo corrispondente a una unità operativa

Campi​

CampoTipoNullableDescrizioneDettagli
costcentervarcharSìIdentificativo della unità operativa e chiave primaria. È il codice richiamato da diversi campi di unità operativa/centro di costo nelle altre tabelle.Chiave primaria
namevarcharSìNome leggibile della unità operativa.—
parentvarcharSìCodice costcenter della unità operativa padre nella gerarchia organizzativa.—
propertiesjsonbSìOggetto JSONB con informazioni aggiuntive della unità operativa. Le proprietà dipendono dalla configurazione del cliente.—
tagsjsonbSìArray JSONB di etichette che classificano la unità operativa.—
sectionsjsonbSìArray JSONB di sezioni interne alla unità operativa. Ogni elemento contiene nome, stato di abilitazione e centro di costo della sezione.—

Struttura dei campi JSONB​

properties

Oggetto contenente informazioni aggiuntive relative alla unità operativa, come area e identificativi del punto vendita.

tags

Array di stringhe. Etichette che identificano il tipo operativo della unità operativa.

Struttura di sections Array di oggetti. Ogni oggetto rappresenta una sezione appartenente a questa unità operativa, con nome, flag di abilitazione e centro di costo associato.

ProprietàSignificato
nameNome della sezione
enabledBooleano, indica se la sezione è attiva
costCenterId/centro di costo della sezione (stringa)

Note operative​

  • parent consente di risalire alla unità operativa madre tramite un self-join su division.costcenter.
  • Le sezioni interne sono nel JSONB sections; sections[].costCenter è il codice usato per collegare le sezioni del dipendente.

clocking​

Registro delle singole timbrature. Ogni riga corrisponde a un evento rilevato o inserito manualmente, con timestamp preciso, dispositivo e flag di validità.

Granularità: Una riga per timbratura. Ogni riga rappresenta un singolo evento di timbratura di un dipendente: entrata, uscita, inizio pausa o fine pausa. Un dipendente ha più timbrature per giorno.

Quando usarla: Usare clocking per analizzare il dettaglio degli eventi di presenza: sequenza delle timbrature, inserimenti manuali, eventi invalidati o anomalie. Per i totali giornalieri già calcolati è normalmente più semplice usare shift.

Relazioni​

Tabella collegataCondizione di joinSignificato
employeeclocking.employee = employee.idDipendente che ha effettuato la timbratura
shiftclocking.employee = shift.employee AND clocking.day = shift.dayTurno del dipendente nello stesso giorno

Campi​

CampoTipoNullableDescrizioneDettagli
employeevarcharSìMatricola del dipendente che ha effettuato la timbratura. Si collega a employee.id.—
daydateSìData della timbratura; è il riferimento pratico per filtri per periodo.Formato: YYYY-MM-DD
tstimestampSìTimestamp esatto dell’evento di timbratura.Formato: YYYY-MM-DD HH:MM:SS.000
actionvarcharSìCodice/tipo dell’evento di timbratura.—
clockvarcharSìIdentificativo del dispositivo o terminale usato per la timbratura.Esempio: SR-07022152, SR-07088236
manualbooleanSìIndica se la timbratura è stata inserita manualmente (true) invece di essere acquisita automaticamente (false).Valori: true — manuale
false — automatica
enabledbooleanSìIndica se la timbratura è attiva/valida. Un valore false può rappresentare un evento annullato o invalidato.Valori: true — attiva
false — disattivata
ingressbooleanSìFlag che distingue gli eventi di ingresso in turno (true) dagli eventi non classificati come ingresso (false).Valori: true — ingresso
false — uscita

Note operative​

attenzione

Un dipendente può avere molte timbrature nello stesso giorno. Non sommare misure provenienti da shift dopo un join diretto con clocking senza gestire la moltiplicazione delle righe.

  • Usare enabled per distinguere le timbrature attive da quelle annullate/invalidate, quando l’analisi richiede solo eventi validi.

breakdown​

Scomposizione analitica delle ore giornaliere del dipendente. Ogni riga rappresenta una singola componente della giornata, con causale, fascia oraria, durata ed eventuale indicazione di anomalia.

Granularità: Una riga per componente di breakdown giornaliero per dipendente. Rappresenta la scomposizione delle ore di una giornata lavorativa nelle sue componenti: ore ordinarie, assenze, aggiunte, anomalie. Un dipendente può avere più righe per lo stesso giorno.

Quando usarla: Usare breakdown quando serve capire da quali componenti è formato il totale giornaliero. Per i soli totali di ore ordinarie, assenze e straordinari è preferibile shift.

Relazioni​

Tabella collegataCondizione di joinSignificato
employeebreakdown.employee = employee.idDipendente a cui appartiene il breakdown
shiftbreakdown.employee = shift.employee AND breakdown.day = shift.dayTurno del dipendente nello stesso giorno
justificationbreakdown.employee = justification.employee AND breakdown.day = justification.day AND breakdown.cause = justification.causeGiustificativo corrispondente per la stessa causale

Campi​

CampoTipoNullableDescrizioneDettagli
employeevarcharSìMatricola del dipendente a cui appartiene la componente di breakdown. Si collega a employee.id.—
daydateSìData effettiva della giornata lavorativa a cui appartiene la componente.Formato: YYYY-MM-DD
reldaydateSìData contrattuale di riferimento a cui viene imputata la componente. Di solito coincide con day, ma può differire, ad esempio per turni a cavallo della mezzanotte o registrazioni imputate a una diversa giornata contrattuale.Formato: YYYY-MM-DD
typevarcharSìCategoria generale della componente di breakdown.—
causevarcharSìCausale di dettaglio che identifica la natura della componente oraria.—
starttimestampSìTimestamp di inizio della componente.Formato: YYYY-MM-DD HH:MM:SS.000
endtimestampSìTimestamp di fine della componente.Formato: YYYY-MM-DD HH:MM:SS.000
durationint2SìDurata della componente, in minuti.Unità: minuti
anomalousbooleanSìIndica se la componente è considerata anomala e richiede verifica o intervento.Valori: true — anomalo
false — normale
costcenterjsonbSìArray JSONB dei centri di costo associati alla componente; può contenerne più di uno.—

Struttura dei campi JSONB​

costcenter

Array di stringhe. Centri di costo associati al breakdown. Può contenere più centri di costo.

Note operative​

attenzione

Sono possibili più righe per dipendente e giorno. La colonna relday può differire da day in casi particolari; scegliere esplicitamente quale data rappresenta il criterio temporale dell’analisi.

  • duration è in minuti.
  • Per collegare un breakdown a un giustificativo con maggiore precisione si usano condizioni su employee, day e cause.
  • costcenter può contenere più centri di costo nello stesso array JSONB.

balance​

Dati di pianificazione e bilanciamento del personale. Ogni riga confronta fabbisogno e disponibilità per centro di costo, giorno, fascia oraria, skill, posizione e riferimento al turno.

Granularità: Una riga per combinazione di costcenter, division, shift, giorno, step (fascia oraria), skill e position, che rappresenta il fabbisogno e la disponibilità di personale in quell'intervallo.

Quando usarla: Usare questa tabella per analisi di copertura del fabbisogno: personale richiesto, minimo, previsto e disponibile nelle diverse fasce orarie.

Relazioni​

Tabella collegataCondizione di joinSignificato
divisionbalance.costcenter = division.costcenter

Campi​

CampoTipoNullableDescrizioneDettagli
costcentervarcharSìCentro di costo a cui si riferisce il dato di bilanciamento. Si collega a division.costcenter.—
divisionvarcharSìCodice o descrizione di unità operativa riportato nella riga.—
shiftvarcharSìRiferimento al turno usato nel dato di pianificazione (shift-driven).—
daydateSìData di riferimento del fabbisogno/disponibilità.Formato: YYYY-MM-DD
steptimestampSìTimestamp della fascia o dello slot orario all’interno della giornata.—
skillvarcharSìCompetenza richiesta o associata alla riga. Fa riferimento ai valori usati in employee.skills[].id.—
positionvarcharSìPosizione o ruolo richiesto per la fascia considerata.Esempio: Addetto
requestint4SìNumero di persone richieste/necessarie nello slot indicato da step.—
request_minint4SìSoglia minima di personale richiesta nello slot.—
request_forecastint4SìPrevisione del fabbisogno di personale, distinta dalla richiesta pianificata in request.—
availableint4SìNumero di persone disponibili nello slot.—

Note operative​

  • request rappresenta il fabbisogno pianificato/effettivo; request_min la soglia minima; request_forecast la previsione; available la disponibilità.
  • skill richiama i valori usati negli elementi employee.skills[].id.

activity​

Registro delle attività operative svolte dai dipendenti. Ogni riga descrive una singola attività con fascia oraria, durata, centro di costo, eventuale risorsa associata e flag di produttività.

Granularità: Una riga per attività svolta da un dipendente in un giorno. Ogni riga rappresenta una singola attività con fascia oraria precisa. Un dipendente può avere più attività nello stesso giorno.

Quando usarla: Usare questa tabella per analizzare cosa è stato svolto durante il turno e per quanto tempo. I nomi delle attività e molte proprietà sono specifici per cliente.

Relazioni​

Tabella collegataCondizione di joinSignificato
employeeactivity.employee = employee.idDipendente che ha svolto l'attività
shiftactivity.employee = shift.employee AND activity.day = shift.dayTurno del dipendente nello stesso giorno

Campi​

CampoTipoNullableDescrizioneDettagli
employeevarcharSìMatricola del dipendente che ha svolto l’attività. Si collega a employee.id.Esempio: 1745, 60030, 1555, 1188, 362, 51
daydateSìData in cui è stata svolta l’attività.Formato: YYYY-MM-DD
namevarcharSìNome dell’attività svolta. I valori sono definiti dal cliente e possono cambiare tra installazioni.—
costcenterjsonbSìCentro di costo associato all’attività, memorizzato come JSONB. Può essere un'unità operativa oppure una sezione.—
activitycostcentervarcharSìIl centro di costo associato direttamente all'attività.—
starttimestampSìTimestamp di inizio dell’attività.Formato: YYYY-MM-DD HH:MM:SS.000
endtimestampSìTimestamp di fine dell’attività.Formato: YYYY-MM-DD HH:MM:SS.000
durationint2SìDurata dell’attività in minuti, calcolata a monte e concettualmente equivalente alla differenza tra end e start.Unità: minuti
resourcevarcharSìRisorsa associata all’attività.—
resource_typevarcharSìCategoria generale della risorsa associata all’attività.—
propertiesjsonbSìOggetto JSONB con proprietà aggiuntive dell’attività. La struttura è variabile e specifica per cliente.—
prodboolSìIndica se l’attività è considerata produttiva.Valori: true
false

Note operative​

  • name e properties sono specifici per cliente; verificare i valori reali prima di costruire classificazioni rigide.
  • duration è in minuti.

Pattern PostgreSQL utili​

Filtrare un elemento in un array JSONB​

Per verificare la presenza di un valore in un campo JSONB usare gli operatori JSONB appropriati. Quando serve accedere agli attributi interni di ogni elemento, espandere l’array:

SELECT
e.id,
skill_item ->> 'id' AS skill,
skill_item
FROM public.employee AS e
CROSS JOIN LATERAL jsonb_array_elements(e.skills) AS s(skill_item);

Recuperare solo gli elementi storici validi in una data​

Gli array storicizzati contengono date come testo nel JSONB. Il filtro deve considerare sia start sia end, convertendo esplicitamente in date quando necessario. La regola precisa dipende dal campo; una stringa vuota rappresenta fine aperta.

Confrontare una riga con la precedente​

Le Window Functions permettono confronti temporali senza self-join. Pattern generico:

SELECT
employee,
day,
ord,
LAG(ord) OVER (PARTITION BY employee ORDER BY day) AS previous_ord
FROM public.shift;

Esempi​

Esempio 1​

Descrizione

Contratti dei dipendenti che hanno fatto pause più lunghe di un'ora

Query SQL

SELECT
e.id AS employee_id,
e.first_name,
e.last_name,
c.contract->>'id' AS contract_id
FROM shift s
JOIN employee e
ON e.id = s.employee
LEFT JOIN LATERAL (
SELECT contract
FROM jsonb_array_elements(COALESCE(e.contracts, '[]'::jsonb)) AS contract
WHERE contract->>'start' <= s.day::text
AND (
COALESCE(contract->>'end', '') = ''
OR contract->>'end' >= s.day::text
)
) AS c(contract)
ON TRUE
WHERE s.bduration > 60
GROUP BY
e.id,
e.first_name,
e.last_name,
c.contract->>'id'
ORDER BY
e.id;

Estratto del risultato

employee_idfirst_namelast_namecontract_id
100GIANNIROSSIC_40.00h_6gg
156JOHN DPATERNOC_40.00h_6gg
1568JEAN MARCSIMONETTIC_24.00h_3gg

Esempio 2​

Descrizione

Dipendenti che, al 3 Settembre 2026, ricoprono la posizione di addetto da almeno 10 anni.

Query SQL

SELECT
e.id,
e.first_name,
e.last_name,
(p->>'start')::date AS position_start
FROM employee AS e
CROSS JOIN LATERAL jsonb_array_elements(e.positions) AS p
WHERE e.enabled IS TRUE
AND p->>'id' = 'Addetto'
AND p->>'end' = ''
AND (p->>'start')::date <= DATE '2026-09-03' - INTERVAL '10 years';

Estratto del risultato

idfirst_namelast_nameposition_start
111GINOGAROFALO2011-07-11
222FRANCESCAFAGGIN2012-03-01
13ALICEDE MARCHI2009-04-01
888MARIA LUISAANTONI2008-05-12

Esempio 3​

Descrizione

Costo orario medio dei responsabili.

Query SQL

SELECT AVG(REPLACE(e.properties->'Costo Orario'->0->>'value', ',', '.')::numeric) AS average_hourly_cost
FROM employee e
WHERE e.supervisor IS TRUE;

Estratto del risultato

average_hourly_cost
11.0855555555555556

Esempio 4​

Descrizione

Dipendenti hanno effettuato almeno 10 ore di straordinario raggruppati per mese nel 2026

Query SQL

WITH months AS (
SELECT generate_series(
'2026-01-01',
'2026-12-01',
INTERVAL '1 month'
)::date AS month
),
overtime AS (
SELECT
date_trunc('month', s.day::date)::date AS month,
s.employee,
SUM(s.ovt) AS overtime_minutes
FROM shift AS s
WHERE s.day::date >= '2026-01-01'
AND s.day::date < '2027-01-01'
GROUP BY date_trunc('month', s.day::date)::date, s.employee
HAVING SUM(s.ovt) >= 300
)
SELECT
m.month,
COUNT(DISTINCT st.employee) AS employee_count
FROM months m
LEFT JOIN overtime st
ON st.month = m.month
GROUP BY m.month
ORDER BY m.month;

Estratto del risultato

monthemployee_count
2026-08-016
2026-09-011

Esempio 5​

Descrizione

Per ogni unità operativa, mostrami il rank di 3 dipendenti in base al totale di ore lavorate nel 2026.

Query SQL

WITH employee_hours AS (
SELECT
s.division AS division_costcenter,
d.name AS division_name,
s.employee AS employee_id,
e.first_name,
e.last_name,
SUM(COALESCE(s.ord, 0) + COALESCE(s.ovt, 0) - COALESCE(s.abs, 0)) / 60.0 AS total_hours
FROM shift s
JOIN employee e
ON e.id = s.employee
LEFT JOIN division d
ON d.costcenter = s.division
WHERE s.day::date >= '2026-01-01'
AND s.day::date < '2027-01-01'
GROUP BY
s.division,
d.name,
s.employee,
e.first_name,
e.last_name
),
ranked_employees AS (
SELECT
division_costcenter,
division_name,
employee_id,
first_name,
last_name,
total_hours,
ROW_NUMBER() OVER (
PARTITION BY division_costcenter
ORDER BY total_hours DESC, employee_id
) AS employee_rank
FROM employee_hours
)
SELECT
division_costcenter,
division_name,
employee_rank,
employee_id,
first_name,
last_name,
total_hours
FROM ranked_employees
WHERE employee_rank <= 3
ORDER BY division_costcenter, employee_rank;

Estratto del risultato

division_costcenterdivision_nameemployee_rankemployee_idfirst_namelast_nametotal_hours
126Reparto A11628MARIODE CINTI389.00
126Reparto A21193MARIACABAS355.50
126Reparto A31597ROBERTAPIRELLI353.50
128Reparto B1845ANNANONI57.50
128Reparto B21811JEANCHINZA36.00
128Reparto B31383GIANNICOSI12.00
154Reparto C1704GIULIODELLOA430.50
154Reparto C21313DAVIDCOSTRA405.00
154Reparto C31500BENGELLI328.00

Esempio 6​

Descrizione

Le 5 skill più comuni tra i dipendenti

Query SQL

SELECT
skill->>'id' AS skill,
COUNT(DISTINCT e.id) AS employee_count
FROM employee e
CROSS JOIN LATERAL jsonb_array_elements(e.skills) AS skill
WHERE skill->>'end' = ''
GROUP BY skill->>'id'
ORDER BY employee_count DESC, skill
LIMIT 5;

Estratto del risultato

skillemployee_count
Chiavi52
Cassa45
Taglio22
Fresatura20
Pulizia15

Esempio 7​

Descrizione

Dipendenti con contratto full-time con più di 50h di assenza nel mese di Settembre 2026

Query SQL

SELECT
e.id AS employee_id,
e.first_name,
e.last_name,
SUM(s.abs) / 60.0 AS absence_hours
FROM employee e
JOIN shift s
ON s.employee = e.id
WHERE EXISTS (
SELECT 1
FROM jsonb_array_elements(e.contracts) AS c
WHERE (c->>'id' ILIKE 'C_40.00h%' OR (c->'data'->>'minutes_per_week')::int = 2400)
AND c->>'start' <= '2026-09-01'
AND (
c->>'end' = ''
OR c->>'end' >= '2026-09-30'
)
)
GROUP BY e.id, e.first_name, e.last_name
HAVING SUM(s.abs) > 3000
ORDER BY absence_hours DESC, e.last_name, e.first_name;

Estratto del risultato

employee_idfirst_namelast_nameabsence_hours
592ROBROGITU325.00
122SANPERRI277.00
895ANNAIONICI253.00

Esempio 8​

Descrizione

Dipendenti con contratto full-time con più di 50h di assenza nel mese di Settembre 2026

Query SQL

SELECT
b.day,
concat(to_char(b.step, 'HH24:MI'),'-',to_char(b.step + INTERVAL '30 minutes', 'HH24:MI')),
b.request - b.available as "offset"
FROM balance b
WHERE b.division = '2'
AND b."day" BETWEEN '2026-10-01' AND '2026-10-07'
AND b.request > b.available;

Estratto del risultato

dayconcatoffset
2026-10-0111:00-11:301
2026-10-0109:30-10:001
2026-10-0114:00-14:302
2026-10-0110:00-10:301
2026-10-0112:30-13:002
2026-10-0113:30-14:002

HR Analytics da StaffRoster

È possibile eseguire estrazioni direttamente da StaffRoster tramite la pagina Console Analytics e esportare i dati in PDF/XLS.

Se il Modulo AI Report è attivo è anche possibile definire reportistica utilizzando il database di HR Analytics.

AI Report su HR Analytics​

Tipi di report possibili:

  • JSON da Analytics
  • PDF da Analytics
  • XLS da Analytics
attenzione

I report derivanti dal database di Analytics saranno disponibile per i soli utenti che possono vedere dati fuori dal proprio cono di visibilità.

Report parametrizzati​

I placeholder sono valori racchiusi tra doppie parentesi graffe e vengono sostituiti prima dell'esecuzione dello script del report. Le chiavi distinguono maiuscole e minuscole e possono contenere soltanto lettere, numeri e _.

Usare un parametro obbligatorio con la sintassi {{CHIAVE}}:

WHERE stato = '{{STATO}}'

Per rendere il parametro facoltativo, dichiarare il valore di default dopo ::

WHERE tipo = '{{TIPO:ORDINARIO}}'

Quando si salva il report, l'editor rileva i placeholder e mostra i parametri nella sezione Parametri. Per ogni parametro si possono indicare valori possibili, separandoli con una virgola. In fase di esecuzione l'utente trova un selettore se sono stati configurati valori possibili; altrimenti può digitare il valore. I parametri senza default sono obbligatori, mentre lasciando vuoto un parametro con default viene usato il valore dichiarato nello script.

La stessa chiave può comparire più volte, ma deve avere sempre la stessa forma: non è possibile alternare {{CHIAVE}} e {{CHIAVE:default}}, né dichiarare default diversi. Se sono presenti valori possibili, il default deve appartenere alla lista.

Il valore di un parametro utente è un valore semplice, non una lista SQL: nel codice SQL le virgolette e gli altri elementi di sintassi restano a carico dello script, come nell'esempio precedente. I valori accettati contengono lettere, numeri, spazi e i caratteri _, ., /, :, -.

Parametri speciali​

I parametri speciali sono riconosciuti automaticamente dall'editor e popolati dal backend. Non appaiono tra i normali campi parametro.

PlaceholderValore sostituitoInterfaccia di esecuzioneUso previsto
{{FROM}}Data iniziale del periodo in formato yyyy-MM-dd.Selettore periodo.Usarlo insieme a {{TO}}.
{{TO}}Data finale del periodo in formato yyyy-MM-dd.Selettore periodo.Usarlo insieme a {{FROM}}.
{{DIVISIONS}}Centri di costo dei reparti selezionati, ad esempio ('CDC1','CDC2').Selettore reparti.Usarlo direttamente in una clausola IN.
{{EMPLOYEES}}Matricole degli addetti trovati con i filtri, ad esempio ('123','AS2').Selettore reparti e filtri addetti.Usarlo direttamente in una clausola IN.

DIVISIONS ed EMPLOYEES non ammettono un default: usare esclusivamente {{DIVISIONS}} e {{EMPLOYEES}}. Anche FROM e TO vanno dichiarati nella forma senza default e sempre in coppia.

Nei report normali rimangono disponibili i filtri ordinari già previsti. Nei report BI, invece, il periodo è visibile se sono presenti entrambi FROM e TO; il selettore reparti è visibile con DIVISIONS oppure EMPLOYEES; i filtri addetti sono visibili con EMPLOYEES.

Esempio​

Il seguente frammento usa il periodo, il reparto, gli addetti e un parametro definito dall'autore:

SELECT matricola, data, centro_costo
FROM presenze
WHERE data >= '{{FROM}}'
AND data < '{{TO}}'
AND centro_costo IN {{DIVISIONS}}
AND matricola IN {{EMPLOYEES}}
AND stato = '{{STATO:ATTIVO}}'

I valori di DIVISIONS ed EMPLOYEES sono già serializzati dal backend come insiemi SQL, comprese le virgolette e l'escape degli apostrofi: non devono essere aggiunte altre virgolette né parentesi attorno al placeholder. L'esecuzione viene rifiutata se non è selezionato alcun reparto per DIVISIONS oppure se la ricerca di EMPLOYEES non restituisce addetti con matricola valida.