Salta el contingut

Esdeveniments programats (Scheduled Events / Jobs)

Què són els esdeveniments programats?

Un esdeveniment programat (o job, en terminologia de molts SGBD) és una tasca SQL que s'executa automàticament en moments predefinits, sense intervenció humana. És l'equivalent dels cron jobs del sistema operatiu, però integrat dins del propi SGBD, amb accés directe a les dades i a tota la infraestructura transaccional.

Mentre que els triggers reaccionen a operacions de dades i els procediments es criden explícitament, els esdeveniments programats funcionen orientats al temps: cada hora, cada dia a les 3:00, cada primer diumenge del mes.

gantt
    title Exemple de planificació de jobs en un SGBD
    dateFormat HH:mm
    axisFormat %H:%M

    section Diàriament
    Neteja de sessions expirades    :00:30, 5m
    Actualitzacio estadistiques      :02:00, 15m
    Arxivat de registres antics     :03:00, 30m
    Generació d'informes            :05:00, 20m

    section Cada hora
    Refresc de vistes materialitzades :00:00, 5m
    Verificacio integritat           :00:30, 3m

Casos d'ús habituals

Cas d'ús Freqüència típica Descripció
Neteja de registres antics Diàriament Eliminar o arxivar files que superen un cert temps de vida.
Arxivat de dades històriques Setmanalment Moure dades antigues a taules d'arxiu o bases de dades d'historial.
Actualització d'estadístiques Diàriament Refrescar les estadístiques del planificador per mantenir plans d'execució òptims.
Refresc de vistes materialitzades Horari / Diari Actualitzar vistes que agreguen grans volums de dades.
Generació d'informes Diàriament Calcular i desar indicadors KPI, recomptes, totals.
Backup lògic parcial Diàriament Exportar taules crítiques com a mesura addicional als backups del sistema.
Comprovació d'integritat Setmanalment Verificar restriccions, detectar orfes, validar coherència.
Enviament d'alertes Horari Detectar situacions anòmales i registrar-les o notificar-les.
Rotació de logs Setmanalment Comprimir o eliminar logs de la pròpia base de dades.

Mecanismes per a cada motor

Visió general

Motor Mecanisme Integrat? Configuració necessària
PostgreSQL pg_cron (extensió) No (extensió externa) Instal·lar l'extensió + postgresql.conf
MySQL / MariaDB EVENT SCHEDULER Activar globalment
SQL Server SQL Server Agent Sí (servei extern) Servei ha d'estar en execució
Oracle DBMS_SCHEDULER Actiu per defecte

PostgreSQL: pg_cron

pg_cron és una extensió de PostgreSQL que permet programar tasques usant la sintaxi de cron estàndard, directament des de SQL. Les tasques es registren en una taula de la BD postgres i s'executen en el procés de background de PostgreSQL.

Instal·lació i configuració

# En sistemes Debian/Ubuntu:
sudo apt-get install postgresql-16-cron

# En CentOS/RHEL:
sudo yum install pg_cron_16
# A postgresql.conf, afegir:
shared_preload_libraries = 'pg_cron'
cron.database_name = 'postgres'   # BD on s'emmagatzemen els jobs
-- Reiniciar PostgreSQL i crear l'extensió:
CREATE EXTENSION pg_cron;

-- Opcional: permetre que altres usuaris programin tasques
GRANT USAGE ON SCHEMA cron TO nom_usuari;

Sintaxi cron

┌──────── minut (0-59)
│ ┌────── hora (0-23)
│ │ ┌──── dia del mes (1-31)
│ │ │ ┌── mes (1-12)
│ │ │ │ ┌ dia de la setmana (0=diumenge, 6=dissabte)
│ │ │ │ │
* * * * *

Exemples: - 0 3 * * * → cada dia a les 3:00 - */5 * * * * → cada 5 minuts - 0 2 * * 0 → cada diumenge a les 2:00 - 0 */6 * * * → cada 6 hores (0:00, 6:00, 12:00, 18:00) - 30 23 1 * * → el primer de cada mes a les 23:30

Gestió de jobs

-- Crear un job
SELECT cron.schedule(
    'neteja-sessions-diaria',       -- nom del job
    '0 3 * * *',                    -- expressió cron
    $$DELETE FROM sessions WHERE expires_at < NOW()$$
);

