Feed aggregator

Will Oracle AI Database be renamed again to Oracle SI Database?

Tom Kyte - Fri, 2026-10-09 21:03
(Not so serious) question is in the subject :-)
Categories: DBA Blogs

how to find lock blockers and lock waiters in RAC

Tom Kyte - Fri, 2026-10-09 21:03
Hello, I know that in single instance I can use DBA_BLOCKERS and DBA_WAITERS to find blocking sessions and waiting sessions. But this does not work in RAC if blocking and waiting sessions run on different instances. How should I write the query using GV$LOCK in RAC ? Could you also explain what does GV$LOCK.BLOCK = 2 really mean and how this can be used; 2 - The lock is not blocking any blocked processes on the local node, but it may or may not be blocking processes on remote nodes. This value is used only in Oracle Real Application Clusters (Oracle RAC) configurations (not in single instance configurations). Thanks.
Categories: DBA Blogs

DBMS_DST.get_latest_timezone_version

Tom Kyte - Fri, 2026-10-09 21:03
Hi, just learned about DBMS_DST.get_latest_timezone_version today ( https://oracle-base.com/articles/misc/update-database-time-zone-file ) <code> SQL> SELECT * FROM v$timezone_file; FILENAME VERSION CON_ID -------------------- ---------- ---------- timezlrg_40.dat 40 0 SQL> SELECT DBMS_DST.get_latest_timezone_version from dual; GET_LATEST_TIMEZONE_VERSION --------------------------- 44 SQL> select banner_full from v$version; BANNER_FULL ---------------------------------------------------------------------------------------------------- Oracle Database 19c EE Extreme Perf Release 19.0.0.0.0 - Production Version 19.28.0.0.0 </code>, want to know more about it (what does it do ? how does it work ?, what are prerequisities>?, which exceptions can it raise ?) however, it does not seem to be documented. ( neither https://docs.oracle.com/en/database/oracle/oracle-database/19/arpls/DBMS_DST.html nor on https://docs.oracle.com/en/database/oracle/oracle-database/26/arpls/DBMS_DST.html )
Categories: DBA Blogs

How can I set JOB_CREATOR to the job owner when creating a Scheduler job in another schema?

Tom Kyte - Fri, 2026-10-09 21:03
Hi, I have a requirement where multiple users need to create Oracle Scheduler jobs in a dedicated target schema. For example, suppose I have two users: USER1 ? the user who creates the job USER2 ? the target schema and intended owner of the job If USER1 creates a Scheduler job for USER2, for example: BEGIN DBMS_SCHEDULER.CREATE_JOB( job_name => 'USER2.MY_JOB', job_type => 'PLSQL_BLOCK', job_action => 'BEGIN NULL; END;', enabled => TRUE ); END; / the resulting job has: OWNER = USER2 JOB_CREATOR = USER1 This is expected behavior. However, there is an issue with this approach in our environment. If USER1 is later dropped from the database, the job still exists and its JOB_CREATOR points to a user that no longer exists. Our requirement is that when USER1 creates a job for USER2, both of the following should be USER2: OWNER = USER2 JOB_CREATOR = USER2 We tried creating a procedure in USER2 that calls DBMS_SCHEDULER.CREATE_JOB, and then calling that procedure from USER1. However, JOB_CREATOR is still recorded as USER1. Is there a supported way in Oracle to create a Scheduler job from USER1 for USER2 while having JOB_CREATOR recorded as USER2? Alternatively, is there a recommended Oracle architecture for this use case, especially when USER1 may be dropped later but the Scheduler jobs must remain valid and associated with USER2? Would Proxy Authentication, definer-rights procedures, or another mechanism be appropriate for this? Thank you.
Categories: DBA Blogs

Application Root uninstall issues

Tom Kyte - Fri, 2026-10-09 21:03
I have an application root with several applications ( from testing out the functionality) Example: Application A create user XXX Application B create table XXX.T1 ... Now I decide to uninstall application A <code>alter pluggable database application A begin uninstall;</code> but can't drop the user XXX <code>drop user XXX cascade;</code> Error report - ORA-00604: error occurred at recursive SQL level 1 ORA-65311: cannot modify the common object of another application so I realise I need to first drop the table XXX.T1 which was installed by application B, so I need to uninstall application B to do this but I can't end the uninstall of application A <code>alter pluggable database application A end uninstall;</code> Error report - ORA-65344: cannot uninstall or purge an application that has objects, users, roles, and profiles and I can't begin the uninstall of application B <code>ALTER PLUGGABLE DATABASE APPLICATION B BEGIN UNINSTALL;</code> Error report - ORA-65213: existing application action in progress If I try to drop the user outside of the application action <code>drop user XXX cascade</code> Error report - ORA-00604: error occurred at recursive SQL level 1 ORA-65311: cannot modify the common object of another application I'm stuck. When I check the objects in Application A there is none ( from dba_objects ) because the only thing in application A is the create user statement ( seen from dba_app_statements). How can I abort the uninstall of application A as this is preventing me from beginning an uninstall of application B ? Ideally I'd like to remove all the applications I created while testing this functionality. Thanks
Categories: DBA Blogs

Reg career path

Tom Kyte - Fri, 2026-10-09 21:03
HI Tom, My question is I have 9+ years of experience working in oracle as a developer with sql,pl/sql, forms,reports and Apex.. Do you think considering my experience it is required for me to go for developer with DBA or stay as Developer itself understanding the latest features in every new version of sql and pl/sql? Is it practically possible for me to be a developer and a DBA. Which is better for career opportunities and long stand in the industry. Your sincere suggestion on this is of great help fro my future career also. Thanks Prashanth R
Categories: DBA Blogs

Calling Oracle Apex from Oracle Forms without User ID and Password Authentication

Tom Kyte - Fri, 2026-10-09 21:03
We have an application developed using Oracle Forms and Oracle Reports, where application user IDs and passwords are maintained and authenticated within our database. As part of ongoing business requirements, we frequently receive requests for data extraction and reporting. We are currently evaluating Oracle APEX as a platform for developing these reports. We would appreciate your guidance on the following questions: 1. Can reports developed in Oracle APEX be launched directly from Oracle Forms? a) If this is supported, what is the recommended integration approach? 2.What is the launch mechanism for invoking an Oracle APEX report from Oracle Forms? Could you provide a sample URL or reference architecture demonstrating this integration? 3.How should user authentication be handled between Oracle Forms and Oracle APEX? a) Is a separate login to Oracle APEX required when a user launches an APEX report from Oracle Forms? b) Alternatively, can Oracle APEX leverage the existing authenticated user session from Oracle Forms, enabling a seamless Single Sign-On (SSO) experience without prompting the user to log in again?
Categories: DBA Blogs

