Saturday, December 27, 2025

Handling DEFAULTs in Oracle Database: What’s New in 26ai

Oracle has supported column default values for a long time, but the semantics around NULL handling, especially across INSERT and UPDATE operations, have evolved significantly over releases. In this post, we’ll walk through:
 
  • How traditional DEFAULT behaves
  • Why DEFAULT ON NULL was introduced in 12c
  • The long-standing gap with UPDATE statements and PL/SQL
  • How Oracle 26ai finally closes that gap with defaults for INSERT, UPDATE, or both
 
All examples below use a simple numeric table to keep the behaviour easy to observe.
 
Let’s start by creating a table with two nullable numeric columns.

 
demo@ORA26AI> create table if not exists t( x number, y number );
 
Table T created.
 
By default, both columns allow NULL values.
 
demo@ORA26AI> desc t
 
Name    Null?    Type
_______ ________ _________
X                NUMBER
Y                NUMBER
 
Now, define a default value for column Y.
 
demo@ORA26AI> alter table t
  2* modify y default 100;
 
Table T altered.
 
If column Y is omitted during an INSERT, Oracle treats it as an implicit NULL and applies the default.
 
demo@ORA26AI> insert into t(x) values (100);
 
1 row inserted.
 
demo@ORA26AI> select * from t;
 
     X      Y
______ ______
   100    100
 
However, if NULL is explicitly provided, Oracle respects the NULL and does not apply the default.
 
demo@ORA26AI> insert into t(x,y) values(101,null);
 
1 row inserted.
 
demo@ORA26AI> select * from t;
 
     X      Y
______ ______
   100    100
   101
 
This behaviour is by design and often leads to extra cleanup logic in applications. Very often that is not the case, if we associate a Default value to the column and have an explicit / implicit value of null to it, then we want the default value to be associated in place of nulls
 
One option we have is to explicit fix those null values with an update statement.

 
demo@ORA26AI> update t set y = 100
  2* where y is null ;
 
1 row updated.
 
demo@ORA26AI> select * from t;
 
     X      Y
______ ______
   100    100
   101    100
 
But that is hardly a robust way of doing the things. Back in Oracle 12c we extended this DEFAULT clause to DEFAULT ON NULL , which treats explicit NULLs on INSERT the same as implicit NULLs.
 
demo@ORA26AI> alter table t modify y default on null 200;
 
Table T altered.
 
Now, even if NULL is explicitly provided, the default value is used.
 
demo@ORA26AI> insert into t(x,y) values(102,null);
 
1 row inserted.
 
demo@ORA26AI> select * from t;
 
     X      Y
______ ______
   100    100
   101    100
   102    200
 
So far, so good—but the story doesn’t end here. With DEFAULT ON NULL, things start to break down during UPDATE operations.
 
demo@ORA26AI> update t
  2  set y = null
  3* where x = 101;
 
Error starting at line : 1 in command -
update t
set y = null
where x = 101
Error at Command Line : 2 Column : 5
Error report -
SQL Error: ORA-01407: cannot update ("DEMO"."T"."Y") to NULL
 
https://docs.oracle.com/error-help/db/ora-01407/01407. 00000 -  "cannot update (%s) to NULL"
*Cause:    An attempt was made to update a table column
           "USER"."TABLE"."COLUMN" with a NULL value.
 
 
The issue becomes more visible in PL/SQL when using %ROWTYPE.
 
demo@ORA26AI> declare
  2     l_row t%rowtype;
  3  begin
  4     l_row.x := 101;
  5
  6     update t
  7     set row = l_row
  8     where x = 101;
  9  end;
 10* /
declare
*
ERROR at line 1:
ORA-01407: cannot update ("DEMO"."T"."Y") to NULL
ORA-06512: at line 6
 
https://docs.oracle.com/error-help/db/ora-01407/
 
 
Even though Y has DEFAULT ON NULL, declarative updates still attempt to assign NULL explicitly. all these gaps are addressed in 26ai and now we can nominate a Default value for insert or update statements or both.
 
demo@ORA26AI> alter table t
  2  modify y default on null
  3* for insert and update 150;
 
Table T altered.
 
Now the default on update applies to update command as well. If we retry the above update command that fails, will become success now, and allow the default values to flows in the presence of null values.
 
demo@ORA26AI> update t
  2  set y = null
  3* where x = 101;
 
1 row updated.
 
demo@ORA26AI> select * from t;
 
     X      Y
______ ______
   100    100
   101    150
   102    200
 
 
The default value flows in automatically whenever NULL is encountered—regardless of whether it comes from an INSERT, an UPDATE, or PL/SQL row-based assignments.
 