-- Crear un job que executa un procediment
SELECT cron.schedule(
    'arxivat-setmanal',
    '0 2 * * 0',  -- diumenges a les 2:00
    $$CALL arxivar_comandes_antigues(NULL)$$
);

-- Llistar jobs existents
SELECT jobid, jobname, schedule, command, active
FROM cron.job
ORDER BY jobname;

-- Veure l'historial d'execucions
SELECT jobid, start_time, end_time, status, return_message
FROM cron.job_run_details
ORDER BY start_time DESC
LIMIT 20;

-- Modificar l'horari d'un job (per nom)
SELECT cron.alter_job(
    job_id := (SELECT jobid FROM cron.job WHERE jobname = 'neteja-sessions-diaria'),
    schedule := '0 4 * * *'  -- canviar de les 3:00 a les 4:00
);

-- Deshabilitar un job sense eliminar-lo
SELECT cron.alter_job(
    job_id := (SELECT jobid FROM cron.job WHERE jobname = 'neteja-sessions-diaria'),
    active := false
);

-- Eliminar un job
SELECT cron.unschedule('neteja-sessions-diaria');
-- o per ID:
SELECT cron.unschedule(jobid)
FROM cron.job
WHERE jobname = 'arxivat-setmanal';

MySQL / MariaDB: EVENT SCHEDULER

MySQL té un planificador d'esdeveniments integrat, l'EVENT SCHEDULER, que s'activa com a procés de background del servidor. Els events es defineixen amb la instrucció CREATE EVENT.

Habilitar l'Event Scheduler

-- Verificar l'estat actual
SHOW VARIABLES LIKE 'event_scheduler';

-- Habilitar per a la sessió actual (no persistent)
SET GLOBAL event_scheduler = ON;

-- Per fer-ho persistent, afegir a my.cnf / my.ini:
-- [mysqld]
-- event_scheduler = ON

-- Verificar que està actiu
SHOW PROCESSLIST;  -- Ha d'aparèixer "Daemon" amb "Waiting for next activation"

Creació i gestió d'events

-- Event que s'executa una sola vegada (AT)
CREATE EVENT neteja_puntual
ON SCHEDULE AT NOW() + INTERVAL 10 MINUTE
DO
    DELETE FROM log_errors WHERE data < DATE_SUB(NOW(), INTERVAL 30 DAY);

-- Event recurrent cada dia a les 3:00
CREATE EVENT neteja_diaria
ON SCHEDULE EVERY 1 DAY
STARTS (TIMESTAMP(CURRENT_DATE) + INTERVAL 3 HOUR)
DO
    DELETE FROM sessions WHERE expires_at < NOW();

-- Event recurrent amb cos de bloc BEGIN...END
DELIMITER //

CREATE EVENT arxivat_mensual
ON SCHEDULE EVERY 1 MONTH
STARTS '2026-04-01 02:00:00'
COMMENT 'Arxiva comandes completades fa més d''un any'
DO
BEGIN
    INSERT INTO comandes_arxiu
    SELECT * FROM comandes
    WHERE estat = 'completada'
      AND data_comanda < DATE_SUB(NOW(), INTERVAL 1 YEAR);

    DELETE FROM comandes
    WHERE estat = 'completada'
      AND data_comanda < DATE_SUB(NOW(), INTERVAL 1 YEAR);
END //

DELIMITER ;

-- Modificar un event
ALTER EVENT neteja_diaria
ON SCHEDULE EVERY 1 DAY
STARTS (TIMESTAMP(CURRENT_DATE) + INTERVAL 4 HOUR);  -- canviar hora

-- Deshabilitar un event
ALTER EVENT neteja_diaria DISABLE;

-- Tornar a habilitar
ALTER EVENT neteja_diaria ENABLE;

-- Eliminar un event
DROP EVENT IF EXISTS neteja_diaria;

-- Llistar events
SHOW EVENTS FROM nom_base_de_dades;

-- Informació detallada
SELECT event_name, event_type, execute_at, interval_value,
       interval_field, status, last_executed
FROM information_schema.EVENTS
WHERE event_schema = DATABASE();

SQL Server: SQL Server Agent

