Enforcing SLA Compliance with SQL Assertions in Oracle 23ai: A Real-World Use Case

One of the most frustrating things I’ve dealt with as a DBA is cleaning up data that should never have existed in the first place. Orphaned records, overlapping date ranges, business rules violated because some batch job skipped a validation step. We’ve all been there.

The traditional solution was triggers. And if you’ve written cross-table validation triggers in Oracle, you know the pain: mutating table errors (ORA-04091), complex exception handling, scattered logic across multiple trigger bodies, and debugging sessions that make you question your career choices.

Starting with Oracle Database 23ai (release 23.26.1), Oracle introduced SQL Assertions, and they change everything about how we enforce cross-table business rules.

What Are SQL Assertions?

An assertion is a schema-level integrity constraint defined by a boolean expression. If that expression evaluates to false during a transaction, the transaction fails. That’s it. The concept has been part of the SQL standard since SQL-92, but no major database vendor actually implemented it until Oracle did it in 23.26.1.

There are two types of assertion expressions:

Existential expressions use [NOT] EXISTS with a subquery. If the condition is true, the transaction proceeds.

Universal expressions use the new ALL ... SATISFY syntax. This lets you say “for every row matching this query, this condition must hold.” It’s Oracle’s elegant alternative to the awkward double-negation pattern (NOT EXISTS ... WHERE NOT EXISTS ...) that SQL traditionally requires for universal quantification.

The Scenario: SLA Compliance for a Ticketing System

Let me show you a real-world use case that goes beyond toy examples. Imagine you run a support ticketing system for an enterprise. You have service level agreements (SLAs) with your customers, and the database needs to enforce these rules:

  1. Every customer must have an active SLA before they can submit a ticket. No SLA, no support.
  2. Tickets can only be created while the customer’s SLA is active (between start and end dates).
  3. High-priority tickets must be assigned to a senior engineer. You can’t assign a critical production issue to a junior team member.
  4. Every SLA must cover at least one service category. An SLA with no covered services is meaningless.

In a traditional Oracle setup, enforcing these rules would require at least four separate triggers across three tables, careful handling of mutating table errors, and a lot of testing to make sure they don’t interfere with each other.

With assertions, each rule is a single declarative statement.

Building the Schema

sql

DROP TABLE IF EXISTS tickets CASCADE CONSTRAINTS PURGE;
DROP TABLE IF EXISTS sla_services CASCADE CONSTRAINTS PURGE;
DROP TABLE IF EXISTS slas CASCADE CONSTRAINTS PURGE;
DROP TABLE IF EXISTS engineers CASCADE CONSTRAINTS PURGE;
DROP TABLE IF EXISTS customers CASCADE CONSTRAINTS PURGE;
CREATE TABLE customers (
id NUMBER GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
name VARCHAR2(200) NOT NULL,
company VARCHAR2(200),
created_at TIMESTAMP DEFAULT SYSTIMESTAMP
);
CREATE TABLE engineers (
id NUMBER GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
name VARCHAR2(200) NOT NULL,
seniority VARCHAR2(20) CHECK (
seniority IN ('junior','mid','senior','lead')
),
specialization VARCHAR2(100)
);
CREATE TABLE slas (
id NUMBER GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
customer_id NUMBER NOT NULL REFERENCES customers(id),
sla_tier VARCHAR2(20) CHECK (
sla_tier IN ('bronze','silver','gold','platinum')
),
start_date DATE NOT NULL,
end_date DATE NOT NULL,
CONSTRAINT sla_dates_valid CHECK (end_date > start_date)
);
CREATE TABLE sla_services (
id NUMBER GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
sla_id NUMBER NOT NULL REFERENCES slas(id),
service_name VARCHAR2(100) NOT NULL
);
CREATE TABLE tickets (
id NUMBER GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
customer_id NUMBER NOT NULL REFERENCES customers(id),
engineer_id NUMBER REFERENCES engineers(id),
priority VARCHAR2(20) CHECK (
priority IN ('low','medium','high','critical')
),
subject VARCHAR2(500) NOT NULL,
created_at TIMESTAMP DEFAULT SYSTIMESTAMP,
status VARCHAR2(20) DEFAULT 'open' CHECK (
status IN ('open','in_progress','resolved','closed')
)
);