Moving data from one partition to another partition of the same table -Best methods

Tom Kyte - Fri, 2026-10-09 21:03
1. The table is partitioned by list ex: week1_user1, week2_user1 2. Each partition contains around 900M records 3. Every week, we are truncating the week2_user1 partition and moving the data from week1_user1 to week2_user1 <code>insert /*+ parallel(8) enable_parallel_dml */ into t1 select /*+ FULL(t1) */'week2_user1' col1,col2,col3 from t1 where col1='week1_user1';</code> col1 is the partition by list column 4. Plan looks like <code> -------------------------------------------------------------------------------------------------------------------------------------------------------------- | Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | Pstart| Pstop | TQ |IN-OUT| PQ Distrib | -------------------------------------------------------------------------------------------------------------------------------------------------------------- | 0 | INSERT STATEMENT | | | | 78705 (100)| | | | | | | | 1 | PX COORDINATOR | | | | | | | | | | | | 2 | PX SEND QC (RANDOM) | :TQ10002 | 682M| 165G| 78705 (15)| 00:00:07 | | | Q1,02 | P->S | QC (RAND) | | 3 | INDEX MAINTENANCE | T1 | | | | | | | Q1,02 | PCWP | | | 4 | PX RECEIVE | | 682M| 165G| 78705 (15)| 00:00:07 | | | Q1,02 | PCWP | | | 5 | PX SEND RANGE | :TQ10001 | 682M| 165G| 78705 (15)| 00:00:07 | | | Q1,01 | P->P | RANGE | | 6 | LOAD AS SELECT (HIGH WATER MARK BROKERED)| T1| | | | | | | Q1,01 | PCWP | | | 7 | OPTIMIZER STATISTICS GATHERING | | 682M| 165G| 78705 (15)| 00:00:07 | | | Q1,01 | PCWP | | | 8 | PX RECEIVE | | 682M| 165G| 78705 (15)| 00:00:07 | | | Q1,01 | PCWP | | | 9 | PX SEND RANDOM LOCAL | :TQ10000 | 682M| 165G| 78705 (15)| 00:00:07 | | | Q1,00 | P->P | RANDOM LOCA| | 10 | PX BLOCK ITERATOR | | 682M| 165G| 78705 (15)| 00:00:07 | KEY | KEY | Q1,00 | PCWC | | |* 11 | TABLE ACCESS FULL | T1| 682M| 165G| 78705 (15)| 00:00:07 | KEY | KEY | Q1,00 | PCWP | | ------------------------------------------------------------------------------------------------------------------------------------------------------------...
Categories: DBA Blogs

Oracle Forensics - Can we Understand what Happened?

Pete Finnigan - Fri, 2026-10-09 21:03
In forensics the evidence can be grouped into two blocks. The first is changes made to the database and the second is read activity. Usually an attackers goal is to steal data (read) but he may need to do changes....[Read More]

Posted by Pete On 06/10/26 At 02:16 PM

Categories: Security Blogs

Generate a PL/SQL Encryption and Decryption Package, Again

Pete Finnigan - Fri, 2026-10-09 21:03
First, I am appending this text here before I post this blog live as I realised its almost exactly 22 years since I started this blog in September 2004; so happy birthday to my blog!! Now to the actual subject....[Read More]

Posted by Pete On 28/09/26 At 08:36 AM

Categories: Security Blogs

Extreme PL/SQL - Creating a simple programming language interpreter in PL/SQL

Pete Finnigan - Fri, 2026-10-09 21:03
Last week I posted a blog - Extreme PL/SQL - Running an Assembly Language Program in PL/SQL where I presented the fact that I have written a virtual machine on PL/SQL and also an assembler in PL/SQL and showed how....[Read More]

Posted by Pete On 23/09/26 At 07:52 AM

Categories: Security Blogs

Extreme PL/SQL - Running an Assembly Language Program in PL/SQL

Pete Finnigan - Fri, 2026-10-09 21:03
Back in 2022 I started work on an idea that I could build an interpreter for a simple language in PL/SQL and then building on that create a Virtual Machine in PL/SQL and an assembler in PL/SQL for the assembly....[Read More]

Posted by Pete On 16/09/26 At 08:54 AM

Categories: Security Blogs

Oracle Security AnythingLLM Tools

Pete Finnigan - Fri, 2026-10-09 21:03
As you will have noticed in my blogs I have been using and playing with AI and Large Language Models (LLMs) for a while now but with a focus on Oracle Security still. I want to understand the capabilities of....[Read More]

Posted by Pete On 10/09/26 At 10:35 AM

