Nota: Si este artículo te parece demasiado largo, pídele a una IA que te lo resuma.

Luego, si quieres, léelo y verás lo que te perdiste.

Escribir este artículo es algo que llevo décadas queriendo hacer.

Desde el estándar SQL-92 existen las “Assertions”, pero ningún motor de base de datos las implementaba hasta ahora.

Podríamos definir una SQL Assertion como una constraint avanzada que permite una comprobación cruzada entre múltiples tablas o entre filas de la misma tabla. Pongo ejemplos más adelante.

En la práctica, este tipo de condiciones sólo podían implementarse como triggers y, además de la complejidad de la lógica de implementación, resultaba muy difícil garantizar su integridad y persistencia. Por no decir que chequear estas condiciones en cada DML podría impactar considerablemente el rendimiento.

Cada vez que daba un curso de SQL y aparecía el tema, siempre indicaba que “eso se explicaba en PL/SQL”, mencionando los triggers.

Y entonces, cuando llegaba el momento de explicar los triggers y para qué eran útiles, los definía como “una capa de PL/SQL pegada a una tabla, sensible a insert/update/delete, capaz de registrar auditorías o trazas complejas, o de mantener restricciones de integridad avanzada entre diferentes tablas”.

Me refería a estas SQL Assertions y, en más de 25 años, no he podido impartir esta lección porque ningún motor de base de datos las implementaba.

Hasta ahora.

Las SQL Assertions son, por fin, una nueva funcionalidad de Oracle26ai. Cuidado, sólo en la versión 23.26.1 (es decir, con el parche RU de enero de 2026 aplicado).

Así que vamos con todo.

Casos prácticos

Vamos a pensar en un primer escenario para el dataset de vuelos en el que podamos aplicar estas SQL Assertions. Si quieres generar el modelo de tablas, lo tienes en el repositorio de GitHub de Café Database aquí.

https://github.com/CafeDatabase/vuelos-dataset

Tenemos VUELOS con capacidades distintas. Estas pueden ser de 50, 60, 100 o 120 plazas. Algo así como si tuviéramos cuatro tipos de aviones.

En el modelo cargado tenemos, para cada vuelo, tantas plazas en la tabla PLAZAS como la capacidad total del avión de ese vuelo. Un vuelo en un avión con capacidad para 50 plazas tiene 50 filas en la tabla PLAZAS. Los vuelos con 100 plazas tienen 100 filas en la tabla PLAZAS.

Podríamos estar interesados en crear una restricción que valide que el número total de filas en la tabla PLAZAS asignadas a cada vuelo no exceda dicha capacidad.

Y necesitamos que esa restricción se cumpla siempre. Tanto si se hace un cambio en la capacidad del avión, como si añadimos nuevas plazas a un determinado vuelo.

PLAZAS han de ser iguales o menores que la CAPACIDAD (columna) del VUELO.

Actualmente, el dataset de vuelos se carga poblando tantas plazas como la capacidad de su vuelo. En este contexto, debería ser posible que un vuelo de 60 plazas tuviera 58 o 59 a la venta, pero no 61.

CREATE ASSERTION capacidad_consistente_con_plazas
CHECK (
  NOT EXISTS (
    SELECT 'plaza fuera de capacidad'
    FROM VUELOS v, PLAZAS p
    WHERE p.VUE_ID_VUELO = v.ID_VUELO
      AND p.PLA_ID > v.CAPACIDAD_AVION
  )
);

Lo interesante de las SQL Assertions es que, para cada UPDATE que pueda alterar esta regla, la Assertion validará que dicha condición se cumpla. Si cambiamos la capacidad de un vuelo (es decir, si cambiamos el avión) por uno de capacidad mayor, no habrá problema, pero si definimos un avión con menos plazas que las que hay insertadas para ese vuelo, tendremos un error en esa misma sentencia.

SQL> CREATE ASSERTION capacidad_consistente_con_plazas
  2  CHECK (
  3    NOT EXISTS (
  4      SELECT 'plaza fuera de capacidad'
  5      FROM VUELOS v, PLAZAS p
  6      WHERE p.VUE_ID_VUELO = v.ID_VUELO
  7        AND p.PLA_ID > v.CAPACIDAD_AVION
  8    )
  9  );