Oracle’s evolution of default handling—from basic DEFAULT, to DEFAULT ON NULL, and now to INSERT/UPDATE–aware defaults in 26ai—marks a significant step toward cleaner, more declarative data modeling.
 
If you’ve ever struggled with ORA-01407 in otherwise valid designs, Oracle 26ai finally gives you the missing piece.

 
 
 

Wednesday, December 17, 2025

Federated Tables in ADB-S : Simplifying Cross-Database Data Access

In the past, we have discussed accessing table hyperlinks (also known as PAR URLs) using external tables. This post focuses on a new enhancement in the Autonomous Database – Shared Infrastructure (ADB-S) platform called Federated Tables.

 

Federated Tables automate the creation of table hyperlinks, map them to external tables, and provide read-only SQL access to remote data—without requiring manual PAR URL management.

 



 

In this example, the setup is done between two databases running in OCI:

  • Source (Producer): Oracle Autonomous Database 19c
  • Target (Consumer): Oracle Autonomous Database 26ai

 

Both databases reside within the same OCI compartment. 

Step 1: Grant Scope Registration Privilege on the Source Database 

First, grant the scope registration privilege to the schema that owns the data in the source database.

 admin@ATP19C> exec dbms_data_access_admin.grant_register('DEMO',scope=>'MY$COMPARTMENT');

 PL/SQL procedure successfully completed.

  

This grants the DEMO schema permission to share data at the compartment level.

 


Step 2: Register the Creation Scope for Shared Objects

 

Next, the data owner registers the creation scope for specific objects. This authorizes consumer databases in the same compartment to create table hyperlinks remotely.

demo@ATP19C> begin
  2     dbms_data_access_scope.register_creation_scope(
  3         schema_name => user
  4         , schema_object_list => '["DEPT","BIG_TABLE"]'
  5         , scope=>'MY$COMPARTMENT' );
  6  end;
  7* /

PL/SQL procedure successfully completed.

demo@ATP19C>

 This step explicitly defines which objects can be shared. To make this sharing to happen at schema level (or) for all the (existing/upcoming) objects in the schema we can pass schema_object_list parameter as NULL

 Step 3: Grant Read Access on the Target (Consumer) Database 


On the target database, grant the required privileges to the consumer schema. 

admin@ADB26ai> grant execute on dbms_data_access to DEMO_USER; 

Grant succeeded. 


Then below grant will provide read access to the remote schema and object to the target schema DEMO_USER in the consumer database. 

Step 4: Create the Federated Table on the Consumer Database

admin@ADB26ai> begin
  2     DBMS_DATA_ACCESS_ADMIN.GRANT_READ(
  3         username => 'DEMO_USER'
  4         , remote_schema_name => 'DEMO'
  5         , remote_schema_object_name => 'DEPT' );
  6  end;
  7* / 

PL/SQL procedure successfully completed.
 
Finally, create the federated external table using the CREATE_FEDERATED_TABLE procedure. 

demo-user@ADB26ai> begin
  2     dbms_data_access.create_federated_table(
  3         table_name => 'MY_DEPT_DEMO'
  4         , remote_schema_name =>'DEMO'
  5         , remote_schema_object_name =>'DEPT'
  6         , db_ocids => '[{"region":"IAD","db_ocid":"OCID1.AUTONOMOUSDATABASE.OC1.IAD.ANUWCLJS3CXJV6AALSKNDVN2XUPBL46LP53FXJQYGOOU5K77X7BTIX2GIFKQ"}]' );
  7  end;
  8* / 

PL/SQL procedure successfully completed. 

The region and db_ocid in the above method, refers to the source (producer) database where the data resides. 

Step 5: Query the Federated Table
 
Once created, the federated table can be queried like a regular table.
 
demo-user@ADB26ai> select * from MY_DEPT_DEMO ;
 
   DEPTNO DNAME         LOC
_________ _____________ ___________
       10 ACCOUNTING    NEW YORK
       20 RESEARCH      DALLAS
       30 SALES         CHICAGO
       40 OPERATIONS    BOSTON

 

Inspecting the table metadata shows that the federated table is implemented as an external table backed by a table hyperlink.

 