Categories: Security Blogs

Perform a Security Audit of an Old Oracle Database

Pete Finnigan - Fri, 2026-10-09 21:03
We support doing security audits of all of the current Oracle databases from 19c to 21c and 26ai. We also support doing security audits on databases in your own data center or in the cloud. No matter where the database....[Read More]

Posted by Pete On 09/09/26 At 12:33 PM

Categories: Security Blogs

Can Synonyms Point to Synonyms?

Pete Finnigan - Fri, 2026-10-09 21:03
I got asked a question via a DM on one of my social media channels a few days ago and thought it worth an investigation. They asked me; Can an Oracle synonym point to another synonym, ad infinitum. Let me....[Read More]

Posted by Pete On 07/09/26 At 08:08 AM

Categories: Security Blogs

Contexts are Database Level Objects in Oracle

Pete Finnigan - Fri, 2026-10-09 21:03
I was asked by someone recently why their context values had disappeared. They had two database users that created the same context. Yes, I know you cannot do that but there was a subtle reason that the code did not....[Read More]

Posted by Pete On 02/09/26 At 09:30 AM

Categories: Security Blogs

GoldenGate 26ai AutoSchema TYPEMAP: overriding the default datatype mapping

Yann Neuhaus - Thu, 2026-10-08 01:20

In my blog about the new automatic schema evolution feature in GoldenGate 26ai, I was replicating the following table from Oracle to PostgreSQL:

CREATE TABLE autosch.t1 (
    id NUMBER PRIMARY KEY,
    name VARCHAR2(50),
    amount NUMBER(10,2),
    created DATE DEFAULT SYSDATE,
    note VARCHAR2(20) DEFAULT 'added'
);

The replicat created a PostgreSQL table from an Oracle one during an initial load. It worked, but one choice was not mine: the NUMBER primary key became a varchar(50). Automatic Schema Evolution takes its datatypes from a default mapping, and the TYPEMAP option is how you override it.

Automatic schema evolution is a preview feature in GoldenGate 26ai: its syntax, messages and behavior can change before it becomes generally available, so the results below apply to the 23.26.3 release I tested.

Where the default mapping comes from

Each GoldenGate 26ai home ships the default mappings as one JSON file per source database and per target database. They are in $OGG_HOME/lib/utl/autoschema/MappingJSON, where MappingGenerator.py combines a source and a target file. For this example, the files are AutoSchemaOracleSourceMappings.json and AutoSchemaPostgreSQLTargetMappings.json.

Reading the two JSON files for an Oracle source and a PostgreSQL target, the default mappings that matter for the example table are:

Oracle source typePostgreSQL target typenumber(precision,scale)decimal(precision,scale)numbervarchar(precision), if the source data length is at most 10485760numbertext, if the value is still too large to be accommodateddatetimestamp(precision)

A NUMBER without precision maps to varchar, and a NUMBER(p,s) maps to decimal(p,s). This is why the key of my source table did not become a number.

For each test below, I drop autosch.t1 on PostgreSQL and start a new replicat, always named REPTM, on the same file.

Overriding one type

AUTOSCHEMAOPTIONS TYPEMAP must be written on a separate line, next to DDL AUTOSCHEMA INCLUDE ALL. The source type and the target type are separated by an equal sign. The option maps every occurrence of the source type to the given target type. Both types must be enclosed in single quotes, otherwise the replicat abends with OGG-30694:

REPLICAT REPTM
TARGETDB ogg_pg_01 USERIDALIAS pgalias DOMAIN OracleGoldenGate
DDL AUTOSCHEMA INCLUDE ALL
AUTOSCHEMAOPTIONS TYPEMAP 'number' = 'bigint'
MAP AUTOSCH.*, TARGET AUTOSCH.*;

The replicat report logs that the override was read:

2026-09-19T15:23:47.078+0000  INFO    OGG-30632  Oracle GoldenGate Delivery for PostgreSQL, REPTM.prm:  AutoSchemaOptions TypeMap is used, TypeNameMap: number -> bigint.

The table created in PostgreSQL, seen with \d autosch.t1 in psql, now shows a bigint as primary key

ogg_test_pg=# \d autosch.t1
                            Table "autosch.t1"
 Column  |              Type              | Collation | Nullable | Default
---------+--------------------------------+-----------+----------+---------
 id      | bigint                         |           | not null |
 name    | character varying(50)          |           |          |
 amount  | numeric(10,2)                  |           |          |
 created | timestamp(0) without time zone |           |          |
 note    | character varying(20)          |           |          |
Indexes:
    "t1_pkey" PRIMARY KEY, btree (id)

The amount column type did not change, since 'number' matches the source type NUMBER without precision. NUMBER(10,2) is another source type, and is not affected by the TYPEMAP instruction.

Mapping several types with AUTOSCHEMAOPTIONS

When mapping multiple types, each mapping needs a dedicated AUTOSCHEMAOPTIONS line:

DDL AUTOSCHEMA INCLUDE ALL
AUTOSCHEMAOPTIONS TYPEMAP 'number' = 'bigint'
AUTOSCHEMAOPTIONS TYPEMAP 'date' = 'date'
AUTOSCHEMAOPTIONS TYPEMAP 'number(10,2)' = 'real'

Every entry is logged separately:

2026-09-19T16:29:07.403+0000  INFO    OGG-30632  Oracle GoldenGate Delivery for PostgreSQL, REPTM.prm:  AutoSchemaOptions TypeMap is used, TypeNameMap: date -> date.
2026-09-19T16:29:07.403+0000  INFO    OGG-30632  Oracle GoldenGate Delivery for PostgreSQL, REPTM.prm:  AutoSchemaOptions TypeMap is used, TypeNameMap: number -> bigint.
2026-09-19T16:29:07.403+0000  INFO    OGG-30632  Oracle GoldenGate Delivery for PostgreSQL, REPTM.prm:  AutoSchemaOptions TypeMap is used, TypeNameMap: number\(10,2\) -> real.