SQL Server Agent és un servei de Windows independent que s'executa junt al motor SQL Server. Gestiona Jobs (tasques), cadascun format per un o més Steps (passos) i associat a un o més Schedules (horaris). Es pot configurar via GUI (SQL Server Management Studio) o via procediments de sistema T-SQL.

Verificació i activació del servei

# Des de PowerShell / línia d'ordres de Windows
sc query SQLSERVERAGENT
net start SQLSERVERAGENT

# O des de SQL Server Configuration Manager (GUI)

Creació de jobs via T-SQL

-- Pas 1: Crear el job
USE msdb;
GO

EXEC sp_add_job
    @job_name       = N'Neteja diari de sessions',
    @description    = N'Elimina sessions expirades cada dia a les 3:00',
    @enabled        = 1,
    @notify_level_eventlog = 2;  -- Registrar errors a l'Event Log de Windows

-- Pas 2: Afegir un step (pas) al job
EXEC sp_add_jobstep
    @job_name    = N'Neteja diari de sessions',
    @step_name   = N'Eliminar sessions expirades',
    @subsystem   = N'TSQL',
    @command     = N'DELETE FROM sessions WHERE expires_at < GETDATE();',
    @database_name = N'la_meva_bd',
    @on_success_action = 1,  -- 1 = Continuar al next step
    @on_fail_action    = 2;  -- 2 = Finalitzar amb error

-- Pas 3: Crear un horari
EXEC sp_add_schedule
    @schedule_name    = N'Cada dia a les 3:00',
    @freq_type        = 4,      -- 4 = Diari
    @freq_interval    = 1,      -- Cada 1 dia
    @active_start_time = 030000; -- 03:00:00

-- Pas 4: Associar l'horari al job
EXEC sp_attach_schedule
    @job_name      = N'Neteja diari de sessions',
    @schedule_name = N'Cada dia a les 3:00';

-- Pas 5: Registrar el job al servidor
EXEC sp_add_jobserver
    @job_name = N'Neteja diari de sessions';

GO

-- Executar el job manualment per fer una prova
EXEC sp_start_job @job_name = N'Neteja diari de sessions';

-- Veure l'historial d'execucions
SELECT
    j.name AS job_nom,
    h.run_date,
    h.run_time,
    h.run_duration,
    CASE h.run_status
        WHEN 0 THEN 'Fallat'
        WHEN 1 THEN 'Correcte'
        WHEN 2 THEN 'Reintentant'
        WHEN 3 THEN 'Cancel·lat'
    END AS estat,
    h.message
FROM msdb.dbo.sysjobhistory h
JOIN msdb.dbo.sysjobs j ON j.job_id = h.job_id
WHERE j.name = N'Neteja diari de sessions'
ORDER BY h.run_date DESC, h.run_time DESC;

-- Eliminar un job
EXEC sp_delete_job @job_name = N'Neteja diari de sessions';

GUI de SQL Server Agent

Tot i que és possible gestionar els jobs via T-SQL, l'eina habitual és SQL Server Management Studio (SSMS): dins l'arbre de connexió, a "SQL Server Agent" → "Jobs". Des d'allà podeu crear, editar, executar manualment i veure l'historial de qualsevol job de manera visual.


Oracle: DBMS_SCHEDULER

DBMS_SCHEDULER és el paquet PL/SQL d'Oracle per gestionar tasques programades. És molt complet: suporta expressions de calendari pròpies (més expressives que cron), prioritats, finestres de manteniment, cadenes de jobs (un job dispara el següent), i monitoratge integrat.

Creació de jobs

-- Job senzill: executa un bloc PL/SQL cada dia a les 3:00
BEGIN
    DBMS_SCHEDULER.CREATE_JOB(
        job_name        => 'NETEJA_SESSIONS_DIARIA',
        job_type        => 'PLSQL_BLOCK',
        job_action      => 'BEGIN
                                DELETE FROM sessions
                                WHERE expires_at < SYSDATE;
                                COMMIT;
                            END;',
        start_date      => SYSTIMESTAMP,
        repeat_interval => 'FREQ=DAILY; BYHOUR=3; BYMINUTE=0; BYSECOND=0',
        enabled         => TRUE,
        comments        => 'Elimina sessions expirades cada dia a les 3:00'
    );