demo-user@ADB26ai> select dbms_metadata.get_ddl('TABLE','MY_DEPT_DEMO') ; 
DBMS_METADATA.GET_DDL('TABLE','MY_DEPT_DEMO')
________________________________________________________________ 
  CREATE TABLE "DEMO_USER"."MY_DEPT_DEMO"
   (    "DEPTNO" NUMBER,
        "DNAME" VARCHAR2(14) COLLATE "USING_NLS_COMP",
        "LOC" VARCHAR2(13) COLLATE "USING_NLS_COMP"
   )  DEFAULT COLLATION "USING_NLS_COMP"
   ORGANIZATION EXTERNAL
    ( TYPE ORACLE_LOADER
      DEFAULT DIRECTORY "DATA_PUMP_DIR"
      ACCESS PARAMETERS
      ( RECORDS ESCAPE CHARACTERSET AL32UTF8 ALLOW MISSING FILES DELIMITED BY NEWLINE NOLOGFILE NOBADFILE NODISCARDFILE READSIZE=10000000
    FIELDS CSV WITHOUT EMBEDDED TERMINATED BY ',' OPTIONALLY ENCLOSED BY '"' MISSING FIELD VALUES ARE NULL   NOTRIM )
      LOCATION
       ( 'https://dataaccess.adb.us-ashburn-1.oraclecloudapps.com/adb/p/8ldGfvy2qeSeJzDaZ_rtf2aTjutCJAURrI5yKLrk8z743dvC64fYj4IwkUzp54cw9dsHPKpaPjc/data*')
    )
   REJECT LIMIT 0
  PARALLEL

 

The LOCATION clause points to a secure HTTPS endpoint, which is the generated table hyperlink.

 

The same table hyperlink can be accessed using any REST client (such as curl, Postman, or Insomnia)

 

$ curl --location 'https://dataaccess.adb.us-ashburn-1.oraclecloudapps.com/adb/p/8ldGfvy2qeSeJzDaZ_rtf2aTjutCJAURrI5yKLrk8z743dvC64fYj4IwkUzp54cw9dsHPKpaPjc/data'

{
  "items": [
    {
      "DEPTNO": 10,
      "DNAME": "ACCOUNTING",
      "LOC": "NEW YORK"
    },
    {
      "DEPTNO": 20,
      "DNAME": "RESEARCH",
      "LOC": "DALLAS"
    },
    {
      "DEPTNO": 30,
      "DNAME": "SALES",
      "LOC": "CHICAGO"
    },
    {
      "DEPTNO": 40,
      "DNAME": "OPERATIONS",
      "LOC": "BOSTON"
    }
  ],
  "hasMore": false,
  "limit": 1000,
  "offset": 0,
  "count": 4,
  "links": [
    {
      "rel": "self",
      "href": "https://dataaccess.adb.us-ashburn-1.oraclecloudapps.com/adb/p/8ldGfvy2qeSeJzDaZ_rtf2aTjutCJAURrI5yKLrk8z743dvC64fYj4IwkUzp54cw9dsHPKpaPjc/data"
    }
  ]
}
 
 

Federated Tables in ADB-S provide a secure and streamlined way to:

  • Share data across Autonomous Databases
  • Eliminate manual PAR URL handling
  • Access remote data using standard SQL
  • Consume the same data via REST endpoints 

This feature significantly simplifies cross-database data sharing while maintaining strong access controls. 

 

 


Monday, December 8, 2025

Value LOBs in Oracle 26ai: The End of Chunked LOB Overhead

 
LOBs in Oracle have historically been stored as chunks, with an associated LOB index segment to allow navigation through the LOB structure. Because of this architecture, when a query returns a LOB column, the client historically receives a pointer to the LOB, and only when the client actually requests the LOB value does the database send the data piece-by-piece across the network.
 
This design still makes sense for very large LOBs, but today many modern JSON or text payloads fall into an awkward middle area: 
  • too large for VARCHAR datatypes
  • too small to justify chunking and LOB indexing overhead 
Let’s walk through a simple demonstration.

 
demo@ADB26AI> create table base_table
  2  as
  3  with base_data as (
  4     select rownum id
  5             , 'Text' || rownum as data
  6     from all_objects
  7     where rownum <= 80 )
  8  , one_clob as (
  9             select json_arrayagg(json{*} returning clob) as jdoc
 10             from base_data )
 11  select rownum as pk
 12      , one_clob.jdoc
 13  from one_clob , all_objects
 14  where rownum <= 100;
 
Table created.
 
And we will create a table with clob data type in a standard way and populate it with some source data
 
demo@ADB26AI> create table t_reference_clob( x number, y clob ) ;
 
Table created.
 
demo@ADB26AI>
demo@ADB26AI> insert into t_reference_clob select * from base_table;
 
100 rows created.
 
demo@ADB26AI> commit;
 
Commit complete.
 
Now query the table from a client session far from the database (in my case, Autonomous Database in a remote region):
 
demo@ADB26AI> select * from t_reference_clob;
 
100 rows selected.
 