And this is the resulting table:

 Column  |         Type          | Collation | Nullable | Default
---------+-----------------------+-----------+----------+---------
 id      | bigint                |           | not null |
 name    | character varying(50) |           |          |
 amount  | real                  |           |          |
 created | date                  |           |          |
 note    | character varying(20) |           |          |

All three overrides are applied. An interesting feature is that the replicat logs an OGG-03056 warning about the target column being smaller than the source column:

2026-09-19T16:29:07.419+0000  WARNING OGG-03056  Oracle GoldenGate Delivery for PostgreSQL, REPTM.prm:  Source table AUTOSCH.T1 column CREATED data size exceeds the maximum target table autosch.t1 column created size. Automatic truncation is enabled for all tables/columns without further warnings.

As expected, mapping DATE to a PostgreSQL date loses the time part, and it can be seen in the target table:

 id | name  | amount |  created   | note
----+-------+--------+------------+-------
  1 | row 1 |  10.25 | 2026-09-19 | added
  2 | row 2 |   20.5 | 2026-09-19 | added

An Oracle DATE carries a time, so the default timestamp(0) mapping is the right one.

AUTOSCHEMAOPTIONS at the MAP level

The same option can be set on a MAP statement, between parentheses. It then only applies to the tables of that MAP statement instead of the whole replicat:

MAP AUTOSCH.*, TARGET AUTOSCH.*, AUTOSCHEMAOPTIONS (TYPEMAP 'number' = 'bigint');
2026-09-19T16:29:33.344+0000  INFO    OGG-06506  Oracle GoldenGate Delivery for PostgreSQL, REPTM.prm:  Wildcard MAP resolved (entry AUTOSCH.*): MAP "AUTOSCH"."T1", TARGET AUTOSCH."T1", AUTOSCHEMAOPTIONS (TYPEMAP 'number' = 'bigint').
2026-09-19T16:29:33.344+0000  INFO    OGG-30632  Oracle GoldenGate Delivery for PostgreSQL, REPTM.prm:  AutoSchemaOptions TypeMap is used, TypeNameMap: number -> bigint.

The table has a bigint key again. In this case, the other number columns outside of the MAP statement would keep the default varchar(50) mapping.

Remapping a single column

To change a single column, write the column name on the left side of the equal sign, instead of specifying the source type. The two forms work, first with the full name of the column:

AUTOSCHEMAOPTIONS TYPEMAP AUTOSCH.T1.NAME = 'text'

And at the MAP level, with the column name alone:

MAP AUTOSCH.T1, TARGET AUTOSCH.T1, AUTOSCHEMAOPTIONS (TYPEMAP NAME = 'text');

Both give the same table, with only name changed into text:

 Column  |              Type              | Collation | Nullable | Default
---------+--------------------------------+-----------+----------+---------
 id      | character varying(50)          |           | not null |
 name    | text                           |           |          |
 amount  | numeric(10,2)                  |           |          |
 created | timestamp(0) without time zone |           |          |
 note    | character varying(20)          |           |          |

To remap several columns on the same MAP, separate the mappings with commas inside the parentheses, and write TYPEMAP only once:

MAP AUTOSCH.T1, TARGET AUTOSCH.T1, AUTOSCHEMAOPTIONS (TYPEMAP NAME = 'text', NOTE = 'text');
 Column  |              Type              | Collation | Nullable | Default
---------+--------------------------------+-----------+----------+---------
 id      | character varying(50)          |           | not null |
 name    | text                           |           |          |
 amount  | numeric(10,2)                  |           |          |
 created | timestamp(0) without time zone |           |          |
 note    | text                           |           |          |

A type and a column can be mixed in the same list. For example, AUTOSCHEMAOPTIONS (TYPEMAP 'number' = 'bigint', NAME = 'text') gives a bigint key and a text name.

To summarize
  • The default mapping files are located in $OGG_HOME/lib/utl/autoschema/MappingJSON. Check them before letting a replicat create tables. A NUMBER without precision becomes a varchar on PostgreSQL, for instance.
  • The override syntax is AUTOSCHEMAOPTIONS TYPEMAP 'source_type' = 'target_type'. Use an equal sign, both types in single quotes, and one mapping per line.
  • A source type is matched as written. An override on 'number' does not change a NUMBER(10,2) column, which needs a separate 'number(10,2)' entry.
  • On a MAP, put the option between parentheses to limit it to those tables. For one column, use AUTOSCH.T1.NAME = 'text' globally or NAME = 'text' on the MAP.
  • Pay attention to OGG-03056 warnings: a target type smaller than the source, like DATE to date, truncates the data without stopping the initial load.

L’article GoldenGate 26ai AutoSchema TYPEMAP: overriding the default datatype mapping est apparu en premier sur dbi Blog.

Archiving 5 TB from Oracle into SQL Server: views, indexes and compression (part 2)

Yann Neuhaus - Wed, 2026-10-07 01:20

At the end of part 1 the data was across, at 42,170 rows per second into a database that could not answer a single question. Every nonclustered index disabled, the largest tables loaded as heaps with no primary key, every foreign key switched off, and 4,784 views that mostly did not compile.

This part is about putting it back: translating the views, rebuilding the keys and indexes under a Standard Edition licence that makes every build offline, and compressing 3.2 TB of heaps that turned out to be mostly empty space.

