Monday, October 12, 2026

Oracle Database Tip: Generate SQL Scripts to Change User Default Tablespaces πŸš€

 Have you ever needed to change the default tablespace for multiple Oracle database users without manually writing individual ALTER USER statements?

Here’s a simple SQL*Plus scripting technique that can save time when working with database administration tasks.

The idea is to generate the SQL commands dynamically from DBA_USERS, spool them into a .sql file, and execute them only after reviewing the generated statements.

πŸ”Ή 1. Generate a script to alter users to the SYSTEM tablespace

SET ECHO OFF
SET FEEDBACK OFF
SET HEADING OFF
SET PAGESIZE 0
SET LINESIZE 32767
SET TRIMSPOOL ON
SET VERIFY OFF
SET TERMOUT OFF

SPOOL alter_non_oracle_users_to_system.sql

SELECT 'ALTER USER "' || username ||
       '" DEFAULT TABLESPACE SYSTEM;'
FROM   dba_users
WHERE  oracle_maintained = 'N'
AND    default_tablespace <> 'SYSTEM'
ORDER  BY username;

SPOOL OFF

This script generates ALTER USER statements for non-Oracle-maintained users whose current default tablespace is not SYSTEM.

πŸ”Ή 2. Generate a script to restore the original default tablespaces

Before changing the users, it's useful to preserve their current default tablespace assignments.

SET ECHO OFF
SET FEEDBACK OFF
SET HEADING OFF
SET PAGESIZE 0
SET LINESIZE 32767
SET TRIMSPOOL ON
SET VERIFY OFF
SET TERMOUT OFF

SPOOL restore_non_oracle_users_default_ts.sql

SELECT 'ALTER USER "' || username ||
       '" DEFAULT TABLESPACE "' ||
       default_tablespace || '";'
FROM   dba_users
WHERE  oracle_maintained = 'N'
AND    default_tablespace <> 'SYSTEM'
ORDER  BY username;

SPOOL OFF

This produces a separate SQL file containing the original default tablespace assignments for the users currently matching the filter.

πŸ’‘ Why is this approach useful?

  • Automation: Generate commands for multiple users in one query.
  • Consistency: Apply the same change across the intended set of accounts.
  • Reversibility: Keep a separate script to restore the captured tablespace assignments.
  • Maintainability: Store generated scripts for review, auditing, and controlled execution.

⚠️ Important considerations

  • Changing a user's default tablespace does not move existing objects or change their tablespace placement.
  • SYSTEM is a critical Oracle-managed tablespace and should generally not be used as the default tablespace for application users. Prefer an appropriate application tablespace.
  • Generate and review the restoration script before changing the users. The restoration query must be run against the original assignments; after the change, the original tablespace values are no longer available from that query.
  • These scripts depend on access to DBA_USERS and should be tested in a non-production environment first.

The takeaway: A simple SQL query combined with SQL*Plus SPOOL can turn repetitive DBA work into a reusable, reviewable SQL script.

πŸ’¬ Do you use SQL generation to automate Oracle Database administration tasks?

#OracleDatabase #OracleDBA #SQL #SQLPlus #DatabaseAdministration #Automation

Generating SQL scripts using the VALUES clause

Generating SQL Scripts Using the VALUES Clause in Oracle AI Database 26ai πŸš€

Sometimes, a small SQL feature can make a big difference in developer productivity.

Oracle AI Database 26ai introduces a table value constructor for SELECT, allowing the VALUES clause to act as an inline table in the FROM or WITH clause.

Why is this useful? You can work with a small set of values directly in SQL without creating a physical table or writing multiple UNION ALL statements.

πŸ’‘ A practical use case: Dynamically generating SQL scripts

Suppose you're building Oracle database demos or lab environments and need to create database users with specific privileges.

Instead of manually writing every GRANT statement, you can generate them dynamically:

select
'grant ' || privilege || ' to singhsha;'
from
(
values
('create assertion'),
('create domain'),
('create job'),
('create mle'),
('create session'),
('debug connect session'),
('execute dynamic mle'),
('create materialized view'),
('create procedure'),
('create property graph'),
('create sequence'),
('create synonym'),
('create table'),
('create trigger'),
('create type'),
('create view')
) p (privilege);

The VALUES clause creates an inline table named p, with a column called privilege. The SELECT statement then generates a GRANT command for each row.

The result? A ready-to-review list of SQL statements that can be incorporated into an initialization script, checked into Git, and reused when setting up demo environments.