Assertion 1: Customers Need an Active SLA to Submit Tickets

This is the core business rule. No active SLA, no ticket creation.

sql

CREATE ASSERTION ticket_requires_active_sla
CHECK (
ALL (SELECT customer_id, created_at FROM tickets) SATISFY
EXISTS (
SELECT 1 FROM slas
WHERE slas.customer_id = tickets.customer_id
AND tickets.created_at
BETWEEN slas.start_date AND slas.end_date
)
);

Read that in plain English: “For all tickets, there must exist an SLA for that customer where the ticket creation date falls within the SLA period.”

If someone tries to insert a ticket for a customer whose SLA has expired, the database will reject the transaction. No application code needed. No trigger needed. The rule is declarative and self-documenting.

Assertion 2: High-Priority Tickets Need Senior Engineers

This is a cross-table constraint that would be especially painful with triggers because it spans tickets and engineers.

sql

CREATE ASSERTION critical_tickets_need_senior_engineer
CHECK (
NOT EXISTS (
SELECT 1
FROM tickets t
JOIN engineers e ON t.engineer_id = e.id
WHERE t.priority IN ('high', 'critical')
AND e.seniority IN ('junior', 'mid')
)
);

This uses the existential pattern. It looks for any high-priority ticket assigned to a junior or mid-level engineer. If it finds one, the transaction fails. Simple, clear, and impossible to bypass from any application that touches this database.

Assertion 3: Every SLA Must Cover at Least One Service

An SLA without any covered services is a data integrity problem waiting to happen.

sql

CREATE ASSERTION sla_must_have_services
CHECK (
ALL (SELECT id FROM slas) SATISFY
EXISTS (
SELECT 1 FROM sla_services
WHERE sla_services.sla_id = slas.id
)
)
DEFERRABLE INITIALLY DEFERRED;

This one uses DEFERRABLE INITIALLY DEFERRED because of the chicken-and-egg problem: the foreign key on sla_services requires the SLA to exist first, but this assertion requires services to exist when an SLA exists. By deferring validation to commit time, you can insert both the SLA and its services in a single transaction.

Testing It Out

Let’s load some data and see the assertions in action:

sql

-- Insert customers
INSERT INTO customers (name, company)
VALUES ('Ahmad Hassan', 'TechCorp Jordan');
INSERT INTO customers (name, company)
VALUES ('Sara Ali', 'DataFlow ME');
-- Insert engineers
INSERT INTO engineers (name, seniority, specialization)
VALUES ('Omar Khalid', 'senior', 'Database');
INSERT INTO engineers (name, seniority, specialization)
VALUES ('Lina Nasser', 'junior', 'Networking');
-- Insert SLA with services (in one transaction
-- because of deferred assertion)
INSERT INTO slas (customer_id, sla_tier, start_date, end_date)
VALUES (1, 'gold', DATE '2025-01-01', DATE '2026-12-31');
INSERT INTO sla_services (sla_id, service_name)
VALUES (1, 'Database Support');
INSERT INTO sla_services (sla_id, service_name)
VALUES (1, '24/7 Monitoring');
COMMIT; -- Assertion validates here: SLA has services, OK
-- This should succeed: customer has active SLA,
-- senior engineer assigned
INSERT INTO tickets
(customer_id, engineer_id, priority, subject)
VALUES
(1, 1, 'critical', 'Production database performance issue');
COMMIT;

Now let’s try violating the rules:

sql