Elapsed: 00:11:04.73
 
Statistics
----------------------------------------------------------
       1070  recursive calls
         17  db block gets
       5713  consistent gets
       5215  physical reads
       2504  redo size
     943880  bytes sent via SQL*Net to client
     631599  bytes received via SQL*Net from client
       2602  SQL*Net roundtrips to/from client
         59  sorts (memory)
          0  sorts (disk)
        100  rows processed
 
 
This happens because the client presumes the LOB might be arbitrarily large, so it retrieves each row’s LOB in chunks, issuing repeated network requests.
 
From 23ai onward, Oracle lets us tell the database that certain LOBs should be treated like values rather than chunked LOB objects.
 
demo@ADB26AI> create table t_value_clob( x number, y clob )
  2  lob(y) query as value;
 
Table created.
 
demo@ADB26AI> insert into t_value_clob select * from base_table;
 
100 rows created.
 
Now if I rerun the query, notice the improvements in the round trips.
 
demo@ADB26AI> select * from t_value_clob;
 
100 rows selected.
 
Elapsed: 00:00:03.12
 
Statistics
----------------------------------------------------------
         23  recursive calls
         16  db block gets
        137  consistent gets
          0  physical reads
       2796  redo size
     420380  bytes sent via SQL*Net to client
        404  bytes received via SQL*Net from client
          2  SQL*Net roundtrips to/from client
          1  sorts (memory)
          0  sorts (disk)
        100  rows processed
 
 
This is all good If the Lobs are small, what if there is a mix ? we can also decide on query by query basis on how would the lob data to be retrieved.  Here is our original table query with LOB_VALUE function.
 
demo@ADB26AI> select x,lob_value(y) from t_reference_clob;
 
100 rows selected.
 
Elapsed: 00:00:03.14
 
Statistics
----------------------------------------------------------
         11  recursive calls
          9  db block gets
         16  consistent gets
        112  physical reads
       1552  redo size
     420391  bytes sent via SQL*Net to client
        404  bytes received via SQL*Net from client
          2  SQL*Net roundtrips to/from client
          1  sorts (memory)
          0  sorts (disk)
        100  rows processed
 
If the data is JSON, then we can also provide the instruction to the database as part of the JSON_SERIALIZE function.
 
demo@ADB26AI> select x,json_serialize(y returning clob value) from t_reference_clob;
 
100 rows selected.
 
Elapsed: 00:00:03.12
 
Statistics
----------------------------------------------------------
         12  recursive calls
         11  db block gets
         16  consistent gets
        100  physical reads
       1700  redo size
     420414  bytes sent via SQL*Net to client
        404  bytes received via SQL*Net from client
          2  SQL*Net roundtrips to/from client
          1  sorts (memory)
          0  sorts (disk)
        100  rows processed
 
 
Why this matters today. 
  • For most modern JSON use cases:
  • ORDS APIs
  • APEX pages
  • Microservices
  • Autonomous clients
  • Cloud apps 
…the payloads are typically kilobytes, not gigabytes. The default LOB architecture is overkill and causes unnecessarily high network latency.
 
With the new approach, we can treat JSON CLOB as a value, not a chunked object, dramatically reducing network round trips and improving performance—without changing the data model.
 
If your applications plan to leverage Oracle’s modern data types such as JSON or Vector, Oracle Database 26ai will store these values as VALUE LOBs by default. This means Oracle uses the VALUE LOB format internally for optimized retrieval and processing.

 
demo-user@ADB26ai> create table t ( x json ,y vector );
 
Table T created.
 
demo-user@ADB26ai> select column_name, value_based
  2  from user_lobs
  3* where table_name ='T' ;
 
COLUMN_NAME    VALUE_BASED
______________ ______________
X              YES
Y              YES

Monday, December 1, 2025

Talk to Your Oracle Database Using Natural Language in VS Code with SQLcl MCP Server

 
stop writing SQL from scratch — let AI understand your schema and generate SQL for you.
 
Oracle SQLcl 25.2+ introduced one of the most important developer features in years: native MCP (Model Context Protocol) server support. With this, Oracle becomes the first major database platform to offer a standardized way for AI assistants like ChatGPT and Claude to securely understand your schema, generate SQL, and even run queries — all from plain English.
 
In this post, we’ll walk through:
 
  • What MCP is and why it matters
  •  How SQLcl can act as an MCP Server
  • Setting up VS Code to use natural language chat with your database
  • Real demos — from simple queries to advanced analytics
  • How AI can generate reports and dashboards from your Oracle data
 
Let’s get started.
 
What is MCP (Model Context Protocol)?
 