END;
/

-- Job que crida un procediment emmagatzemat
BEGIN
    DBMS_SCHEDULER.CREATE_JOB(
        job_name        => 'ARXIVAT_MENSUAL',
        job_type        => 'STORED_PROCEDURE',
        job_action      => 'ARXIVAR_COMANDES_ANTIGUES',  -- nom del procediment
        start_date      => TO_TIMESTAMP_TZ('2026-04-01 02:00:00 Europe/Madrid',
                                           'YYYY-MM-DD HH24:MI:SS TZR'),
        repeat_interval => 'FREQ=MONTHLY; BYMONTHDAY=1; BYHOUR=2; BYMINUTE=0',
        enabled         => TRUE,
        comments        => 'Arxivat mensual de comandes antigues'
    );
END;
/

-- Gestió de jobs
-- Habilitar / Deshabilitar
BEGIN
    DBMS_SCHEDULER.DISABLE('NETEJA_SESSIONS_DIARIA');
    DBMS_SCHEDULER.ENABLE('NETEJA_SESSIONS_DIARIA');
END;
/

-- Executar manualment
BEGIN
    DBMS_SCHEDULER.RUN_JOB('NETEJA_SESSIONS_DIARIA');
END;
/

-- Eliminar un job
BEGIN
    DBMS_SCHEDULER.DROP_JOB('NETEJA_SESSIONS_DIARIA');
END;
/

-- Llistar jobs
SELECT job_name, enabled, state, run_count, failure_count,
       last_start_date, next_run_date
FROM user_scheduler_jobs
ORDER BY job_name;

-- Veure l'historial
SELECT job_name, status, actual_start_date, run_duration, error#
FROM user_scheduler_job_run_details
ORDER BY actual_start_date DESC;

Expressions de calendari d'Oracle

Oracle usa el seu propi llenguatge de calendari (repeat_interval), més expressiu que cron:

FREQ=DAILY; BYHOUR=3; BYMINUTE=0            → Cada dia a les 3:00
FREQ=WEEKLY; BYDAY=SUN; BYHOUR=2            → Cada diumenge a les 2:00
FREQ=MONTHLY; BYMONTHDAY=1; BYHOUR=0        → El primer de cada mes a mitjanit
FREQ=HOURLY; BYMINUTE=30                    → Cada hora i mitja
FREQ=MINUTELY; INTERVAL=15                  → Cada 15 minuts
FREQ=YEARLY; BYMONTH=JAN; BYMONTHDAY=1      → L'1 de gener de cada any

Exemple pràctic 1: neteja diària de registres antics

-- Taula d'exemple: log_activitat amb molts registres antics
-- Conservem 1 any d'historial i eliminem la resta

SELECT cron.schedule(
    'neteja-log-activitat',
    '0 3 * * *',  -- Cada dia a les 3:00
    $$
    DELETE FROM log_activitat
    WHERE data_hora < NOW() - INTERVAL '1 year';
    $$
);

-- Versió amb procediment (recomanada per poder gestionar errors):
CREATE OR REPLACE PROCEDURE neteja_log_activitat()
LANGUAGE plpgsql
AS $$
DECLARE
    v_eliminats INT;
BEGIN
    DELETE FROM log_activitat
    WHERE data_hora < NOW() - INTERVAL '1 year';

    GET DIAGNOSTICS v_eliminats = ROW_COUNT;

    INSERT INTO log_jobs(job_nom, data_execucio, files_afectades, estat)
    VALUES ('neteja_log_activitat', NOW(), v_eliminats, 'OK');

    RAISE NOTICE 'Neteja completada: % registres eliminats', v_eliminats;
EXCEPTION
    WHEN OTHERS THEN
        INSERT INTO log_jobs(job_nom, data_execucio, files_afectades, estat, error)
        VALUES ('neteja_log_activitat', NOW(), 0, 'ERROR', SQLERRM);
        RAISE;
END;
$$;

SELECT cron.schedule(
    'neteja-log-activitat-proc',
    '0 3 * * *',
    'CALL neteja_log_activitat()'
);
DELIMITER //