The logbook resumes at day 5.

Day 5. Four thousand views to convert

4,658 of the 4,784 views live in a single schema, and nobody needed all of them. We went through the list with the client first and kept the ones that actually matter to the business, which brought the scope down to about 710. SSMA converted around 600 of those on its own.

SSMA fails mostly on CONNECT BY, START WITH and SYS_CONNECT_BY_PATH. For some easy cases, DECODE to CASE and NVL handles it well, for the record. For more complex use case, we needed to find an automatic solution process. So first, we discussed with the client to know which view were critical and needed to be migrated. Then, with this list, we leveraged the power of AI to help us converting the remaining views based on the following workflow:

The workflow

SSMA converts what it can. The failures get extracted from dba_views into files. Then, and this is the step that changes the economics, they get classified by pattern in the Oracle source rather than one at a time by error message.

Translation itself was done in batches of one family at a time, outside production, on extracted files, with an AI assistant and a context file describing the type mapping decisions, the column types of the referenced tables and the target version. Then human review, tests against Oracle and finally, deployment.

The catalogue of families is the real deliverable. Eight of them closed the great majority of the cases:

FamilyVerdict+ concatenation on numerics (col + '_' + col)CONCATUntyped NULL in a UNION, cast to numeric(38,10) against a datetime2 siblingretype the castCall to a package function that was never migratedinline it if possiblessma_oracle.* helper functionsinstall the extension packXML extraction (.extract().getStringVal())rewrite part of the queryTO_TIMESTAMP_TZrewrite part of the queryDate arithmetic landing in floatredefine type mappingCONNECT BY used as a generatormaterialise the level connectors

And a word on the AI, since that is the part people ask about. No data ever left the environment: only the view definitions were processed, and every test against the live database was run by hand so we kept control of the process. We could have added MCP connectors and let a local model run the tests itself, but with the number of days allocated to the project and the small number of views left to fix, doing it manually was simpler. Around 85 percent of the views wanted by the client had already come through SSMA.

Day 6. Putting the indexes back and making the database smaller Recovering what I had thrown away

The primary keys I dropped to load into heaps had left no trace on the SQL Server side. Nothing in sys.indexes, no disabled definition, nothing.

What Standard Edition costs

Index builds are offline and single-threaded. The table is locked for the duration, metadata reads queue behind it, but we managed to run 4 builds in parallel. SORT_IN_TEMPDB = OFF for the clustered builds. The sort covers the entire table, and 36 GB of tempdb was never going to hold a 900 GB sort.

Meanwhile percent_complete sits at 0 for the whole build, with the serene confidence of something that has never been asked to account for itself. It works for BACKUP DATABASE. It does not work here. Real progress lives in sys.dm_exec_query_profiles, operator by operator :

SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
SELECT physical_operator_name, row_count, estimate_row_count,
       CAST(row_count*100.0/NULLIF(estimate_row_count,0) AS decimal(5,1)) AS pct
FROM sys.dm_exec_query_profiles WITH (NOLOCK)
WHERE session_id = <spid of the build> 
ORDER BY node_id;

Those are not three parallel counters. They are three sequential phases of one pipeline, and only one of them moves at a time. The scan reads the source table, and while it runs its percentage is the only honest progress figure available. The sort is a blocking operator: it cannot emit a single row until it has consumed all 31.9 million, so it stays at zero through the whole scan and then through the whole sort. That is the blind spot. Once the scan reaches 100 percent you get a long stretch where all three counters report nothing while the server works flat out, and if the sort spills to disk that stretch can outlast the other two phases combined. Index Insert starts moving only when the sort begins handing rows over, and it is the first number that means the end is actually in sight.

Why compress, and what it returned

On a read-only database, PAGE compression has no recurring write cost. You keep the read benefit and pay nothing back. The argument is rarely this clean.

Compression trades CPU for I/O, and on an archive that trade is one-sided. These pages are read rarely, and when they are, nothing heavy is competing for the processor, so the decompression cost lands on a machine with nothing better to do. What you get back is density: more rows per page, which means fewer reads to find the one row somebody asked for and more of the table sitting in memory at once.

The data was unusually well suited to it, for a reason worth spelling out. These tables carry roughly 200 numeric columns, mostly null, plus five nvarchar(200) aggregation labels that repeat endlessly. Here is the part that surprises people: a fixed-length numeric column that is NULL still occupies its full width in SQL Server. “Mostly null” saves nothing before compression. It is precisely why the heap was that large, and precisely what the page dictionary destroys.

The allocated space per row tells the story:

TableRowsGB (heap)bytes allocated per rowTABLE1251,777,518960.5~4,100TABLE2229,881,717876.9~4,100TABLE3343,997,550874.8~2,700TABLE4447,972,849227.9~550TABLE5519,727,931120.2~250TABLE6505,819,544117.0~250

Three tables hold 2.7 TB of the 3.2 TB total. At around 4 KB of allocated space per row against an 8 KB page, the top two fit around two rows per page and waste most of what is left.

Then the first build, on 505,819,544 rows with a five-column primary key:

ALTER TABLE <TABLE6>
ADD CONSTRAINT PK_TABLE6 PRIMARY KEY CLUSTERED
    (<COLUMNS_NAME>)
WITH (DATA_COMPRESSION = PAGE, SORT_IN_TEMPDB = OFF);

117 GB to 30.7 GB. A ratio of 3.81.

Finally, as you can see above, more than 2.5 T have been reclaimed (datafiles were full with the heaps). Compression has made huge benefits and that means less data on disks, which means fewer pages to take in the buffer (less IO).

The script that puts everything back