MCP is an open-source standard for connecting AI models to real systems.
 
Just like USB-C gives your devices a universal connector, MCP gives AI a universal connector to databases, filesystems, tools, and workflows
.
 


 
With MCP:
 
  • An AI assistant can inspect your database schema
  • Generate SQL queries
  • Execute them only with your permission
  • Produce dashboards, summaries, or reports
  • Plug into developer tools like VS Code
 
Oracle SQLcl now supports this protocol natively — no separate servers, plugins, or bridges needed.

 
SQLcl as an MCP Server
 
SQLcl 25.2 introduced the new command:

 
sql -mcp
 
Running this transforms SQLcl into a live MCP Server, allowing AI clients to talk to the database through a safe, controlled interface.
 
Example:

 
C:\Users\Rajeshwaran Jeyabal> sql -mcp
---------- MCP SERVER STARTUP ----------
MCP Server started successfully on Fri Nov 28 19:27:09 IST 2025
Press Ctrl+C to stop the server
----------------------------------------
 
This server acts as a secure gateway — the AI can propose SQL, but you must approve execution.
 
 
Prerequisites
 
Before beginning, ensure the following: 
  •  Visual Studio Code v1.101 or later
  • Oracle SQL Developer Extension for VS Code (version 25.2.0+)
  • At least one Oracle Database connection with password saved
 
Once this setup is complete, the chat panel in VS Code will automatically detect the MCP tools exposed by SQLcl.

 
Saving a Database Connection for MCP
 
First create a saved connection in SQLcl — MCP relies on this.

 
C:\Trash> sql /nolog
SQLcl: Release 25.3 Production on Thu Nov 27 20:46:15 2025
idle> conn hr/hr@localhost:1521/ora23ai
Connected.
hr@ORA23AI> connect -savepwd -save hr_local_mcp
Name: hr_local_mcp
Connect String: localhost:1521/ora23ai
User: hr
Password: ******
 
Now the MCP server will be able to use this connection automatically.
 
 
See It in Action — Talking to Your Database in Natural Language
 
Open the Chat Panel in VS Code.
You will notice SQLcl’s MCP tools automatically appear on the left sidebar.
 
This confirms that VS Code + AI Assistant + SQLcl MCP Server are connected.

 


 
 
Example 1 — “Show me all tables with their data volume”
 
You simply ask the AI:
 
Can you connect to the HR database and show me all tables with their data volume?
 
Claude / ChatGPT will: 
  • Check available MCP tools
  • Propose a SQL query
  • Ask for your confirmation
  • Execute it through SQLcl
  • Return a neat table of row counts
This ensures safety — nothing runs without approval.

 

 
Claude always asks permission prior to executing any queries. We can see exactly what it plans to execute before approving.
 


 
 
Example 2 — Analyzing Employees Hired in 2008
 
You ask:

 
Show me employees hired in 2008, grouped by department, with average salary for each group. Also show the highest-paid employee in each department.
 
AI generates a multi-part SQL including:
  • Filtering by hire year
  • GROUP BY analyses
  • JOINs to departments
  • Window functions to identify the highest salary
 
And after you approve, the results come back instantly.

 
 


 
Example 3 — AI-Generated Analytics Dashboard (Markdown Report)
 
Now let’s ask something more advanced:
 
Can you create a comprehensive HR dashboard showing: department headcount, salary ranges, recent hiring trends, and identify any departments that might need attention based on salary distribution or hiring patterns. Create the report as MarkDown
 
The AI will:
 
  • Pull multiple SQL queries
  • Aggregate the results
  • Identify anomalies/trends
 
Generate a clean Markdown report that looks like this:
 
HR Dashboard (Generated by AI)
1. Department Headcount
 
                Sales — 34 employees
                IT — 19 employees
                Marketing — 12 employees
                Finance — 11 employees
 
2. Salary Distribution
 
                IT: Highest variance, indicates mixed seniority
                Sales: Several outliers with high commission-based compensation
                Finance: Narrow salary band → stable structure
 
3. Recent Hiring Trends
 
                Hiring spikes in Q2 and Q4
                Marketing hired heavily last year due to campaign cycle
 
4. Departments Needing Attention
 
                IT — high salary spread
                Marketing — rapid hiring, potential onboarding load

 
 




 
This entire dashboard was produced using: 
  • Your HR schema
  • SQL generated automatically
  • Data analyzed via AI
  • Result formatted into Markdown
 No SQL written manually.
 
Why This Matters
 
This workflow fundamentally changes how developers work with Oracle:
 
  • No need to remember every syntax detail
  • No need to hand-craft queries
  • Faster exploration of schema and data
  • Insightful reports without BI tools
  • Safe execution (AI asks before running SQL)
  • Works locally — no cloud dependency
 