CREATE PROCEDURE neteja_log_activitat()
BEGIN
    DECLARE v_eliminats INT DEFAULT 0;
    DECLARE EXIT HANDLER FOR SQLEXCEPTION
    BEGIN
        INSERT INTO log_jobs(job_nom, data_execucio, estat, error_msg)
        VALUES ('neteja_log_activitat', NOW(), 'ERROR', 'Excepció SQL');
        RESIGNAL;
    END;

    DELETE FROM log_activitat
    WHERE data_hora < DATE_SUB(NOW(), INTERVAL 1 YEAR);

    SET v_eliminats = ROW_COUNT();

    INSERT INTO log_jobs(job_nom, data_execucio, files_afectades, estat)
    VALUES ('neteja_log_activitat', NOW(), v_eliminats, 'OK');
END //

DELIMITER ;

-- Crear l'event
CREATE EVENT evt_neteja_log
ON SCHEDULE EVERY 1 DAY
STARTS (TIMESTAMP(CURRENT_DATE) + INTERVAL 3 HOUR)
COMMENT 'Neteja logs amb més d''un any d''antiguitat'
DO CALL neteja_log_activitat();
-- Procediment de neteja
CREATE OR ALTER PROCEDURE neteja_log_activitat
AS
BEGIN
    SET NOCOUNT ON;
    DECLARE @eliminats INT;

    BEGIN TRY
        DELETE FROM log_activitat
        WHERE data_hora < DATEADD(YEAR, -1, GETDATE());
        SET @eliminats = @@ROWCOUNT;

        INSERT INTO log_jobs(job_nom, data_execucio, files_afectades, estat)
        VALUES ('neteja_log_activitat', GETDATE(), @eliminats, 'OK');
    END TRY
    BEGIN CATCH
        INSERT INTO log_jobs(job_nom, data_execucio, estat, error_msg)
        VALUES ('neteja_log_activitat', GETDATE(), 'ERROR', ERROR_MESSAGE());
        THROW;
    END CATCH;
END;
GO

-- Job a SQL Server Agent:
USE msdb;
EXEC sp_add_job     @job_name = N'Neteja log activitat diari';
EXEC sp_add_jobstep @job_name = N'Neteja log activitat diari',
                    @step_name = N'Executar neteja',
                    @command   = N'EXEC neteja_log_activitat',
                    @database_name = N'la_meva_bd';
EXEC sp_add_schedule @schedule_name = N'Diari 3h',
                     @freq_type = 4, @freq_interval = 1,
                     @active_start_time = 030000;
EXEC sp_attach_schedule @job_name = N'Neteja log activitat diari',
                        @schedule_name = N'Diari 3h';
EXEC sp_add_jobserver   @job_name = N'Neteja log activitat diari';
CREATE OR REPLACE PROCEDURE neteja_log_activitat
AS
    v_eliminats NUMBER;
BEGIN
    DELETE FROM log_activitat
    WHERE data_hora < ADD_MONTHS(SYSDATE, -12);

    v_eliminats := SQL%ROWCOUNT;
    COMMIT;

    INSERT INTO log_jobs(job_nom, data_execucio, files_afectades, estat)
    VALUES ('neteja_log_activitat', SYSDATE, v_eliminats, 'OK');
    COMMIT;
EXCEPTION
    WHEN OTHERS THEN
        ROLLBACK;
        INSERT INTO log_jobs(job_nom, data_execucio, estat, error_msg)
        VALUES ('neteja_log_activitat', SYSDATE, 'ERROR', SQLERRM);
        COMMIT;
        RAISE;
END neteja_log_activitat;
/

BEGIN
    DBMS_SCHEDULER.CREATE_JOB(
        job_name        => 'JOB_NETEJA_LOG',
        job_type        => 'STORED_PROCEDURE',
        job_action      => 'NETEJA_LOG_ACTIVITAT',
        repeat_interval => 'FREQ=DAILY; BYHOUR=3; BYMINUTE=0; BYSECOND=0',
        enabled         => TRUE
    );
END;
/

Exemple pràctic 2: refresc setmanal d'estadístiques

-- Actualitzar les estadístiques de totes les taules principals
-- per mantenir plans d'execució òptims