Assertion created.

SQL> select id_vuelo, capacidad_avion from vuelos fetch first 1 rows only;

  ID_VUELO CAPACIDAD_AVION
---------- ---------------
     94848        100

SQL> select count(*) from plazas where vue_id_vuelo=94848;

  COUNT(*)
----------
       100

SQL> update vuelos set capacidad_avion=60 where id_vuelo=94848;
update vuelos set capacidad_avion=60 where id_vuelo=94848
                                                    *
ERROR at line 1:
ORA-08601: SQL assertion (VUELOS.CAPACIDAD_CONSISTENTE_CON_PLAZAS) violated.
Help: https://docs.oracle.com/error-help/db/ora-08601/

Pero ¿qué ocurre si queremos permitir que la Assertion se valide a nivel de transacción? Es decir, permitir que el vuelo cambie a una capacidad menor y, antes de hacer el COMMIT, borrar las plazas adicionales.

En este caso, al igual que con las constraints, tenemos la cláusula DEFERRABLE INITIALLY DEFERRED.

CREATE ASSERTION capacidad_consistente_con_plazas
CHECK (
  NOT EXISTS (
    SELECT 'plaza fuera de capacidad'
    FROM VUELOS v, PLAZAS p
    WHERE p.VUE_ID_VUELO = v.ID_VUELO
      AND p.PLA_ID > v.CAPACIDAD_AVION
  )
) DEFERRABLE INITIALLY DEFERRED;

Así es posible actualizar un vuelo a una capacidad menor, pero debemos asegurarnos antes de realizar el commit de que hemos eliminado el excedente de plazas.

Vamos a verlo.

SQL> drop assertion capacidad_consistente_con_plazas;

Assertion dropped.

SQL> CREATE ASSERTION capacidad_consistente_con_plazas
  2  CHECK (
  3    NOT EXISTS (
  4      SELECT 'plaza fuera de capacidad'
  5      FROM VUELOS v, PLAZAS p
  6      WHERE p.VUE_ID_VUELO = v.ID_VUELO
  7        AND p.PLA_ID > v.CAPACIDAD_AVION
  8    )
  9  ) DEFERRABLE INITIALLY DEFERRED;

Assertion created.

SQL> update vuelos set capacidad_avion=60 where id_vuelo=94848;

1 row updated.

SQL> -- He podido modificar la capacidad, pero la SQL Assertion no se valida hasta el commit 
SQL> delete from plazas where pla_id>60 and vue_id_vuelo=94848;

40 rows deleted.

SQL> commit;

Commit complete.

SQL> -- Voy a reducir la capacidad a 50 plazas y dejar 51 plazas en PLAZAS

SQL> update vuelos set capacidad_avion=50 where id_vuelo=94848;

1 row updated.

SQL> delete from plazas where pla_id>51 and vue_id_vuelo=94848;

9 rows deleted.

SQL> commit;
commit
*
ERROR at line 1:
ORA-02091: transaction rolled back
ORA-08601: SQL assertion (VUELOS.CAPACIDAD_CONSISTENTE_CON_PLAZAS) violated.
Help: https://docs.oracle.com/error-help/db/ora-02091/

SQL> -- toda la transacción conflictiva se rechaza y se mantiene la capacidad a 60 plazas

SQL> select id_vuelo, capacidad_avion from vuelos where id_vuelo=94848;

  ID_VUELO CAPACIDAD_AVION
---------- ---------------
     94848  60

SQL> select count(*) from plazas where vue_id_vuelo=94848;

  COUNT(*)
----------
 60

Involucrando un control adicional sobre RESERVAS.

Vamos a complicar el ejemplo impidiendo que un vuelo pueda reducir su capacidad (por ejemplo, de 120 a 100 plazas) si hay más plazas reservadas de las que dispone el nuevo avión.

De este modo, el UPDATE con el cambio de plazas a un avión más pequeño solo se permitiría si no se ha vendido un número de plazas superior a la nueva capacidad.

Por lo tanto, voy a crear una segunda SQL Assertion para verificar las reservas. Si hay más reservas que la capacidad actual del avión, ese UPDATE en la tabla VUELOS deberá fallar.