With SQLcl MCP Server, your database becomes conversational.
 
Conclusion
 
Oracle has taken a major step forward by bringing MCP support directly into SQLcl. Combined with VS Code AI chat, developers can now:
 
  • Query databases in plain English
  • Let AI generate complex joins and analytics
  • Approve execution safely
  • Produce dashboards and reports instantly
 
This is the future of database development — not replacing SQL, but removing the friction around writing it.

Thursday, November 27, 2025

Creating and Using Table Hyperlink Groups in Oracle Autonomous Database

Table Hyperlinks in Oracle Autonomous Database provide a secure and convenient way to expose database objects and SQL query results through pre-authenticated URLs.
 
A recent enhancement extends this capability even further—Table Hyperlink Groups, which allow a single URL to represent multiple hyperlinks, each pointing to a table, view, or SQL query.
 
A Hyperlink Group allows you to bundle several table hyperlinks into a single group URL.
This is useful when: 
  • You want a single API endpoint that exposes multiple datasets
  • You want to simplify integration for downstream systems
  • You want to centralize access control under one pre-authenticated URL
  • You want to organize multiple SQL queries or objects logically
 
Below is a conceptual representation:



 
In this example, we will see how to create a Table Hyperlink Group using two data sources — one defined by a SQL query and the other referencing a table.
 
demo-user@ATP19C> declare
  2     l_status long;
  3  begin
  4     dbms_data_access.create_url(
  5             sqls => '[
  6                               {
  7                                     "name": "Employees table",
  8                                     "description": "Employees details",
  9                                     "sql_statement": " select empno,ename,hiredate,job,deptno from emp "
 10                               },
 11                               {
 12                                     "name": "Department table",
 13                                     "description": "Department details",
 14                                     "schema_name": "DEMO_USER",
 15                                     "schema_object_name": "DEPT"
 16                               }
 17                             ]'
 18             , result => l_status );
 19     dbms_output.put_line( l_status );
 20  end;
 21  /
{
  "status" : "SUCCESS",
  "id" : "JxfkVbJGWKYbD83nyC1wgcGTEuO2-6lzJDgN68V5Z7_Xf4OUTERg7CeUmXnU0RQh",
  "preauth_url" : "https://dataaccess.adb.us-ashburn-1.oraclecloudapps.com/adb/p/GW147LdUM4iIPKAsgNe2NIoByKCCGThJN4RKlrxHkcv7x_erMFkD8GQsgjprD64on7lQML_f2v8/data",
  "member_ids" :
  [
    "T_mxypj7jytHDoW5Li--q0Agxk_TKL3s2SZ46bJ0R6rMrDFa-X8lTLV5KjHsnEUk",
    "9I6oXda2SKvyDfTu8cCsr8c9T_BOUqIHT0kz4qgvWWAHG3dWYfoWmrDTdriNHp0J"
  ],
  "expiration_ts" : "2026-02-23T16:33:47.309Z"
}
 
PL/SQL procedure successfully completed.
 
demo-user@ATP19C>
 
Calling the group URL returns information about all member hyperlinks:
 
$ curl https://dataaccess.adb.us-ashburn-1.oraclecloudapps.com/adb/p/GW147LdUM4iIPKAsgNe2NIoByKCCGThJN4RKlrxHkcv7x_erMFkD8GQsgjprD64on7lQML_f2v8/data
 
{
  "member_urls": [
    {
      "id": "T_mxypj7jytHDoW5Li--q0Agxk_TKL3s2SZ46bJ0R6rMrDFa-X8lTLV5KjHsnEUk",
      "name": "Employees table",
      "description": "Employees details"
    },
    {
      "id": "9I6oXda2SKvyDfTu8cCsr8c9T_BOUqIHT0kz4qgvWWAHG3dWYfoWmrDTdriNHp0J",
      "name": "Department table",
      "description": "Department details"
    }
  ]
}
 
 
To query data from a specific member in the group, supply its member_id:
 
$ curl https://dataaccess.adb.us-ashburn-1.oraclecloudapps.com/adb/p/GW147LdUM4iIPKAsgNe2NIoByKCCGThJN4RKlrxHkcv7x_erMFkD8GQsgjprD64on7lQML_f2v8/data?member_id=9I6oXda2SKvyDfTu8cCsr8c9T_BOUqIHT0kz4qgvWWAHG3dWYfoWmrDTdriNHp0J
 