-- This should FAIL: assigning critical ticket
-- to junior engineer
INSERT INTO tickets
(customer_id, engineer_id, priority, subject)
VALUES
(1, 2, 'critical', 'Server outage');
COMMIT;
-- ERROR: assertion CRITICAL_TICKETS_NEED_SENIOR_ENGINEER violated
-- This should FAIL: customer 2 has no SLA
INSERT INTO tickets
(customer_id, engineer_id, priority, subject)
VALUES
(2, 1, 'low', 'General question');
COMMIT;
-- ERROR: assertion TICKET_REQUIRES_ACTIVE_SLA violated

The database enforces the rules. Every time. Regardless of which application, API, or batch job is inserting the data.

Why This Matters

The traditional approach to these rules would involve:

  • Four or more BEFORE INSERT triggers across multiple tables
  • Careful handling of ORA-04091 mutating table errors (probably using compound triggers or package variables)
  • Testing every combination of insert/update/delete across all tables
  • Documentation that explains what each trigger does and how they interact
  • A maintenance burden that grows with every new business rule

With assertions, each rule is one statement. They live in the data dictionary alongside your other constraints. You can query USER_CONSTRAINTS to see them. They are self-documenting. And Oracle’s internal incremental checking mechanism ensures they perform well because the database only validates the data that actually changed, not the entire table.

Practical Notes

Grant the privilege. CREATE ASSERTION is not included in RESOURCE. Use GRANT DB_DEVELOPER_ROLE TO your_user; or grant it explicitly.

Assertions share the constraint namespace. You cannot have an assertion and a constraint with the same name in the same schema.

Cross-schema assertions need ASSERTION REFERENCES. If your assertion references tables in another schema, you need this object privilege on those tables, and you must use fully qualified table names (synonyms are not supported).

Start with ENABLE NOVALIDATE on existing systems. This lets you add an assertion without checking existing data, which is essential when adding rules to a database that might already contain violations.

Subqueries can nest up to three levels. For most business rules, this is more than enough.

Resources

Thank you

Osama

Oracle Autonomous Database (ADB): A Technical Guide

Oracle Autonomous Database (ADB) on Oracle Cloud Infrastructure (OCI) is a cloud service that leverages machine learning to automate routine database tasks, offering users a self-driving, self-securing, and self-repairing database solution. This blog post will delve into setting up and interacting with an Autonomous Transaction Processing (ATP) instance, showcasing how to deploy a sample application to demonstrate its capabilities.

Overview of Oracle Autonomous Database

Self-Driving: Automates performance tuning and scaling.

Self-Securing: Applies security patches automatically.

Self-Repairing: Offers built-in high availability and backup solutions.

Step 1: Creating an Autonomous Database

Log into OCI Console: Go to console.oracle.com and log in to your account.

Create Autonomous Database:

  • Navigate to the Database section and click on Autonomous Database.
  • Click on Create Autonomous Database.
  • Fill in the required details:
    • Display Name: MyATPDB
    • Database Name: MYATPDB
    • Database Type: Autonomous Transaction Processing
    • CPU Count: 1 (can be adjusted later)
    • Storage: 1 TB (adjust as necessary)
  • Configure the Admin Password and ensure you store it securely.
  • Click Create Autonomous Database.

Step 2: Setting Up the Network

2.1: Create a Virtual Cloud Network (VCN)
  1. Navigate to the Networking Section.
  2. Click on Create VCN and fill in the necessary details:
    • VCN Name: MyVCN
    • CIDR Block: 10.0.0.0/16
    • Subnets: Create a public subnet with a CIDR block of 10.0.0.0/24.
2.2: Configure Security Lists
  1. In the VCN settings, add a security rule to allow traffic to your database:
    • Source CIDR: Your public IP address (for SQL Developer access).
    • IP Protocol: TCP
    • Source Port Range: All
    • Destination Port Range: 1522 (default for ADB)
Step 3: Connecting to the Autonomous Database
3.1: Download Wallet
  1. In the ADB console, navigate to your database and click on DB Connection.
  2. Download the Client Credentials (Wallet). This will be a zip file containing the wallet and connection files.