CREATE ASSERTION capacidad_no_inferior_a_plazas_reservadas
CHECK (
   NOT EXISTS (
    SELECT 'plaza fuera de capacidad'
    FROM VUELOS v, RESERVAS r
    WHERE r.VUE_ID_VUELO = v.ID_VUELO
      AND r.PLA_ID_PLAZA > v.CAPACIDAD_AVION
  )
);

Aunque aquí estoy haciendo una pequeña trampa.

Las plazas de un avión, en nuestro modelo de datos, son un dato ordinal; es decir, se numera del 1 al número de plazas del avión. Las reservas, si se hacen secuencialmente, también llenarían el avión de forma ordinal: la primera reserva ocupa la plaza 1 del vuelo, la segunda, la plaza 2, y así sucesivamente.

Si los clientes eligen la plaza porque prefieren estar al final del avión, por ejemplo, las últimas plazas estarían ocupadas, pero quizás el avión esté medio vacío.

Por no decir que, si hay cancelaciones, esas plazas deberían volver a estar desocupadas y sí debería permitir actualizar a un avión más pequeño.

CREATE ASSERTION capacidad_no_inferior_a_plazas_reservadas
CHECK (
  3    NOT EXISTS (
  4      SELECT 'plaza fuera de capacidad'
  5      FROM VUELOS v, RESERVAS r
  6      WHERE r.VUE_ID_VUELO = v.ID_VUELO
  7        AND r.PLA_ID_PLAZA > v.CAPACIDAD_AVION
  8    )
  9  );

Assertion created.

SQL> select vue_id_vuelo, count(*) from reservas group by vue_id_vuelo having count(*)>20;  

VUE_ID_VUELO   COUNT(*)
------------ ----------
 1006      21

Elapsed: 00:00:00.08
 
SQL> select * from reservas where vue_id_vuelo=1006;