{
  "items": [
    {
      "DEPTNO": 10,
      "DNAME": "ACCOUNTING",
      "LOC": "NEW YORK"
    },
    {
      "DEPTNO": 20,
      "DNAME": "RESEARCH",
      "LOC": "DALLAS"
    },
    {
      "DEPTNO": 30,
      "DNAME": "SALES",
      "LOC": "CHICAGO"
    },
    {
      "DEPTNO": 40,
      "DNAME": "OPERATIONS",
      "LOC": "BOSTON"
    }
  ],
  "hasMore": false,
  "limit": 1000,
  "offset": 0,
  "count": 4,
  "links": [
    {
      "rel": "self",
      "href": "https://dataaccess.adb.us-ashburn-1.oraclecloudapps.com/adb/p/GW147LdUM4iIPKAsgNe2NIoByKCCGThJN4RKlrxHkcv7x_erMFkD8GQsgjprD64on7lQML_f2v8/data?member_id=9I6oXda2SKvyDfTu8cCsr8c9T_BOUqIHT0kz4qgvWWAHG3dWYfoWmrDTdriNHp0J"
    }
  ]
}
 
You can list all active hyperlinks — including groups — using json_table on dbms_data_access.list_active_urls:
 
demo-user@ATP19C> select *
  2  from
  3      json_table ( dbms_data_access.list_active_urls, '$[*]'
  4          columns (
  5              id
  6          , created_by
  7          , access_count
  8          , created path '$.created.timestamp()'
  9         , sql_statement
 10         , is_group_url
 11         )
 12*    ) ;
 
ID                                                                  CREATED_BY    ACCESS_COUNT    CREATED                            SQL_STATEMENT                                                       IS_GROUP_URL
___________________________________________________________________ _____________ _______________ __________________________________ ___________________________________________________________________ _______________
4PVOG8xtDt7CXzyhcKDcbAkW2rnXAWrzMAGt9W7D5IAOQwjPkrZbSGwRpsR7UNkP    DEMO_USER     1               15-NOV-25 03.54.44.291000000 PM     select * from emp where hiredate = :b1
JxfkVbJGWKYbD83nyC1wgcGTEuO2-6lzJDgN68V5Z7_Xf4OUTERg7CeUmXnU0RQh    DEMO_USER     4               25-NOV-25 04.33.47.333000000 PM                                                                        true
vzCL8zVM2Vj5Z0jGqhIb-56J7SlRvvnnqdelPVmUhOcRz2NbgG4BMVnGqY5dxCzU    DEMO_USER     4               16-NOV-25 03.57.59.390000000 PM     select * from emp where deptno = 10
v09slcWFCCHUgaH3XEEqqwMQClpSHabYebpYPaFp6Z3vsdnT6An8CZeCmfJv0-Oy    DEMO_USER     5               15-NOV-25 01.16.01.541000000 PM     select * from emp where deptno = :deptId
sfBVyDyNXhkwsodUQDHMnAaa4nrlAeVt4_2SE0F5yX0iMwfJNgUhScUlZ2Og8u0I    DEMO_USER     2               15-NOV-25 03.57.33.997000000 PM     select * from emp where hiredate >= to_date(:b1,'dd-mon-yyyy')
gTqfJcv0JfckskdR7eI77QCUrMcMe5N4k1iXeeWstlo6c7uKxXQbyQQPotFTi10E    DEMO_USER     12              21-NOV-25 02.58.18.205000000 PM     select * from emp where deptno = :b1 and job = :b2
 
6 rows selected.
 
is_group_url = TRUE identifies group hyperlinks
 
To list the member hyperlinks of an existing table hyperlink group, we can do the below pl/sql call.

 
demo-user@ATP19C> declare
  2     l_status clob;
  3  begin
  4     dbms_data_access.list_members(
  5         id => 'JxfkVbJGWKYbD83nyC1wgcGTEuO2-6lzJDgN68V5Z7_Xf4OUTERg7CeUmXnU0RQh'
  6         , result => l_status );
  7     dbms_output.put_line( l_status );
  8  end;
  9* /
{
  "members" :
  [
    {
      "id" : "T_mxypj7jytHDoW5Li--q0Agxk_TKL3s2SZ46bJ0R6rMrDFa-X8lTLV5KjHsnEUk",
      "consistent" : false,
      "sql_statement" : " select empno,ename,hiredate,job,deptno from emp ",
      "name" : "Employees table",
      "description" : "Employees details"
    },
    {
      "id" : "9I6oXda2SKvyDfTu8cCsr8c9T_BOUqIHT0kz4qgvWWAHG3dWYfoWmrDTdriNHp0J",
      "consistent" : false,
      "schema_name" : "DEMO_USER",
      "schema_object_name" : "DEPT",
      "name" : "Department table",
      "description" : "Department details"
    }
  ]
}
 