3.2: Set Up SQL Developer
  1. Open Oracle SQL Developer.
  2. Go to Tools > Preferences > Database > Advanced and set the Use Wallet option to true.
  3. In the Connections pane, click on the green + icon to create a new connection.
  4. Set the connection type to Cloud Wallet, then specify:
    • Connection Name: MyATPConnection
    • Username: ADMIN
    • Password: Your admin password
    • Wallet Location: Path to the unzipped wallet directory
  5. Click Test to verify the connection, then click Save.

Step 4: Creating a Sample Schema and Table

Once connected to your database, execute the following SQL commands to create a sample schema and a table:

-- Create a new user/schema
CREATE USER sample_user IDENTIFIED BY SamplePassword;
GRANT ALL PRIVILEGES TO sample_user;

-- Connect as the new user
ALTER SESSION SET CURRENT_SCHEMA = sample_user;

-- Create a sample table
CREATE TABLE employees (
employee_id NUMBER GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
first_name VARCHAR2(50) NOT NULL,
last_name VARCHAR2(50) NOT NULL,
email VARCHAR2(100) NOT NULL UNIQUE,
hire_date DATE DEFAULT CURRENT_DATE
);

-- Insert sample data
INSERT INTO employees (first_name, last_name, email)
VALUES ('John', 'Doe', 'john.doe@example.com');

INSERT INTO employees (first_name, last_name, email)
VALUES ('Jane', 'Smith', 'jane.smith@example.com');

COMMIT;

Querying the Data

To verify the data insertion, run:

SELECT * FROM employees;

Step 5: Using Autonomous Database Features

5.1: Auto-Scaling

ADB allows you to scale compute and storage resources automatically. To enable auto-scaling:

  1. Navigate to your Autonomous Database instance in the OCI console.
  2. Click on Edit.
  3. Enable Auto Scaling for both CPU and storage.
  4. Specify the minimum and maximum resources.

5.2: Monitoring Performance

Utilize the Performance Hub feature to monitor real-time database performance. You can view metrics like:

  • Active Sessions
  • Wait Events
  • Resource Consumption

Regads
Osama

Changing Archive Log Destination

Today I am Gonna Show you How to change Archive log Destination in Two Ways :

1.Temporarily Changing the Destination Using SQL*Plus

sqlplus / as sysdba

sql> archive log list;

Database log mode Archive Mode
Automatic archival Enabled
Archive destination /u01/app/oracle/product/10.2.0/db_1/dbs/arch
Oldest online log sequence 9285
Next log sequence to archive 9287
Current log sequence 9287

 To change the location

sql>ARCHIVE LOG START ‘/u01/arch’;

To Verify your changes:

sql> archive log list;
Database log mode Archive Mode
Automatic archival Enabled
Archive destination /oracle/arch
Oldest online log sequence 9285
Next log sequence to archive 9287
Current log sequence 9287

2-Permanently Changing the Destination Using SQL*Plus

sqlplus / as sysdba

ALTER SYSTEM SET log_archive_dest =’/oradata/arch’ scope=both;

To Verify your changes:

sql> archive log list;

Database log mode Archive Mode
Automatic archival Enabled
Archive destination /oracle/arch
Oldest online log sequence 9285
Next log sequence to archive 9287
Current log sequence 9287   

To change the size of archive log

SQL> alter system SET DB_RECOVERY_FILE_DEST_SIZE = 10G SCOPE=BOTH SID=’orcl’;

System altered. 

Thank You
Osama Mustafa

  

Linux Command On Windows :)

Install Cygwin

Cygwin comes with a normal setup.exe to install in Windows, but there are a couple steps you will need to pay attention to, so we will walk you through the installation.

To keep the installation small while saving bandwidth for you and Cygwin, the default installer will download only the files you need from the internet.

The default install path is C:\Cygwin

Click next until you come to a download mirror selection. Unfortunately, the installer does not say where the mirrors are located so in most cases you might as well just guess which mirror works best.

After you have selected a mirror, the installer will download a list of available packages for you to install. Here is where things get a bit more intimidating.
There will be hundreds of packages available separated by multiple different categories. If you don’t know what the package is you can leave the default selection and install additional packages later by running the installer again.