CREATE OR REPLACE PROCEDURE refrescar_estadistiques()
LANGUAGE plpgsql
AS $$
BEGIN
    ANALYZE comandes;
    ANALYZE linies_comanda;
    ANALYZE productes;
    ANALYZE clients;
    ANALYZE log_activitat;

    RAISE NOTICE 'Estadístiques actualitzades a %', NOW();

    INSERT INTO log_jobs(job_nom, data_execucio, estat)
    VALUES ('refrescar_estadistiques', NOW(), 'OK');
END;
$$;

SELECT cron.schedule(
    'refresc-estadistiques-setmanal',
    '0 1 * * 0',  -- Cada diumenge a la 1:00
    'CALL refrescar_estadistiques()'
);
DELIMITER //

CREATE EVENT evt_refresc_estadistiques
ON SCHEDULE EVERY 1 WEEK
STARTS (TIMESTAMP(CURRENT_DATE - WEEKDAY(CURRENT_DATE) + 6) + INTERVAL 1 HOUR)
COMMENT 'Actualitza estadístiques de les taules principals cada diumenge a la 1:00'
DO
BEGIN
    ANALYZE TABLE comandes;
    ANALYZE TABLE linies_comanda;
    ANALYZE TABLE productes;
    ANALYZE TABLE clients;

    INSERT INTO log_jobs(job_nom, data_execucio, estat)
    VALUES ('refresc_estadistiques', NOW(), 'OK');
END //

DELIMITER ;
-- SQL Server: actualitzar estadístiques amb UPDATE STATISTICS
CREATE OR ALTER PROCEDURE refrescar_estadistiques
AS
BEGIN
    SET NOCOUNT ON;
    UPDATE STATISTICS comandes WITH FULLSCAN;
    UPDATE STATISTICS linies_comanda WITH FULLSCAN;
    UPDATE STATISTICS productes WITH FULLSCAN;
    UPDATE STATISTICS clients WITH FULLSCAN;

    INSERT INTO log_jobs(job_nom, data_execucio, estat)
    VALUES (N'refresc_estadistiques', GETDATE(), N'OK');
END;
GO

-- Job setmanal (diumenge a la 1:00)
USE msdb;
EXEC sp_add_job     @job_name = N'Refresc estadístiques setmanal';
EXEC sp_add_jobstep @job_name = N'Refresc estadístiques setmanal',
                    @step_name = N'UPDATE STATISTICS',
                    @command   = N'EXEC refrescar_estadistiques',
                    @database_name = N'la_meva_bd';
EXEC sp_add_schedule
    @schedule_name    = N'Setmanal diumenge 1h',
    @freq_type        = 8,    -- 8 = Setmanal
    @freq_interval    = 1,    -- Diumenge (bitmask: 1=diumenge)
    @freq_recurrence_factor = 1,
    @active_start_time = 010000;
EXEC sp_attach_schedule @job_name = N'Refresc estadístiques setmanal',
                        @schedule_name = N'Setmanal diumenge 1h';
EXEC sp_add_jobserver   @job_name = N'Refresc estadístiques setmanal';
-- Oracle: DBMS_STATS.GATHER_SCHEMA_STATS per actualitzar estadístiques
CREATE OR REPLACE PROCEDURE refrescar_estadistiques
AS
BEGIN
    DBMS_STATS.GATHER_TABLE_STATS(
        ownname => USER,
        tabname => 'COMANDES',
        cascade => TRUE
    );
    DBMS_STATS.GATHER_TABLE_STATS(
        ownname => USER,
        tabname => 'LINIES_COMANDA',
        cascade => TRUE
    );
    DBMS_STATS.GATHER_TABLE_STATS(
        ownname => USER,
        tabname => 'PRODUCTES',
        cascade => TRUE
    );

    INSERT INTO log_jobs(job_nom, data_execucio, estat)
    VALUES ('refresc_estadistiques', SYSDATE, 'OK');
    COMMIT;
END refrescar_estadistiques;
/

BEGIN
    DBMS_SCHEDULER.CREATE_JOB(
        job_name        => 'JOB_REFRESC_STATS',
        job_type        => 'STORED_PROCEDURE',
        job_action      => 'REFRESCAR_ESTADISTIQUES',
        repeat_interval => 'FREQ=WEEKLY; BYDAY=SUN; BYHOUR=1; BYMINUTE=0',
        enabled         => TRUE,
        comments        => 'Actualitza estadístiques cada diumenge a la 1:00'
    );