You can add an existing standalone hyperlink to a group using add_member:
 
demo-user@ATP19C> declare
  2     l_status clob ;
  3  begin
  4     dbms_data_access.add_member(
  5         id => 'JxfkVbJGWKYbD83nyC1wgcGTEuO2-6lzJDgN68V5Z7_Xf4OUTERg7CeUmXnU0RQh'
  6         , member_id =>'v09slcWFCCHUgaH3XEEqqwMQClpSHabYebpYPaFp6Z3vsdnT6An8CZeCmfJv0-Oy'
  7         , result => l_status );
  8     dbms_output.put_line( l_status );
  9  end;
 10* /
{
  "status" : "SUCCESS"
}
 
 
Verifying the update group members
 
demo-user@ATP19C> declare
  2     l_status clob;
  3  begin
  4     dbms_data_access.list_members(
  5         id => 'JxfkVbJGWKYbD83nyC1wgcGTEuO2-6lzJDgN68V5Z7_Xf4OUTERg7CeUmXnU0RQh'
  6         , result => l_status );
  7     dbms_output.put_line( l_status );
  8  end;
  9* /
{
  "members" :
  [
    {
      "id" : "T_mxypj7jytHDoW5Li--q0Agxk_TKL3s2SZ46bJ0R6rMrDFa-X8lTLV5KjHsnEUk",
      "consistent" : false,
      "sql_statement" : " select empno,ename,hiredate,job,deptno from emp ",
      "name" : "Employees table",
      "description" : "Employees details"
    },
    {
      "id" : "v09slcWFCCHUgaH3XEEqqwMQClpSHabYebpYPaFp6Z3vsdnT6An8CZeCmfJv0-Oy",
      "consistent" : false,
      "sql_statement" : " select * from emp where deptno = :deptId "
    },
    {
      "id" : "9I6oXda2SKvyDfTu8cCsr8c9T_BOUqIHT0kz4qgvWWAHG3dWYfoWmrDTdriNHp0J",
      "consistent" : false,
      "schema_name" : "DEMO_USER",
      "schema_object_name" : "DEPT",
      "name" : "Department table",
      "description" : "Department details"
    }
  ]
}
 
 
Accessing the New Member with Bind Variables, include them as query parameters:
 
$ curl --location 'https://dataaccess.adb.us-ashburn-1.oraclecloudapps.com/adb/p/GW147LdUM4iIPKAsgNe2NIoByKCCGThJN4RKlrxHkcv7x_erMFkD8GQsgjprD64on7lQML_f2v8/data?member_id=v09slcWFCCHUgaH3XEEqqwMQClpSHabYebpYPaFp6Z3vsdnT6An8CZeCmfJv0-Oy&deptId=10'
 
{
  "items": [
    {
      "EMPNO": 7839,
      "ENAME": "KING",
      "JOB": "PRESIDENT",
      "HIREDATE": "1981-11-17T00:00:00",
      "SAL": 5000,
      "DEPTNO": 10
    },
    {
      "EMPNO": 7782,
      "ENAME": "CLARK",
      "JOB": "MANAGER",
      "MGR": 7839,
      "HIREDATE": "1981-06-09T00:00:00",
      "SAL": 2450,
      "DEPTNO": 10
    },
    {
      "EMPNO": 7934,
      "ENAME": "MILLER",
      "JOB": "CLERK",
      "MGR": 7782,
      "HIREDATE": "1982-01-23T00:00:00",
      "SAL": 1300,
      "DEPTNO": 10
    }
  ],
  "hasMore": false,
  "limit": 1000,
  "offset": 0,
  "count": 3,
  "links": [
    {
      "rel": "self",
      "href": "https://dataaccess.adb.us-ashburn-1.oraclecloudapps.com/adb/p/GW147LdUM4iIPKAsgNe2NIoByKCCGThJN4RKlrxHkcv7x_erMFkD8GQsgjprD64on7lQML_f2v8/data?member_id=v09slcWFCCHUgaH3XEEqqwMQClpSHabYebpYPaFp6Z3vsdnT6An8CZeCmfJv0-Oy&deptId=10"
    }
  ]
}
 
 
 
Table Hyperlink Groups provide: 
  •  A single, central access point for multiple data sources
  • Simplified integration with external applications
  • Easier control over permissions and lifecycle
  • Support for both object-based and SQL-query-based hyperlinks
  • Flexibility through bind variables 
This feature is particularly valuable when building lightweight APIs, exposing curated datasets, or integrating with downstream data pipelines.