Which results in this neat script:

grant create assertion to singhsha; grant create domain to singhsha; grant create job to singhsha; grant create mle to singhsha; grant create session to singhsha; grant debug connect session to singhsha; grant execute dynamic mle to singhsha; grant create materialized view to singhsha; grant create procedure to singhsha; grant create property graph to singhsha; grant create sequence to singhsha; grant create synonym to singhsha; grant create table to singhsha; grant create trigger to singhsha; grant create type to singhsha; grant create view to singhsha;

After careful review, it can be deployed to my demo/lab environment and checked into Git.


Why I like this approach

✅ Less boilerplate SQL

✅ Better readability and maintainability

✅ No need for repetitive UNION ALL queries

✅ Convenient generation of scripts for database initialization, migrations, and administrative tasks

The generated statements should always be reviewed before execution, particularly when dealing with database privileges.

The takeaway: The VALUES clause isn't just a convenient way to define inline data. In Oracle AI Database 26ai, it can also simplify the process of generating SQL scripts dynamically.

πŸ“š Explore the Oracle AI Database 26ai documentation to learn more about the VALUES clause.

Happy scripting!

#OracleDatabase #Oracle26ai #SQL #DatabaseDevelopment #Automation #PLSQL

Thursday, September 17, 2026

How to rollback an MRP (Monthly Recommended Patch)

 

How to rollback an MRP

You know, I am not a fan of in-place patching. Actually, I think everyone should always patch out-of-place. But I know that for instance EBS with a 19c database requires in-place patching, and out-of-place will be supported only with 26ai for EBS. Therefore, in relation to an exercise I did over the weekend, let me share how to rollback an MRP.

How to rollback an MRP

What is an MRP?

Very briefly, it stands for Monthly Recommended Patch – thus MRP. And it co-exists with CSPUs. I described the difference here. Read more if you are interested.

 

Rolling back an MRP

The reason why I rolled back an MRP was simply that I installed the wrong MRP. I wanted MRP3 for 19.31 but downloaded MRP4. So, after applying it and realizing how terrible in-place-patching is, I wanted to roll it back.

The README of the MRP has a deinstallation section under 4.

Follow the steps below, to roll back specific fixes included in the MRP patch.

  1. Shut down all instances and listeners associated with the Oracle home that you are updating.
  2. $ORACLE_HOME/OPatch/opatch nrollback -id <patch1, patch2… patchn>   
  3. Start all instances and listeners associated with the Oracle home.

So, at first, the “nrollback” command is needed instead of the “rollback” since we are going to rollback multiple patches. But unfortunately, nowhere in the README does it specify the command ready for copy&paste.

Instead, I need to fetch the patch numbers, which in this case, is a nasty exercise. It could have done it with Kodex of course, but the first step is still required:

$ORACLE_HOME/OPatch/opatch lspatches
39779336;Fix for Bug 39779336
39750798;Fix for Bug 39750798
39661114;Fix for Bug 39661114
39661105;Fix for Bug 39661105
39661089;Fix for Bug 39661089
39598920;MERGE ON DATABASE RU 19.31.0.0.0 OF 39060101 39127997 39128024
39594515;Fix for Bug 39594515
39575331;Fix for Bug 39575331
39498075;MERGE ON DATABASE RU 19.31.0.0.0 OF 39360889 39370763
39429942; DB_DOMAIN NAME CHANGED TO UPPER CASE AUTOMATICALLY
39290842;Fix for Bug 39290842
39234501;Fix for Bug 39234501
39060241;Fix for Bug 39060241
39052286;Fix for Bug 39052286
38937302;AFTER APPLYING 19.30 DBRU THE AUTOTASK SQL TUNING ADVISOR GETS ORA-13602
38494116;Fix for Bug 38494116
39196236;DATAPUMP BUNDLE PATCH 19.31.0.0.0
38906621;OJVM RELEASE UPDATE: 19.31.0.0.260421 (38906621)
39034528;Database Release Update : 19.31.0.0.260421 (REL-APR2026) (39034528)
29585399;OCW RELEASE UPDATE 19.3.0.0.0 (29585399)
OPatch succeeded.

Everything except the last 4 lines are patches belonging to the MRP.

I passed this into my editor and edited it to form the command. Of course, I could have done this task with Kodex or any other LLM as well.

$ORACLE_HOME/OPatch/opatch nrollback -id 39779336,39750798,39661114,39661105,39661089,39598920,39594515,39575331,39498075,39429942,39290842,39234501,39060241,39052286,38937302,38494116