END;
/

Monitoratge i consulta de jobs

-- Llistar tots els jobs actius
SELECT jobid, jobname, schedule, active, username
FROM cron.job
ORDER BY jobname;

-- Historial de les darreres 50 execucions
SELECT
    j.jobname,
    r.start_time,
    r.end_time,
    r.end_time - r.start_time AS durada,
    r.status,
    r.return_message
FROM cron.job_run_details r
JOIN cron.job j ON j.jobid = r.jobid
ORDER BY r.start_time DESC
LIMIT 50;

-- Jobs fallats
SELECT j.jobname, r.start_time, r.return_message
FROM cron.job_run_details r
JOIN cron.job j ON j.jobid = r.jobid
WHERE r.status = 'failed'
ORDER BY r.start_time DESC;
-- Llistar tots els events
SHOW EVENTS;

-- Detalls dels events
SELECT
    event_name,
    event_type,
    execute_at,
    interval_value,
    interval_field,
    starts,
    ends,
    status,
    last_executed,
    event_comment
FROM information_schema.EVENTS
WHERE event_schema = DATABASE()
ORDER BY event_name;
-- Llistar tots els jobs amb el seu estat
SELECT
    j.name AS job_nom,
    j.enabled,
    j.description,
    h.run_date AS darrera_execucio,
    CASE h.run_status
        WHEN 0 THEN 'Fallat'
        WHEN 1 THEN 'Correcte'
        WHEN 3 THEN 'Cancel·lat'
    END AS darrer_resultat,
    s.next_run_date,
    s.next_run_time
FROM msdb.dbo.sysjobs j
LEFT JOIN msdb.dbo.sysjobhistory h ON h.job_id = j.job_id
    AND h.step_id = 0  -- step 0 = resultat global del job
LEFT JOIN msdb.dbo.sysjobschedules js ON js.job_id = j.job_id
LEFT JOIN msdb.dbo.sysschedules s ON s.schedule_id = js.schedule_id
ORDER BY j.name;
-- Llistar jobs actius
SELECT
    job_name,
    job_type,
    state,
    enabled,
    run_count,
    failure_count,
    last_start_date,
    next_run_date,
    comments
FROM user_scheduler_jobs
ORDER BY job_name;

-- Historial d'execucions
SELECT
    job_name,
    status,
    actual_start_date,
    run_duration,
    error#,
    additional_info
FROM user_scheduler_job_run_details
ORDER BY actual_start_date DESC
FETCH FIRST 50 ROWS ONLY;

Miniactivitat — AC0407

Miniactivitat — AC0407 · Planificació de manteniment automatitzat

Disposeu d'una BD amb les taules: comandes, linies_comanda, productes, clients, sessions_usuari, log_errors, estadistiques_diaries.

Tasca 1 — Neteja diària: Creeu un job que s'executi cada dia a les 2:30 i faci les tasques següents en aquest ordre: 1. Elimini les sessions d'usuari amb expires_at anterior a ara. 2. Elimini els errors de log amb data anterior a 90 dies. 3. Insereixi un registre a log_jobs indicant quants registres ha eliminat de cada taula i el temps total d'execució.

Tasca 2 — Resum diari: Creeu un job que s'executi cada dia a les 23:55 i calculi les estadístiques del dia (nombre de comandes, import total, producte més venut, client amb més compres) i les insereixi a estadistiques_diaries.

Tasca 3 — Simulació i verificació: Executeu els jobs manualment (sense esperar a l'hora programada) i verifiqueu que els registres s'han creat correctament a log_jobs i estadistiques_diaries.

Tasca 4 — Alarma per errors: Amplieu el job de la Tasca 1 perquè, si es produeix un error en qualsevol dels passos, ho registri a log_jobs amb el missatge d'error i continuï amb el pas següent (no ha d'aturar-se en el primer error).

Entrega: Arxiu SQL amb els procediments i la configuració dels jobs per al motor escollit. Incloeu captures de pantalla (o la sortida de text) que demostrin que els jobs s'han executat correctament i que el registre a log_jobs és correcte.