ID_RESERVA    IMPORTE CLI_NIF  TRS AGE_ID_AGENCIA PLA_ID_PLAZA VUE_ID_VUELO
---------- ---------- ---------- --- -------------- ------------ ------------
Mg`TVfJJBL     103.19 90857407-N TEL   53        2  1006
DPKEOEUWO[ 101.2 87095872-R INT   40        6  1006
bdfOPJdiEP     113.17 16602711-O VEN   14        7  1006
MZBFJITNM` 189.4 2657873-D  INT  133       11  1006
LHWShhcOU^     193.73 82355352-I INT   40       13  1006
ZGWaRhK^P^     147.67 8071986-E  VEN  136       20  1006
JPFITFcGYP     101.74 21409917-K VEN   54       29  1006
iAbZBPZOXI     177.72 31934765-J TEL    7       38  1006
YPUeJZePeU     109.58 20615714-I VEN   12       39  1006
BeJVLaM\JG 181.4 15451433-W VEN  136       42  1006
WV`IhKYZWh 134.4 57515235-U INT  121       98  1006
XTLg`QYMYE     192.39 56560002-I VEN   19      108  1006
4CD6D1B19F 118.3 12996373-S TEL   95        1  1006
gQREZPSESX 177.7 56920247-J TEL   86       22  1006
BDh`]WAJXS     197.07 24307672-R VEN   55       32  1006
UTLh`aWLCZ     120.66 77586025-R INT  144       49  1006
dYSZKdPYUe     186.31 92136826-K INT   25       50  1006
WKHU`dETNd     134.73 30179377-V VEN    5       63  1006
ddZJ]hUGIU     174.29 9167243-T  TEL   85       78  1006
UJX]DHA\ae     148.58 67159700-G TEL   66       95  1006
^BDLCVRKRX     120.22 94489790-X TEL   43       96  1006

21 rows selected.

Elapsed: 00:00:00.04
SQL> update vuelos set capacidad_avion=100 where id_vuelo=1006;
update vuelos set capacidad_avion=100 where id_vuelo=1006
                                                     *
ERROR at line 1:
ORA-08601: SQL assertion (VUELOS.CAPACIDAD_NO_INFERIOR_A_PLAZAS_RESERVADAS) violated.
Help: https://docs.oracle.com/error-help/db/ora-08601/

Elapsed: 00:00:00.14

En este caso, entonces, no podríamos reducir la ocupación del vuelo 1006 de 120 a 100 plazas porque hay una persona ocupando la plaza 108.

La SQL Assertion que hemos creado, comparando el número de la plaza con la capacidad, es una medida un poco “regulera”. Lo óptimo en este caso sería contar las plazas reservadas para permitir, o no, reducir la capacidad del vuelo a un avión más pequeño.

El código de la SQL Assertion podría ser el siguiente:

CREATE ASSERTION capacidad_no_inferior_a_reservas
CHECK (
  NOT EXISTS (
    SELECT 1
    FROM VUELOS v
    WHERE v.CAPACIDAD_AVION < (
      SELECT COUNT(*)
      FROM RESERVAS r
      WHERE r.VUE_ID_VUELO = v.ID_VUELO
    )
  )
);

Y digo “podría” porque esta sintaxis no está permitida.

Las SQL Assertions vienen con un montón de limitaciones. Sólo podemos usarlas en sentencias SQL que no contengan funciones de grupo; no soportan ANSI SQL; no resuelven accesos a través de sinónimos; no se permiten sobre vistas materializadas ni información de contexto (SYSDATE, USERENV, CURRENT_SCHEMA, etc.).

Han de ser estrictamente determinísticas.

Por si quieres echarle un ojo a las restricciones de las SQL Assertions, aquí tienes la documentación oficial con toda la lista de restricciones:

https://docs.oracle.com/en/database/oracle/oracle-database/26/sqlrf/create-assertion.html

Y por si piensas que, mediante funciones analíticas, quizás puedas salvar la restricción de uso de funciones de grupo, has de saber que también forman parte de la lista de funcionalidades no prohibidas, aunque el error “Las tablas pertenecientes a SYS no están permitidas” no sea muy ilustrativo.

SQL> SELECT 'plaza fuera de capacidad'
  2  FROM VUELOS v, RESERVAS r
  3  WHERE r.VUE_ID_VUELO = v.ID_VUELO
  4   QUALIFY ROW_NUMBER() OVER (
  5             PARTITION BY r.VUE_ID_VUELO order by r.PLA_ID_PLAZA
  6               ) > v.CAPACIDAD_AVION;

no rows selected

SQL> CREATE ASSERTION capacidad_no_inferior_a_reservas
  2  CHECK (
  3    NOT EXISTS (
  4  SELECT 'plaza fuera de capacidad'
  5  FROM VUELOS v, RESERVAS r
  6  WHERE r.VUE_ID_VUELO = v.ID_VUELO
  7     QUALIFY ROW_NUMBER() OVER (
  8      PARTITION BY r.VUE_ID_VUELO order by r.PLA_ID_PLAZA
  9        ) > v.CAPACIDAD_AVION
 10    )
 11  )
    FROM VUELOS v, RESERVAS r
         *
ERROR at line 5:
ORA-08689: CREATE ASSERTION failed
ORA-08697: SYS owned tables are not supported.
Help: https://docs.oracle.com/error-help/db/ora-08689/

Otro ejemplo: comprobar que un cliente no toma dos vuelos el mismo día.

Vamos a explorar otro ejemplo: validar que un cliente no tenga reservas en vuelos distintos para un mismo día. En este caso, los datos de fecha del vuelo están en la tabla VUELOS, y para ello necesitamos hacer el cruce con vuelos y reservas dos veces.

Recuerda que no podemos usar ni funciones de grupo ni analíticas.

CREATE ASSERTION no_dos_vuelos_mismo_dia
CHECK (
  NOT EXISTS (
    SELECT 1
    FROM reservas r1,
         reservas r2,
         vuelos   v1,
         vuelos   v2
    WHERE r1.cli_nif = r2.cli_nif
      AND r1.rowid < r2.rowid
      AND r1.vue_id_vuelo = v1.id_vuelo
      AND r2.vue_id_vuelo = v2.id_vuelo
      AND TRUNC(v1.fecha_vuelo) = TRUNC(v2.fecha_vuelo)
  )
) ENABLE NOVALIDATE; 

Aquí nos encontramos con más restricciones en la validación de la constraint. Dado que en la tabla RESERVAS el valor de CLI_NIF puede ser NULL, la SQL Assertion nos fallará con un error que nos advierte de que la comprobación de la assertion ha de ser de tipo FAST, y que con las condiciones habituales, no es posible garantizar.

Tendremos dos opciones para que funcione: o añadimos las cláusulas WHERE CLI_NIF IS NOT NULL al código de la SQL Assertion, o modificaremos la tabla RESERVAS para impedir que CLI_NIF sea NULL

SQL> CREATE ASSERTION no_dos_vuelos_mismo_dia
  2  CHECK (
  3    NOT EXISTS (
  4  SELECT 1
  5  FROM reservas r1,
  6       reservas r2,
  7       vuelos   v1,
  8       vuelos   v2
  9  WHERE r1.cli_nif = r2.cli_nif
 10    AND r1.rowid < r2.rowid 
 11    AND r1.vue_id_vuelo = v1.id_vuelo 
 12    AND r2.vue_id_vuelo = v2.id_vuelo 
 13    AND TRUNC(v1.fecha_vuelo) = TRUNC(v2.fecha_vuelo) 
 14    ) 
 15  ) ENABLE NOVALIDATE 
    FROM reservas r1,          
  * ERROR at line 5: ORA-08689: CREATE ASSERTION failed ORA-08673: Equijoin "R1"."CLI_NIF"="R2"."CLI_NIF" found 
does not meet the criteria to do a FAST validation. Help: https://docs.oracle.com/error-help/db/ora-08689/ 

SQL> alter table reservas modify cli_nif not null;

Table altered.

SQL> CREATE ASSERTION no_dos_vuelos_mismo_dia
  2  CHECK (
  3    NOT EXISTS (
  4  SELECT 1
  5  FROM reservas r1,
  6       reservas r2,
  7       vuelos   v1,
  8       vuelos   v2
  9  WHERE r1.cli_nif = r2.cli_nif
 10    AND r1.rowid < r2.rowid 
 11    AND r1.vue_id_vuelo = v1.id_vuelo 
 12    AND r2.vue_id_vuelo = v2.id_vuelo 
 13    AND TRUNC(v1.fecha_vuelo) = TRUNC(v2.fecha_vuelo) 
 14    ) 
 15  ) ENABLE NOVALIDATE 

Assertion created. 

Elapsed: 00:00:00.22 

SQL> insert into reservas values ('Y\QZ]ZJPPV',197.65,'10108372-X','INT',38,9,20108);
insert into reservas values ('Y\QZ]ZJPPV',197.65,'10108372-X','INT',38,9,20108)
            *
ERROR at line 1:
ORA-08601: SQL assertion (VUELOS.NO_DOS_VUELOS_MISMO_DIA) violated.
Help: https://docs.oracle.com/error-help/db/ora-08601/

Una precaución aquí: hay que tener cuidado con el rendimiento.

La SQL Assertion anterior se ha creado con la cláusula ENABLE NOVALIDATE de forma instantánea. Aunque las comprobaciones no parezcan afectar el rendimiento al ejecutar el código SQL que la valida, la creación de estas SQL Assertions puede llevar un tiempo considerable debido a la validación de todos los casos existentes.

Podemos ver que la query se ejecuta en menos de un segundo, pero la activación con validación de la Assertion tarda más de 1 hora.

SQL> SELECT 'cliente con dos vuelos el mismo dia'
  2     FROM reservas r1,
  3      reservas r2,
  4           vuelos   v1,
  5      vuelos   v2
  6      WHERE r1.cli_nif = r2.cli_nif
  7       AND r1.rowid < r2.rowid
  8       AND r1.vue_id_vuelo = v1.id_vuelo
  9       AND r2.vue_id_vuelo = v2.id_vuelo
 10       AND TRUNC(v1.fecha_vuelo) = TRUNC(v2.fecha_vuelo)

no rows selected

Elapsed: 00:00:00.60

SQL> alter assertion NO_DOS_VUELOS_MISMO_DIA enable validate;

Elapsed: 01:43:05.21

¿Qué hay por debajo?

 

Vamos a echarle un ojo a las tablas que se crean automáticamente con la creación de las constraints.

SQL> select table_name, blocks*(8*1024) bytes from user_tables

TABLE_NAME           BYTES
------------------------------------------------------- ----------
CLIENTES          778240
COMPANIAS           40960
AGENCIAS           40960
TIPOS_VUELO           40960
EMPRESAS_VIAJES           40960
SEXOS            40960
PLAZAS         82714624
AEROPUERTOS           40960
ESTADOSLABORALES          40960
PAISES            40960
RESERVAS        11288576
VUELOS          4063232
ESTADOSCIVILES           40960
CATERING           40960
TIPOS_RESERVAS           40960
EDADES            40960
CIUDADES           40960
ORA$SA$TE_VUELOS
ORA$SA$TE_PLAZAS
ORA$SA$VC_CAPACIDAD_CONSISTENTE_CON_PLAZAS
ORA$SA$TE_RESERVAS
ORA$SA$VC_CAPACIDAD_NO_INFERIOR_A_PLAZAS_RESERVADAS
ORA$SA$VC_NO_DOS_VUELOS_MISMO_DIA

23 rows selected.

SQL> select count(*) from ORA$SA$TE_VUELOS;
select count(*) from ORA$SA$TE_VUELOS
                     *
ERROR at line 1:
ORA-08709: Reads from SQL assertion auxiliary tables are restricted.
Help: https://docs.oracle.com/error-help/db/ora-08709/


Para consultar al diccionario, tenemos a nuestra disposición las vistas USER_ASSERTIONS, USER_ASSERTION_DEPENDENCIES y USER_ASSERTION_LOCK_MATRIX

SQL> desc USER_ASSERTIONS
 Name        Null?    Type
 ----------------------------------------- -------- ----------------------------
 ASSERTION_NAME         VARCHAR2(128)
 OBJECT_ID       NOT NULL NUMBER
 STATUS          VARCHAR2(8)
 DEFERRABLE         VARCHAR2(14)
 DEFERRED         VARCHAR2(9)
 VALIDATED         VARCHAR2(13)
 INVALID         VARCHAR2(7)
 DEFINITION_SQL         CLOB

SQL> desc USER_ASSERTION_DEPENDENCIES
 Name        Null?    Type
 ----------------------------------------- -------- ----------------------------
 ASSERTION_NAME         VARCHAR2(128)
 REFERENCED_OWNER        VARCHAR2(128)
 REFERENCED_NAME        VARCHAR2(128)
 REFERENCED_TYPE        VARCHAR2(23)
 REFERENCED_ID       NOT NULL NUMBER
 VALIDATION_TYPE        VARCHAR2(8)
 VALIDATION_EVENT        VARCHAR2(24)

SQL> desc USER_ASSERTION_LOCK_MATRIX
 Name        Null?    Type
 ----------------------------------------- -------- ----------------------------
 ASSERTION_NAME         VARCHAR2(128)
 TABLE_ID         NUMBER
 TABLE_OWNER         VARCHAR2(128)
 TABLE_NAME         VARCHAR2(128)
 ADJ_TABLE_ID         NUMBER
 ADJ_TABLE_OWNER        VARCHAR2(128)
 ADJ_TABLE_NAME         VARCHAR2(128)
 LOCK_SCOPE         VARCHAR2(18)
 JOIN_COLUMNS         VARCHAR2(4000)
 LOCK_MODE         VARCHAR2(8)
 LOCK_TEXT         VARCHAR2(4000)


SQL> select assertion_name, object_id, status, validated
  2  from user_assertions;

ASSERTION_NAME         OBJECT_ID STATUS   VALIDATED
-------------------------------------------------- ---------- -------- -------------
CAPACIDAD_CONSISTENTE_CON_PLAZAS         131257 ENABLED  VALIDATED
CAPACIDAD_NO_INFERIOR_A_PLAZAS_RESERVADAS        131262 ENABLED  VALIDATED
NO_DOS_VUELOS_MISMO_DIA           131306 ENABLED  NOT VALIDATED

SQL> select assertion_name, referenced_owner, referenced_name, validation_type, validation_event
  2  from user_assertion_dependencies;

ASSERTION_NAME         REFERENCED_OWNER  REFERENCED_NAME      VALIDATI VALIDATION_EVENT
--------------------------------------------------- -------------------- -------------------- -------- ------------------------
CAPACIDAD_CONSISTENTE_CON_PLAZAS      VUELOS   PLAZAS        FAST     ROWS INSERTED OR UPDATED
CAPACIDAD_NO_INFERIOR_A_PLAZAS_RESERVADAS     VUELOS   RESERVAS       FAST     ROWS INSERTED OR UPDATED
NO_DOS_VUELOS_MISMO_DIA        VUELOS   RESERVAS       FAST     ROWS INSERTED OR UPDATED
NO_DOS_VUELOS_MISMO_DIA        VUELOS   RESERVAS       FAST     ROWS INSERTED OR UPDATED
CAPACIDAD_CONSISTENTE_CON_PLAZAS      VUELOS   VUELOS        FAST     ROWS INSERTED OR UPDATED
CAPACIDAD_NO_INFERIOR_A_PLAZAS_RESERVADAS     VUELOS   VUELOS        FAST     ROWS INSERTED OR UPDATED
NO_DOS_VUELOS_MISMO_DIA        VUELOS   VUELOS        FAST     ROWS INSERTED OR UPDATED
NO_DOS_VUELOS_MISMO_DIA        VUELOS   VUELOS        FAST     ROWS INSERTED OR UPDATED

8 rows selected.

SQL> select assertion_name, table_name, adj_table_name, lock_scope, join_columns, lock_mode, lock_text
  2  from user_assertion_lock_matrix
  3  order by assertion_name; 

ASSERTION_NAME         TABLE_NAME ADJ_TABLE_ LOCK_SCOPE      JOIN_COLUMNS    LOCK_MOD LOCK_TEXT
--------------------------------------------------- ---------- ---------- ------------------ --------------- -------- ---------------
CAPACIDAD_CONSISTENTE_CON_PLAZAS      VUELOS     PLAZAS   JOIN_COLUMN_VALUES DETALLES      S       T#:0 C#:1:3
CAPACIDAD_CONSISTENTE_CON_PLAZAS      PLAZAS     PLAZAS   JOIN_COLUMN_VALUES VUE_ID_VUELO    SX       T#:0 C#:1:3
CAPACIDAD_CONSISTENTE_CON_PLAZAS      PLAZAS     VUELOS   JOIN_COLUMN_VALUES PLA_ID      S       T#:1 C#:1:1
CAPACIDAD_CONSISTENTE_CON_PLAZAS      VUELOS     VUELOS   JOIN_COLUMN_VALUES ID_VUELO      SX       T#:1 C#:1:1
CAPACIDAD_NO_INFERIOR_A_PLAZAS_RESERVADAS     RESERVAS   RESERVAS   JOIN_COLUMN_VALUES VUE_ID_VUELO    SX       T#:0 C#:1:7
CAPACIDAD_NO_INFERIOR_A_PLAZAS_RESERVADAS     RESERVAS   VUELOS   JOIN_COLUMN_VALUES ID_RESERVA      S       T#:1 C#:1:1
CAPACIDAD_NO_INFERIOR_A_PLAZAS_RESERVADAS     VUELOS     VUELOS   JOIN_COLUMN_VALUES ID_VUELO      SX       T#:1 C#:1:1
CAPACIDAD_NO_INFERIOR_A_PLAZAS_RESERVADAS     VUELOS     RESERVAS   JOIN_COLUMN_VALUES CAT_ID_CATERING S       T#:0 C#:1:7
NO_DOS_VUELOS_MISMO_DIA        VUELOS     RESERVAS   JOIN_COLUMN_VALUES CAT_ID_CATERING S       T#:0 C#:1:7
NO_DOS_VUELOS_MISMO_DIA        VUELOS     RESERVAS   TABLE         S       T#:0 C#:0:
NO_DOS_VUELOS_MISMO_DIA        VUELOS     VUELOS   TABLE         SX       T#:3 C#:0:
NO_DOS_VUELOS_MISMO_DIA        VUELOS     RESERVAS   JOIN_COLUMN_VALUES CAT_ID_CATERING S       T#:1 C#:1:7
NO_DOS_VUELOS_MISMO_DIA        RESERVAS   VUELOS   JOIN_COLUMN_VALUES ID_RESERVA      S       T#:2 C#:1:1
NO_DOS_VUELOS_MISMO_DIA        RESERVAS   VUELOS   TABLE         S       T#:2 C#:0:
NO_DOS_VUELOS_MISMO_DIA        RESERVAS   VUELOS   TABLE         S       T#:3 C#:0:
NO_DOS_VUELOS_MISMO_DIA        RESERVAS   VUELOS   JOIN_COLUMN_VALUES ID_RESERVA      S       T#:3 C#:1:1
NO_DOS_VUELOS_MISMO_DIA        VUELOS     VUELOS   TABLE         SX       T#:2 C#:0:
NO_DOS_VUELOS_MISMO_DIA        VUELOS     VUELOS   JOIN_COLUMN_VALUES ID_VUELO      SX       T#:2 C#:1:1
NO_DOS_VUELOS_MISMO_DIA        VUELOS     VUELOS   TABLE         S       T#:2 C#:0:
NO_DOS_VUELOS_MISMO_DIA        VUELOS     VUELOS   TABLE         S       T#:3 C#:0:
NO_DOS_VUELOS_MISMO_DIA        VUELOS     VUELOS   JOIN_COLUMN_VALUES ID_VUELO      SX       T#:3 C#:1:1
NO_DOS_VUELOS_MISMO_DIA        RESERVAS   RESERVAS   JOIN_COLUMN_VALUES CLI_NIF      SX       T#:1 C#:1:3
NO_DOS_VUELOS_MISMO_DIA        RESERVAS   RESERVAS   TABLE         SX       T#:1 C#:0:
NO_DOS_VUELOS_MISMO_DIA        RESERVAS   RESERVAS   JOIN_COLUMN_VALUES VUE_ID_VUELO    SX       T#:1 C#:1:7
NO_DOS_VUELOS_MISMO_DIA        RESERVAS   RESERVAS   JOIN_COLUMN_VALUES CLI_NIF      S       T#:1 C#:1:3
NO_DOS_VUELOS_MISMO_DIA        RESERVAS   RESERVAS   JOIN_COLUMN_VALUES CLI_NIF      S       T#:0 C#:1:3
NO_DOS_VUELOS_MISMO_DIA        RESERVAS   RESERVAS   JOIN_COLUMN_VALUES CLI_NIF      SX       T#:0 C#:1:3
NO_DOS_VUELOS_MISMO_DIA        RESERVAS   RESERVAS   JOIN_COLUMN_VALUES VUE_ID_VUELO    SX       T#:0 C#:1:7
NO_DOS_VUELOS_MISMO_DIA        RESERVAS   RESERVAS   TABLE         SX       T#:0 C#:0:
NO_DOS_VUELOS_MISMO_DIA        VUELOS     RESERVAS   TABLE         S       T#:1 C#:0:

30 rows selected.

Disponible sólo en Oracle AI Database a partir de la release 23.26.1

Estamos ante una funcionalidad completamente nueva. Sólo disponible a partir del RU de enero de 2026 para la base de datos Oracle AI Database (26ai).

No solo eso: se trata de una funcionalidad nueva en el mundo de las bases de datos en general, pues hasta ahora sólo existía en el marco teórico.

El comportamiento y la sintaxis recuerdan mucho a la gestión de constraints, lo que permite combinaciones entre ENABLE/DISABLE y VALIDATE/NOVALIDATE. Desconozco si en el futuro la lista de restricciones de uso se irá reduciendo, ni si podremos contar con SQL Assertions con expresiones de grupo o funciones analíticas, pero la idea de acercarnos cada vez más al estándar y poder implementar desde el motor el mayor número posible de reglas de negocio sobre los datos me parece súper interesante.

Echo de menos una cláusula EXCEPTIONS INTO para gestionar excepciones en una tabla EXCEPTIONS, igual que ocurre con las constraints, y tampoco tenemos documentación sobre si el optimizador utilizará la lógica de las assertions para mejorar las estimaciones de cardinalidad y selectividad.

En cualquier caso, por fin vemos implementada una funcionalidad diseñada para mejorar la lógica de negocio que ha tardado décadas en ver la luz.

Al igual que este artículo.

Share This