I don’t spend any attention to figure out from the README which of the patches are “online”, and which aren’t since I applied all of them entirely.

But there is no way to rollback an MRP by using the MRP’s patch number: 39834048: DATABASE MRP 19.31.0.0.260818

 

What happens during rollback?

This is a pretty time-consuming exercise. It rolls back patch after patch. And if I’m not mistaken, it updates the inventory after every removal.

$ORACLE_HOME/OPatch/opatch nrollback -id 39779336,39750798,39661114,39661105,39661089,39598920,39594515,39575331,39498075,39429942,39290842,39234501,39060241,39052286,38937302,38494116
Oracle Interim Patch Installer version 12.2.0.1.51
Copyright (c) 2026, Oracle Corporation. All rights reserved.
Oracle Home : /u01/app/oracle/product/19
Central Inventory : /u01/app/oraInventory
from : /u01/app/oracle/product/19/oraInst.loc
OPatch version : 12.2.0.1.51
OUI version : 12.2.0.7.0
Log file location : /u01/app/oracle/product/19/cfgtoollogs/opatch/opatch2026-08-29_14-15-31PM_1.log
Patches will be rolled back in the following order:
39779336 39750798 39661114 39661105 39661089 39598920 39594515 39575331 39498075 39429942 39290842 39234501 39060241 39052286 38937302 38494116
The following patch(es) will be rolled back: 39779336 39750798 39661114 39661105 39661089 39598920 39594515 39575331 39498075 39429942 39290842 39234501 39060241 39052286 38937302 38494116
Please shutdown Oracle instances running out of this ORACLE_HOME on the local system.
(Oracle Home = '/u01/app/oracle/product/19')
Is the local system ready for patching? [y|n]
y
User Responded with: Y
Rolling back patch 39779336...
RollbackSession rolling back interim patch '39779336' from OH '/u01/app/oracle/product/19'
Patching component oracle.perlint, 5.28.1.0.0...
RollbackSession removing interim patch '39779336' from inventory
Rolling back patch 39750798...
RollbackSession rolling back interim patch '39750798' from OH '/u01/app/oracle/product/19'
Patching component oracle.sdo, 19.0.0.0.0...
Patching component oracle.rdbms.dbscripts, 19.0.0.0.0...
RollbackSession removing interim patch '39750798' from inventory
Rolling back patch 39661114...
RollbackSession rolling back interim patch '39661114' from OH '/u01/app/oracle/product/19'
Patching component oracle.rdbms, 19.0.0.0.0...
Patching component oracle.network.rsf, 19.0.0.0.0...
Patching component oracle.rdbms.rsf, 19.0.0.0.0...
RollbackSession removing interim patch '39661114' from inventory
(... cut out many lines for readability ...)
Rolling back patch 38494116...
RollbackSession rolling back interim patch '38494116' from OH '/u01/app/oracle/product/19'
Patching component oracle.ldap.rsf, 19.0.0.0.0...
Patching component oracle.rdbms.rsf, 19.0.0.0.0...
Patching component oracle.rdbms, 19.0.0.0.0...
Patching component oracle.ldap.client, 19.0.0.0.0...
RollbackSession removing interim patch '38494116' from inventory
Log file location: /u01/app/oracle/product/19/cfgtoollogs/opatch/opatch2026-08-29_14-15-31PM_1.log
OPatch succeeded.

The whole exercise took from 2:15h until 2:37h – 22 minutes (!!) in total.

If you ask my why I don’t like in-place patching, this is one of the many reasons. It has significant disadvantages compared to out-of-place patching.

 

datapatch

Of course, in case you are trying this rollback of an MRP for real, you must run datapatch in any case at the end of the process in your database(s). Make sure that all PDBs are opened as well to avoid any issues. datapatch will sort all necessary tasks out for you. You just call it, and it does the job.

 

Summary

Rolling back an MRP may be necessary especially for those who do in-place patching.

Be prepared that currently the README does not list the whole command, you need to put it together by yourself. And you need to use nrollback, not rollback, since an MRP contains multiple one-off fixes.

In addition, take into account that every patch will be rolled back sequentially. Thus, an MRP with more patches takes quite a bit. As you see in my example, it took 22 minutes – on a fairly fast system with a decent storage.

I highly recommend out-of-place patching. This avoids all the hassle, and it makes patching not even faster but also more solid and secure since you get rid of the ballast, and you have a fallback if something goes wrong.