If you know what package you need, you can search for it and the results will be automatically filtered.

Once you click next, it will take a little while to download all the selected tools and then finish the installation.

Add Cygwin Path to Windows Environment Variable

In the left column click on advanced system settings to open the system properties window we are looking for


From the advanced tab, click on environment variables at the bottom.

Then in the system variables, locate the path variable and click edit.

At the end of the variable value option, add the Cygwin bin location like so.
;C:\Cygwin\bin
Note: Make sure you add a semicolon to separate it from the other values.




Click OK to close out of the window and then open a command prompt to test out a few Linux commands.

Enjoy

Osama Mustafa

How to recreate DBA roles if accidentally drop?

VERSION 7.X:   From Server Manager, logged in as internal or sys, run the following commands:

         SVRMGRL > create role dba;
        SVRMGRL > grant all privileges to dba with admin option;

VERSION 8.X:   From SQL*Plus or Server Manager, logged in as internal or sys, run the   following commands:   

SQL > create role dba;       
SQL > grant all privileges to dba with admin option;       
SQL > grant select_catalog_role to dba with admin option;       
SQL > grant execute_catalog_role to dba with admin option;       
SQL > grant delete_catalog_role to dba with admin option;

Explanation:  ============    
The DBA role is created at database creation time by the “sql.bsq” script. (The “sql.bsq” script is typically found in the $ORACLE_HOME/dbs or the $ORACLE_HOME/rdbms/admin directory.)  Additional grants are made to dba through other scripts in the admin directory.  For example, exp_full_database  and imp_full_database in catexp.sql.

Note : Version 8.X will works On 10g , 11g

Thank you
Osama mustafa

Upgrade 11.1.0.6 to 11.1.0.7 – APPS DBA

On each application tier server node,
shut down all server processes or services.

On the database tier server node,
shut down the database listener in the old Oracle home.

On the database server node,
as the owner of the Oracle 11g file system and database instance, unzip and extract the 11.1.0.7 patch set file for your platform.

Use the runInstaller in the patchset to start the OUI.

Once the OUi starts from the LOV s in the Oracle_home list choose the appropriate one to be upgraded and the location also.

The present oracle_home and its location will appear if the environment is set correctly.

Proceed with the next steps on the OUI screen.

On next Screen it will Say Configuration Assistant has failed.Skip this Step since its not required for Oracle E-Business Suite .

MOST IMPORTANT STEP IN UPGRADE .
After Installation you need to Apply the RDBMS patches7486407 and 7684818

Copy $APPL_TOP/admin/adstats.sql from the administration server node to the database server node.

$ sqlplus / as sysdba
SQL> shutdown normal;
SQL> startup restrict;
SQL> @adstats.sql
SQL> shutdown normal;
SQL> startup;
SQL> exit;

Thank You
Osama mustafa

Change Enterprise manager password for 11g

For the DB Control Release 11.2 and higher, you need to set the environment variable ORACLE_UNQNAME to the value of the DB_UNIQUE_NAME database parameter.

Steps :

1.Stop the DB Control
On Unix
$ emctl stop dbconsole

On Windows
Open a Command Window and type
**> emctl stop dbconsole

2.Check that the DB Control is stopped
 On Unix
$ emctl status dbconsole
 

On Windows
Open a Command Window and type
 **>emctl status dbconsole

3.Connect to the database as a user with DBA privilege with SQL*Plus and execute:

SQL> alter user sysman identified by ;
 
4.Check the new password
SQL> connect sysman/[@database_alias]
 
5.From the database directory $ORACLE_HOME/bin, execute:
On Unix

$ emctl setpasswd dbconsole
Provide the new SYSMAN password
 
On Windows

**>: emctl setpasswd dbconsoleProvide the new SYSMAN password
 
6.Restart the DB Control
On Unix
$ emctl start dbconsole
 
On Windows
Open a DOS Command Window and type
**>: emctl start dbconsole
 

Thank you 

Osama Mustafa