Across every schema that is two cursors, one rebuilding the disabled indexes with compression and one re-enabling the foreign keys. It is driven by the same #Schemas table as the disable script in part 1:

SET NOCOUNT ON;
DECLARE @Execute bit = 0;   -- 0 = preview ; 1 = execute
  
DECLARE @cmd nvarchar(max);
DECLARE @n   int = 0;
  
DECLARE reb_cur CURSOR LOCAL FAST_FORWARD FOR
    SELECT N'ALTER INDEX ' + QUOTENAME(i.name)
         + N' ON ' + QUOTENAME(s.name) + N'.' + QUOTENAME(t.name)
         + N' REBUILD WITH (DATA_COMPRESSION = PAGE);'
    FROM   sys.indexes  i
    JOIN   sys.tables   t ON i.object_id = t.object_id
    JOIN   sys.schemas  s ON t.schema_id = s.schema_id
    WHERE  s.name IN (SELECT schema_name FROM #Schemas)
      AND  i.is_disabled = 1          
    ORDER BY s.name, t.name, i.name;
  
OPEN reb_cur;
FETCH NEXT FROM reb_cur INTO @cmd;
WHILE @@FETCH_STATUS = 0
BEGIN
    SET @n += 1;
    PRINT @cmd;
    IF @Execute = 1
    BEGIN
        BEGIN TRY EXEC sys.sp_executesql @cmd; END TRY
        BEGIN CATCH PRINT N'   -- FAILED: ' + ERROR_MESSAGE(); END CATCH
    END
    FETCH NEXT FROM reb_cur INTO @cmd;
END
CLOSE reb_cur;
DEALLOCATE reb_cur;
  
PRINT N'---';
PRINT CONVERT(nvarchar(10), @n) + N' index '
    + CASE WHEN @Execute = 1 THEN N'rebuilt.' ELSE N'to be rebuilt (preview).' END;
GO
  
  
SET NOCOUNT ON;
DECLARE @Execute bit = 1;   -- 0 = preview ; 1 = executer
  
DECLARE @cmd nvarchar(max);
DECLARE @n   int = 0;
  
DECLARE fkc_cur CURSOR LOCAL FAST_FORWARD FOR
    SELECT N'ALTER TABLE ' + QUOTENAME(s.name) + N'.' + QUOTENAME(t.name)
         + N' WITH NOCHECK CHECK CONSTRAINT ' + QUOTENAME(fk.name) + N';'
    FROM   sys.foreign_keys fk
    JOIN   sys.tables  t ON fk.parent_object_id = t.object_id
    JOIN   sys.schemas s ON t.schema_id = s.schema_id
    WHERE  s.name IN (SELECT schema_name FROM #Schemas);
  
OPEN fkc_cur;
FETCH NEXT FROM fkc_cur INTO @cmd;
WHILE @@FETCH_STATUS = 0
BEGIN
    SET @n += 1;
    PRINT @cmd;
    IF @Execute = 1
    BEGIN
        BEGIN TRY EXEC sys.sp_executesql @cmd; END TRY
        BEGIN CATCH PRINT N'   -- FAILED : ' + ERROR_MESSAGE(); END CATCH
    END
    FETCH NEXT FROM fkc_cur INTO @cmd;
END
CLOSE fkc_cur;
DEALLOCATE fkc_cur;
  
PRINT N'---';
PRINT CONVERT(nvarchar(10), @n) + N' FK '
    + CASE WHEN @Execute = 1 THEN N'activated and validated.' ELSE N'to be activated (preview).' END;
GO

The foreign keys, which turned out to be free

All 6,072 foreign keys were already back, active and marked not trusted, because the loading script re-enabled them with NOCHECK. Revalidating them with WITH CHECK would mean full joins against the largest tables in the database.

On a read-only archive whose source integrity was enforced by Oracle for fifteen years, that buys one thing: the optimizer regains the right to eliminate redundant joins. Worth having on a schema of deeply nested views, not worth days of scanning here. They stayed not trusted.

Day 7. Testing 600 views

The principle is simple: run every view, record what happened, and never lose the record. Each one gets a SELECT TOP (100) * inside a TRY/CATCH, so a view that fails is logged rather than fatal, and the outcome lands in a table with its duration in milliseconds. The cursor only picks up views that are not already recorded, which means an interrupted run resumes where it stopped instead of starting over, and the duration column is what you sort on once it is done:

SET NOCOUNT ON;
 
IF OBJECT_ID('dbo.ViewTestResults') IS NULL
    CREATE TABLE dbo.ViewTestResults (
        schema_name  sysname,
        view_name    sysname,
        statut       varchar(20),
        duree_ms     int,
        message      nvarchar(2048),
        tested_at    datetime2 DEFAULT SYSDATETIME(),
        CONSTRAINT PK_ViewTestResults PRIMARY KEY (schema_name, view_name)
    );
 
DECLARE @sch sysname, @vue sysname, @sql nvarchar(max), @t0 datetime2, @ms int, @i int = 0, @n int;
 
SELECT @n = COUNT(*)
FROM   sys.views v JOIN sys.schemas s ON v.schema_id = s.schema_id
WHERE  s.name = 'SCDAT'
  AND  NOT EXISTS (SELECT 1 FROM dbo.ViewTestResults r
                   WHERE r.schema_name = s.name AND r.view_name = v.name);
 
RAISERROR('=== %d view remaining ===', 0, 1, @n) WITH NOWAIT;
 
DECLARE c CURSOR FAST_FORWARD FOR
  SELECT s.name, v.name
  FROM   sys.views v JOIN sys.schemas s ON v.schema_id = s.schema_id
  WHERE  s.name = 'SCDAT'
    AND  NOT EXISTS (SELECT 1 FROM dbo.ViewTestResults r
                     WHERE r.schema_name = s.name AND r.view_name = v.name)
  ORDER  BY v.name;
 
OPEN c;
FETCH NEXT FROM c INTO @sch, @vue;
WHILE @@FETCH_STATUS = 0
BEGIN
    SET @i += 1;
    RAISERROR('[%d/%d] %s.%s', 0, 1, @i, @n, @sch, @vue) WITH NOWAIT;
 
    SET @t0 = SYSDATETIME();
    BEGIN TRY
        SET @sql = N'SELECT TOP (100) * FROM ' + QUOTENAME(@sch) + N'.' + QUOTENAME(@vue) + N';';
        EXEC sp_executesql @sql;
        SET @ms = DATEDIFF(MILLISECOND, @t0, SYSDATETIME());
        INSERT dbo.ViewTestResults (schema_name, view_name, statut, duree_ms, message)
        VALUES (@sch, @vue, 'OK', @ms, NULL);
    END TRY
    BEGIN CATCH
        SET @ms = DATEDIFF(MILLISECOND, @t0, SYSDATETIME());
        INSERT dbo.ViewTestResults (schema_name, view_name, statut, duree_ms, message)
        VALUES (@sch, @vue, 'FAILED', @ms, ERROR_MESSAGE());
        RAISERROR('    -> FAILED: %s', 0, 1, @vue) WITH NOWAIT;
    END CATCH
 
    FETCH NEXT FROM c INTO @sch, @vue;
END
CLOSE c; DEALLOCATE c;
 
RAISERROR('=== Done ===', 0, 1) WITH NOWAIT;
 
-- summary
SELECT statut, COUNT(*) AS nb, SUM(duree_ms) AS duree_totale_ms
FROM dbo.ViewTestResults GROUP BY statut;
 
-- failed
SELECT schema_name, view_name, duree_ms, message
FROM dbo.ViewTestResults WHERE statut='FAILED' ORDER BY view_name;
 
-- all
SELECT schema_name, view_name, statut, duree_ms
FROM dbo.ViewTestResults ORDER BY duree_ms DESC;

The result looked excellent: 604 views OK, zero errors, against real data. Six skipped because they are not implemented to be run without an explicit filter added. SELECT TOP 100 * without a filter is not a representative test but still confirms that definition and joins are working fine. So: test with the filter the users will actually write. A view that only misbehaves when queried in a way nobody queries it is not a broken view, but you cannot know that from a green tick.

Day 8. Closing the hatch

Database set to read-only, final backup taken and user’s specific queries tested with success, the story now comes to a happy end.

What I would tell the next person

An archive migration is not a cheap application migration. It is a different decision set, and read-only is the input that drives most of it. 25,000 triggers are pointless, PAGE compression free, and untrusted foreign keys perfectly acceptable. It even tells you how to index: broadly and in advance, because nobody can tell you which row an auditor will want in three years. Before anything else, ask what the target will be used for. The answer rewrites the plan before the first byte moves.

Measure, and distrust anything that presents itself as progress. The SSMA counter refreshes per batch and sits still for minutes at a time. percent_complete stays at zero for an entire index build. A green tick on SELECT TOP 100 confirms that the joins resolve and nothing beyond that. In every one of those cases the real number was sitting in a DMV.

On views, the conversion rate is the least useful number in the report. What made four thousand views a finite piece of work was not the translation but the two steps around it: agreeing the real scope with the client first, then classifying what remained by pattern in the Oracle source rather than one error message at a time. An AI assistant translates a family of twenty views very well. It validates nothing, and the test against the source stays yours.

Compression on an archive is the rare decision with nothing to weigh against it. 117 GB down to 30.7 GB on the first table, more than 2.5 TB reclaimed across the set, and not a single write to pay it back with. If your heap is mostly null numeric columns, it is bigger than you think, and it will compress better than you expect.

L’article Archiving 5 TB from Oracle into SQL Server: views, indexes and compression (part 2) est apparu en premier sur dbi Blog.

Is your content ready for AI?

Yann Neuhaus - Tue, 2026-10-06 03:48

Imagine an employee asking the company’s AI assistant, ‘What is the notice period for supplier XYZ?’

The answer comes back immediately: a clear, precise quote from the contract. It sounds right, so nobody checks it. However, the quoted contract is an expired draft. The signed amendment that changed the notice period is stored somewhere else entirely.

This is a hypothetical scenario, but I believe similar situations are already occurring. It illustrates what I think is the most underestimated aspect of AI in ECM: a poor search result is obvious, but a poor AI response can be convincing.

Is your content ready for AI? AI turns content quality into a risk

Before AI, poor content quality was mostly just an annoyance. Searches took longer and users had to scroll through several versions of the same document, but they usually noticed when something looked wrong. Humans were still the filter.

With AI, however, this filter becomes less effective. The assistant reads the content, selects what it deems relevant and presents a coherent summary. Users may never see the other versions, outdated drafts or documents that contradict the answer.

In my post on information debt, I wrote about the shortcuts organizations take with their content and the price they pay later. AI is often the moment when that debt becomes payable. All those duplicates, forgotten drafts, and incomplete properties are now the raw material that your AI works with.

AI does not fix messy content. It simply reads it faster, summaries it with confidence and spreads the mess further.

What AI struggles with in typical repositories

When I look at how content is usually managed, I see five recurring problems.

Duplicates and near-duplicates. Which version is the truth? A person might know from experience. An AI assistant has no instinct for this unless the system gives it clear signals.

Outdated content. A procedure valid in 2019 sits next to the current one. Both are well written, and both look equally credible.

Missing or inconsistent metadata.. AI depends on context: the type of document, its status, its owner, the period it applies to. When the “Status” property is empty, or means different things in different departments, that context disappears. I covered part of this in The metadata trap. Metadata can fail by being too complex, and it can also fail by being unreliable.

Missing relationships. A contract, its amendments and its invoices tell one story. If they are stored as separate islands, the AI sees fragments. This is exactly the point I made in The forgotten power of relationships in ECM.

Unclear authority. Is this document a draft or approved? An internal opinion or an official policy? Without lifecycle information, an AI can’t tell the difference, and often neither can the user.

The good news: ECM discipline is AI readiness

Here’s what I find reassuring: All the features that make content manageable in an ECM also make it trustworthy for AI:

  • Lifecycle and status tell the AI what is current and approved.
  • Metadata tells the AI what a document is and what it applies to.
  • Relationships show which documents belong together.
  • Permissions determine what each user’s AI is allowed to see.

The practices we have promoted for years, such as clear metadata, defined workflows and linked objects, were not just housekeeping measures. They provide the context that transforms a collection of documents into a knowledge base that an AI can utilize responsibly.

I made a similar point in ‘ECM + AI: achieving optimal synergy’: AI does not replace a well-structured ECM; it depends on it.

The permissions blind spot

There is one more risk that deserves its own section because it takes many organizations by surprise.

For years, content that was shared too widely was protected by obscurity. A document might be accessible to more people than intended, but it would remain hidden because no one was looking for it. AI changes that. An assistant that can search and summarize everything a user has access to will surface content in seconds that nobody knew was reachable.

Therefore, before enabling AI features, check who can access your sensitive content. This includes broad groups, inherited rights, and shared links. It is much better to discover over-sharing during a controlled review than through an AI-generated response in front of the wrong person.

A practical way forward

Making all content ‘AI-ready’ can seem overwhelming, but it doesn’t have to be. Here is the approach I would recommend:

Start with one use case, rather than the entire repository

Choose an area where AI could clearly add value, such as contracts, quality procedures or customer documentation. Prepare that content first.

Define what ‘authoritative’ means

Decide which statuses, document types and validity dates identify trusted content. If your team cannot answer the question ‘What counts as the official version?’, neither can the AI.

Remove the obvious noise

Archive or delete expired, duplicate and abandoned documents. This is where the lifecycle thinking from my earlier posts comes in useful.

Fix the important metadata

Don’t try to perfect every property. Focus on the ones that users and AI rely on, such as type, status, owner, validity and relationships with related objects.

Review permissions before activation

NOT AFTER!

Keep a human in the loop at the beginning

Require AI answers to show their sources so that users can verify them. During the pilot, review the results and collect feedback on incorrect or unexpected answers.

Measure and provide feedback

When an answer is incorrect, establish the reason why. This is usually due to a content problem, such as a missing status or a duplicate. Fixing these issues improves every future answer.

AI can help with the cleanup too

This doesn’t mean that you have to have perfect content before you start. AI can help you achieve this. As I explained in M-Files AI: From Intelligent Metadata to AI Agents, it can suggest properties, classify documents, and identify similar content. This makes it a useful tool for identifying duplicates and filling in missing metadata.

A sensible approach is to work in cycles rather than gates: improve the content a little, use AI, see where it fails, then improve it again. However, it is important to have a human validate what the AI suggests, especially for properties that drive decisions.

A simple readiness check

Ask yourself the following questions about the content that AI would use:

  • Can you tell whether any document is current and approved?
  • Are the metadata for your key document types consistent and complete?
  • Do you have an idea of how many duplicates exist of your most important content?
  • Are related documents linked to each other?
  • Do you know who can access your sensitive content, including via shared links?
  • Can every AI answer be traced back to a source that users can verify?
  • Is the quality of this content owned by someone?

If you answered “no” or “I don’t know” to several of these questions, that’s not a reason to stop. It’s a reason to start preparing!

Conclusion

AI readiness isn’t a separate project. Rather, it is what good ECM practice looks like when you finally ask your content to do more than just sit in a repository. It also provides a business reason to invest in quality work that was often postponed in the past.

The organizations that benefit most from AI won’t necessarily be those with the most advanced model. They will be the ones whose content can be trusted.

Have you tested AI features on your own content yet? I’d be interested to hear what surprised you.

L’article Is your content ready for AI? est apparu en premier sur dbi Blog.

Capture VS Code requests to Ollama using MITM reverse proxy

Flavio Casetta - Tue, 2026-10-06 01:54
Have you ever wondered what exactly your VS Code AI extensions are whispering to your local models? By setting up a Man-in-the-Middle (MITM) proxy, you can peek behind the curtain and capture every prompt generated by your editor.
 


Using mitmdump, we can create a reverse proxy in front of a local Ollama instance (which runs on port 11434) and record the incoming traffic:
Bash

mitmdump --mode reverse:http://localhost:11434 -p 11435 -w ~/Documents/ollama_requests.log

By pointing your VS Code extension to localhost:11435 instead of the default port, all HTTP traffic is silently intercepted and saved to your log file.

Next, we convert this binary log into a human-readable format, cranking up the detail level to capture the full request payloads:
Bash

mitmdump -r ollama_requests.log --set flow_detail=3 > today.txt

Now you have a raw text file containing every prompt and system instruction sent by VS Code. The final step is to feed today.txt back into an LLM. You can ask it to summarize your daily coding activity, analyze the hidden system prompts injected by your extensions, or extract recurring development patterns. It is a fantastic way to reverse-engineer how AI assistants structure their context.
Categories: DBA Blogs

Pages

Subscribe to Oracle FAQ aggregator