Thursday, November 12, 2026

Exploring Hybrid Vector Search in Oracle AI Database 26ai - Part II

In my earlier post, I explored hybrid vector search in Oracle AI Database.

For this post, let us work through a practical example using park descriptions. We will search the same data in different ways and examine how the results change.

Consider someone looking for a park where they can learn about prehistoric mammals. They might search for fossils, or ask a question such as:

Where can I explore ancient fossils and prehistoric mammals?

These searches express a related interest, but they give the search engine different information. The keyword expression specifies words to match. The question provides text that can be embedded and compared with the meaning of the park descriptions.

Using the execution below, we will explore both approaches and then combine them.

Prepare the hybrid vector index

For this demonstration, we will use the park data from the previous blogpost, The resulting table includes the park name, code, description, location, directions, and weather information. Our searches will use the PARK_DESCRIPTION column.

With the data available, creating a hybrid vector index can be as simple as:

demo@ADB26ai> create hybrid vector index park_description_hvi
  2  on parks_info( park_description )
  3  parameters( 'MODEL MY_DEMO_MODEL');

Index created.

Here, MY_DEMO_MODEL is an embedding model already available in my database.

For the remaining examples, I recreated the index using an explicit vectorizer preference. This gives us visibility into the chunking and vector-index settings used for the demonstration.

demo@ADB26ai> begin
  2     dbms_vector_chain.create_preference(
  3             pref_name => 'MY_HYBRID_INDEX_PREF'
  4             , pref_type => dbms_vector_chain.VECTORIZER
  5             , params => json_object('vector_idxtype' value 'ivf'
  6                                     , 'distance' value 'cosine'
  7                                     , 'accuracy' value 90 , 'model' value 'MY_DEMO_MODEL'
  8                                     , 'by' value 'words' , 'max' value 100
  9                                     , 'overlap' value 0, 'split' value 'recursively'
 10                                     returning json)
 11                             );
 12  end;
 13  /

PL/SQL procedure successfully completed.

The preference specifies word-based chunking with a maximum chunk size of 100 words, no overlap, and recursive splitting. It also selects an IVF vector index with cosine distance and a target search accuracy of 90. This accuracy setting is a vector-search target, rather than a measure of how relevant the answers are to a user. Oracle vectorizer preference documentation

I then recreated the index using this preference:

demo@ADB26ai> create hybrid vector index park_description_hvi
  2  on parks_info( park_description )
  3  parameters( 'VECTORIZER MY_HYBRID_INDEX_PREF');

Index created.

Inspect the generated vector data

Before searching, let us inspect a few rows from the generated vector data:

demo@ADB26ai> select *
  2  from park_description_hvi$VECTORS
  3  fetch first 5 rows only;

DOC_ROWID          DOC_CHUNK_ID DOC_CHUNK_COUNT DOC_CHUNK_OFFSET DOC_CHUNK_LENGTH DOC_CHUNK_TEXT                           DOC_CHUNK_EMBEDDING
------------------ ------------ --------------- ---------------- ---------------- ---------------------------------------- ----------------------------------------
AAQVAeABUAAPJSpAAu            1               1                1              297 Headlines screamed "Gold!" The dream of  [4.77597751E-002,7.22050816E-002,3.07750
AAQVAeABUAAPJSpAA2            1               1                1              390 The ancient geologic landscape of the up [-4.76894975E-002,9.31795128E-003,7.6873
AAQVAeABUAAPJSpAA4            1               1                1              382 Lava Beds National Monument is a land of [4.78412099E-002,2.7518522E-002,6.380993
AAQVAeABUAAPJSpABD            1               1                1              344 The Delaware River, the largest free-flo [5.27650416E-002,-3.61868404E-002,4.4181
AAQVAeABUAAPJSpABO            1               1                1              395 Welcome to Martin Luther King, Jr. Natio [6.68741763E-002,1.09639429E-001,-1.0090

This makes the relationship between the source description, its text chunk, and its embedding visible.

In the five sampled rows, DOC_CHUNK_COUNT is 1. The descriptions shown fit into a single chunk with the selected settings. This observation applies to the sampled rows; it does not establish that every park description has only one chunk.

Now let us search the index.

Starting with semantic search in document mode

Our first query asks:

Where can I explore ancient fossils and prehistoric mammals?

We supply the question inside the vector section and omit the text section.

demo@ADB26ai> SELECT JSON_SERIALIZE(
  2      DBMS_HYBRID_VECTOR.SEARCH(
  3          JSON('{
  4              "hybrid_index_name": "PARK_DESCRIPTION_HVI",
  5              "vector": {
  6                  "search_text": "Where can I explore ancient fossils and prehistoric mammals?",
  7                  "search_mode": "DOCUMENT",
  8                  "aggregator": "MAX"
  9              },
 10              "return": {
 11                  "topN": 5,
 12                  "values": ["rowid", "score","vector_score"]
 13              }
 14          }')
 15      )
 16      RETURNING CLOB PRETTY
 17  ) final_output ;

FINAL_OUTPUT
--------------------------------------------------------------------------------
[
  {
    "rowid" : "AAQVAeABUAAPJS5AAy",
    "score" : 70.91,
    "vector_score" : 70.91
  },
  {
    "rowid" : "AAQVAeABUAAPJS5AAI",
    "score" : 70.47,
    "vector_score" : 70.47
  },
  {
    "rowid" : "AAQVAeABUAAPJSgABd",
    "score" : 69.25,
    "vector_score" : 69.25
  },
  {
    "rowid" : "AAQVAeABUAAPJSgABI",
    "score" : 69.08,
    "vector_score" : 69.08
  },
  {
    "rowid" : "AAQVAeABUAAPHl8AAF",
    "score" : 69.03,
    "vector_score" : 69.03
  }
]

Because this is a vector-only search, the final score is the vector score.

DOCUMENT mode returns park-level results. When retrieved chunks belong to the same document, MAX uses the highest retrieved chunk score as that document’s vector score. Oracle search parameter documentation

The returned descriptions discuss Pliocene mammals, fossil deposits, prehistoric plants, dinosaurs, and Miocene mammals. They relate to the subject of the question without needing to contain its complete wording.

The scores are relevance values used for ranking; a score of 70.91 should not be interpreted as a 70.91% probability that the result is correct.

Connecting the results to the original park data

The search output contains the ROWID of the source row. We can use it to retrieve the original description and other park attributes.

My execution log uses JSON_TABLE to extract those row identifiers and join them back to PARKS_INFO.

demo@ADB26ai> with hits as (
  2      SELECT JSON_SERIALIZE(
  3      DBMS_HYBRID_VECTOR.SEARCH(
  4          JSON('{
  5              "hybrid_index_name": "PARK_DESCRIPTION_HVI",
  6              "vector": {
  7                  "search_text": "Where can I explore ancient fossils and prehistoric mammals?",
  8                  "search_mode": "DOCUMENT",
  9                  "aggregator": "MAX"
 10              },
 11              "return": {
 12                  "topN": 5,
 13                  "values": ["rowid", "score","vector_score"]
 14              }
 15          }')
 16      )
 17      RETURNING CLOB PRETTY
 18  ) final_output
 19  )
 20  select p.park_description
 21  from parks_info p , hits, json_table ( hits.final_output , '$[*]'
 22          columns( rid varchar2(80) path '$.rowid' , score number, vector_score) ) jt
 23  where p.rowid = jt.rid ;

PARK_DESCRIPTION
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
In the early 1900s, paleontologists unearthed the Age of Mammals when they found full skeletons of extinct Miocene mammals in the hills of Nebraska -- species previously only known through fragments.
At the same time, an age of friendship began between rancher James Cook and Chief Red Cloud of the Lakota. These two unprecedented events are preserved and protected here... at Agate Fossil Beds.

Beneath a grassy mountain valley in central Colorado lies one of the richest and most diverse fossil deposits in the world. Petrified redwood stumps up to 14 feet wide and thousands of detailed fossil
s of insects and plants reveal the story of a very different, prehistoric Colorado.

150 million years ago, dinosaurs roamed here. Their fossils are still embedded in the rocks. Today, mountains, desert, and rivers flowing in canyons support a variety of life. Petroglyphs reveal the l
ives and connections of Indigenous people to this land. Homesteaders and outlaws found refuge here. Whether your passion is science, adventure, history, or scenery, Dinosaur offers much to explore.

During the Pliocene, this place looked quite different. Lush wetlands, forests, and grasslands provided excellent habitat for a variety of animals. From fascinating, now-extinct creatures like the sab
er-toothed cat, mastodon, and ground sloth, to more familiar animals like horses, beavers, and birds, the scientific study of Pliocene fossils is the key to Hagerman.

In the ridges of southwest Wyoming are some of the best-preserved fossils in the world. They tell the story of ancient life in a warm, wet environment in and around a freshwater lake. Stingrays swam i
n the lake. Turtles basked in the sun. Leaves rustled in the breeze while early horses darted between the trunks. We reveal more of the past with each fossil found.

Returning the matching passages with chunk mode

Sometimes we want the relevant passage itself, rather than only a reference to the source document.

For the same question, we can switch to CHUNK mode and request the chunk text:

demo@ADB26ai> SELECT JSON_SERIALIZE(
  2      DBMS_HYBRID_VECTOR.SEARCH(
  3          JSON('{
  4              "hybrid_index_name": "PARK_DESCRIPTION_HVI",
  5              "vector": {
  6                  "search_text":
  7                      "Where can I explore ancient fossils and prehistoric mammals?",
  8                  "search_mode": "CHUNK"
  9              },
 10              "return": {
 11                  "values": [
 12                      "rowid",
 13                      "chunk_id",
 14                      "chunk_text",
 15                      "score",
 16                      "vector_score"
 17                  ],
 18                  "topN": 5
 19              }
 20          }')
 21      )
 22      RETURNING CLOB PRETTY
 23  ) AS search_results ;

SEARCH_RESULTS
--------------------------------------------------------------------------------
[
  {
    "rowid" : "AAQVAeABUAAPJS5AAy",
    "chunk_id" : "1",
    "chunk_text" : "During the Pliocene, this place looked quite different. Lush
 wetlands, forests, and grasslands provided excellent habitat for a variety of a
nimals. From fascinating, now-extinct creatures like the saber-toothed cat, mast
odon, and ground sloth, to more familiar animals like horses, beavers, and birds
, the scientific study of Pliocene fossils is the key to Hagerman.",
    "score" : 70.91,
    "vector_score" : 70.91
  },
  {
    "rowid" : "AAQVAeABUAAPJS5AAI",
    "chunk_id" : "1",
    "chunk_text" : "In the ridges of southwest Wyoming are some of the best-pres
erved fossils in the world. They tell the story of ancient life in a warm, wet e
nvironment in and around a freshwater lake. Stingrays swam in the lake. Turtles
basked in the sun. Leaves rustled in the breeze while early horses darted betwee
n the trunks. We reveal more of the past with each fossil found.",
    "score" : 70.47,
    "vector_score" : 70.47
  },
  {
    "rowid" : "AAQVAeABUAAPJSgABd",
    "chunk_id" : "1",
    "chunk_text" : "Beneath a grassy mountain valley in central Colorado lies on
e of the richest and most diverse fossil deposits in the world. Petrified redwoo
d stumps up to 14 feet wide and thousands of detailed fossils of insects and pla
nts reveal the story of a very different, prehistoric Colorado.",
    "score" : 69.25,
    "vector_score" : 69.25
  },
  {
    "rowid" : "AAQVAeABUAAPJSgABI",
    "chunk_id" : "1",
    "chunk_text" : "150 million years ago, dinosaurs roamed here. Their fossils
are still embedded in the rocks. Today, mountains, desert, and rivers flowing in
 canyons support a variety of life. Petroglyphs reveal the lives and connections
 of Indigenous people to this land. Homesteaders and outlaws found refuge here.
Whether your passion is science, adventure, history, or scenery, Dinosaur offers
 much to explore.",
    "score" : 69.08,
    "vector_score" : 69.08
  },
  {
    "rowid" : "AAQVAeABUAAPHl8AAF",
    "chunk_id" : "1",
    "chunk_text" : "In the early 1900s, paleontologists unearthed the Age of Mam
mals when they found full skeletons of extinct Miocene mammals in the hills of N
ebraska -- species previously only known through fragments. At the same time, an
 age of friendship began between rancher James Cook and Chief Red Cloud of the L
akota. These two unprecedented events are preserved and protected here... at Aga
te Fossil Beds.",
    "score" : 69.03,
    "vector_score" : 69.03
  }
]

This result gives us the passage that explains the match: it directly discusses extinct mammals and the study of Pliocene fossils.

For this execution, the document and chunk searches returned the same five row identifiers with the same scores. The returned chunks all had chunk_id = 1.

That similarity should not be generalized to larger documents. A document can contain several relevant chunks. Document mode groups retrieved chunks by their source document; chunk mode can return multiple passages from the same document. Oracle search modes

Searching with keywords

Next, let us search using an Oracle Text expression: fossil OR fossils

This time, we supply a text section and omit the vector section.

demo@ADB26ai> SELECT JSON_SERIALIZE(
  2      DBMS_HYBRID_VECTOR.SEARCH(
  3          JSON('{
  4              "hybrid_index_name": "PARK_DESCRIPTION_HVI",
  5              "text": {
  6                  "contains": "fossil OR fossils"
  7              },
  8              "return": {
  9                  "values": ["rowid", "score", "text_score"],
 10                  "topN": 5
 11              }
 12          }')
 13      )
 14      RETURNING CLOB PRETTY
 15  ) AS search_results ;

SEARCH_RESULTS
--------------------------------------------------------------------------------
[
  {
    "rowid" : "AAQVAeABUAAPJSpACM",
    "score" : 9,
    "text_score" : 9
  },
  {
    "rowid" : "AAQVAeABUAAPJSgABI",
    "score" : 9,
    "text_score" : 9
  },
  {
    "rowid" : "AAQVAeABUAAPJS5AAy",
    "score" : 9,
    "text_score" : 9
  },
  {
    "rowid" : "AAQVAeABUAAPJS5AAI",
    "score" : 9,
    "text_score" : 9
  },
  {
    "rowid" : "AAQVAeABUAAPJSgABd",
    "score" : 9,
    "text_score" : 9
  }
]
demo@ADB26ai>
demo@ADB26ai> with hits as (
  2     SELECT JSON_SERIALIZE(
  3             DBMS_HYBRID_VECTOR.SEARCH(
  4                     JSON('{
  5                             "hybrid_index_name": "PARK_DESCRIPTION_HVI",
  6                             "text": {
  7                                     "contains": "fossil OR fossils"
  8                             },
  9                             "return": {
 10                                     "values": ["rowid", "score", "text_score"],
 11                                     "topN": 5
 12                             }
 13                     }')
 14             )
 15             RETURNING CLOB PRETTY
 16     ) AS final_output
 17  )
 18  select p.park_description
 19  from parks_info p , hits, json_table ( hits.final_output , '$[*]'
 20          columns( rid varchar2(80) path '$.rowid' , score number, vector_score) ) jt
 21  where p.rowid = jt.rid ;

PARK_DESCRIPTION
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
During the Pliocene, this place looked quite different. Lush wetlands, forests, and grasslands provided excellent habitat for a variety of animals. From fascinating, now-extinct creatures like the sab
er-toothed cat, mastodon, and ground sloth, to more familiar animals like horses, beavers, and birds, the scientific study of Pliocene fossils is the key to Hagerman.

In the ridges of southwest Wyoming are some of the best-preserved fossils in the world. They tell the story of ancient life in a warm, wet environment in and around a freshwater lake. Stingrays swam i
n the lake. Turtles basked in the sun. Leaves rustled in the breeze while early horses darted between the trunks. We reveal more of the past with each fossil found.

150 million years ago, dinosaurs roamed here. Their fossils are still embedded in the rocks. Today, mountains, desert, and rivers flowing in canyons support a variety of life. Petroglyphs reveal the l
ives and connections of Indigenous people to this land. Homesteaders and outlaws found refuge here. Whether your passion is science, adventure, history, or scenery, Dinosaur offers much to explore.

Beneath a grassy mountain valley in central Colorado lies one of the richest and most diverse fossil deposits in the world. Petrified redwood stumps up to 14 feet wide and thousands of detailed fossil
s of insects and plants reveal the story of a very different, prehistoric Colorado.

A landscape where deep time lies fully on display, Petrified Forest National Park blends colorful badlands, vast grasslands, and one of the worldÆs largest and most vivid collections of petrified wood
. Spanning more than 200 million years of geologic history, the park protects ancient fossils, ancestral Puebloan sites, habitat for desert animals, and a sweeping stretch of historic Route 66.

The returned descriptions included Hagerman, Dinosaur, and Petrified Forest, along with descriptions of fossil deposits in Wyoming and Colorado.

The distinction is in the request we made. The semantic query asked about a subject. This request asks Oracle Text to evaluate a keyword expression against the indexed descriptions. Both requests use PARK_DESCRIPTION_HVI, but they provide different relevance signals

RequestReturned information
Semantic DOCUMENTSource documents ranked using retrieved chunk scores
Semantic CHUNKIndividual passages with their source row identifiers
Keyword searchSource documents matching the Oracle Text expression

Thursday, October 29, 2026

Exploring Hybrid Vector Search in Oracle AI Database 26ai - Part I

Keyword search and semantic search solve different problems. Keyword search finds documents containing particular words or expressions, while semantic search finds documents with meanings similar to a question—even when they use different vocabulary.

Oracle’s hybrid vector indexes bring these capabilities together, allowing us to search the same indexed content using keywords, vectors, or a combination of both.

In this post, we’ll use descriptions from the US National Park Service to create a hybrid vector index, inspect its generated chunks, and explore semantic and hybrid queries.

What is a hybrid vector index?

A hybrid vector index combines Oracle Text indexing with vector indexing. For the text-column example in this post, Oracle handles document chunking and embedding generation as part of the indexing pipeline. The vector component can use either an IVF or HNSW index

This gives us three useful search options:

  • Keyword search: Find documents matching an Oracle Text expression.
  • Semantic search: Find content similar in meaning to a question.
  • Hybrid search: Combine results from both approaches.

The appropriate option depends on the question. Semantic search can help with natural-language questions, while keyword search is useful when particular terms matter.

Oracle Hybrid Vector Index

Source documents → Text indexing, chunking and embeddings

Oracle Text index
Inverted index of terms to documents

Keyword query
fossils OR prehistoric

Keyword results
Text scores and ranks
Vector index
Document embeddings as vector points

Query embedding
Ancient fossils and prehistoric mammals

Semantic results
Vector scores and ranks

↓
Fusion — RRF / RSF / WRRF
RRF combines ranks, not raw scores.
↓
Final ranked results

Keywords find matching terms. Vectors find similar meaning. Fusion combines the results.

Why chunking matters

Embedding an entire document as a single vector can dilute its meaning when the document covers several unrelated topics. Embedding models also have input-length limits.

Chunking divides the text into smaller passages so that each embedding represents a more focused piece of content.

There is no universally correct chunk size. Small chunks may lose useful context, while large chunks may combine too many ideas or exceed the embedding model’s input limit. Chunk size and overlap should therefore be evaluated against representative questions.

For this example, I use the all-MiniLM-L12-v2 model, previously loaded into the database as MY_DEMO_MODEL. Check the input limits of the actual ONNX model you load; the original Sentence Transformers model documents a default truncation length of 256 word pieces. Words and model tokens are not interchangeable. Model card

Prepare the park descriptions

The National Park Service provides an API containing descriptions and other information about parks and related sites. An API key is required to retrieve the data. NPS API guides

For this demonstration, I downloaded a JSON response and uploaded it to OCI Object Storage.

The following example extracts selected fields from the response’s data array into a relational table. Replace the Object Storage URL with your own location.

demo@ADB26ai> create table parks_info
  2  as
  3  with rws as (
  4  select *
  5  from external(
  6    ( json_data json )
  7    type oracle_bigdata
  8       access parameters(
  9                     com.oracle.bigdata.fileformat = jsondoc
 10                     com.oracle.bigdata.credential.name = OCI$RESOURCE_PRINCIPAL
 11                     )
 12   location ('https://objectstorage.us-ashburn-1.oraclecloud.com/n/ax3h6mpnbnhx/b/tinno-etl-data-backup-dev/o/TEST/park_response_full.json')
 13   reject limit 0) )
 14   select jt.*
 15   from rws , json_table( json_data, '$.data[*]'
 16      columns( park_id varchar2(80) path '$.id'
 17          , park_fullName varchar2(500) path '$.fullName'
 18          , park_Code varchar2(20) path '$.parkCode'
 19          , park_description varchar2(1000) path '$.description'
 20          , park_latitude varchar2(80) path '$.latitude'
 21          , park_longitude varchar2(80) path '$.longitude'
 22          , park_states varchar2(20) path '$.states'
 23          , park_directions_info varchar2(1000) path '$.directionsInfo'
 24          , park_directions_url varchar2(200) path '$.directionsUrl'
 25          , park_weatherInfo varchar2(1000) path '$.weatherInfo'
 26      )
 27   ) jt ;

Table created.

This example assumes that the Autonomous Database resource principal is enabled for the executing user and has permission to read the object.

Review a few rows before indexing.

demo@ADB26ai> SELECT park_description
  2  FROM parks_info
  3  ORDER BY park_fullname
  4  FETCH FIRST 5 ROWS ONLY;

PARK_DESCRIPTION
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
For over a century people from around the world have come to rural Central Kentucky to honor the humble beginnings of our 16th president, Abraham Lincoln. His early life on Kentucky's frontier shaped
his character and prepared him to lead the nation through Civil War. Visit our country's first memorial to Lincoln, built with donations from young and old, and the site of his childhood home.

Acadia National Park protects the natural beauty of the highest rocky headlands along the Atlantic coastline of the United States, an abundance of habitats, and a rich cultural heritage. At 4 million
visits a year, it's one of the top 10 most-visited national parks in the United States. Visitors enjoy 27 miles of historic motor roads, 158 miles of hiking trails, and 45 miles of carriage roads.

From the sweet little farm at the foot of PennÆs Hill to the gentlemanÆs country estate at Peace field, Adams National Historical Park is the story of ôheroes, statesmen, philosophersàand learned wome
nö whose ideas and actions helped to transform thirteen disparate colonies into one united nation.

Over 200,000 African-American soldiers and sailors served in the U.S. Army and Navy during the Civil War. Their service helped to end the war and free over four million slaves. The African American Ci
vil War Memorial honors their service and sacrifice.

The African Burial Ground stands as the oldest and largest known excavated burial site in North America for both free and enslaved Africans. It offers a profound testament to the enduring legacy of Af
rican communities whose labor, resilience, and cultural contributions were fundamental in shaping the development of New York.

The dataset includes descriptions of landscapes, wildlife, historic sites, and fossil deposits, giving us a useful range of subjects to search.

Create the hybrid vector index

If the embedding model is already available in the database, the basic index definition is straightforward:

demo@ADB26ai> create hybrid vector index park_description_hvi
  2  on parks_info( park_description )
  3  parameters( 'MODEL MY_DEMO_MODEL');

Index created.

This uses the default vector-index and chunking settings. Oracle’s documented default vector index type is IVF. To control chunking and vector-index settings, create a vectorizer preference:

demo@ADB26ai> begin
  2     dbms_vector_chain.create_preference(
  3             pref_name => 'MY_HYBRID_INDEX_PREF'
  4             , pref_type => dbms_vector_chain.VECTORIZER
  5             , params => json_object('vector_idxtype' value 'ivf'
  6              , 'distance' value 'cosine'
  7              , 'accuracy' value 90 , 'model' value 'MY_DEMO_MODEL'
  8              , 'by' value 'words' , 'max' value 100
  9              , 'overlap' value 0, 'split' value 'recursively'
 10              returning json)
 11   );
 12  end;
 13  /

PL/SQL procedure successfully completed.

Here, the preference requests chunks of up to 100 words, no overlap, cosine distance, and an IVF vector index. The accuracy setting is an approximate-search target. It does not mean that the returned documents will be “90% relevant.” To use the preference in this demonstration, recreate the index:

demo@ADB26ai> drop index if exists park_description_hvi;

Index dropped.

demo@ADB26ai> create hybrid vector index park_description_hvi
  2  on parks_info( park_description )
  3  parameters( 'VECTORIZER MY_HYBRID_INDEX_PREF');

Index created.

demo@ADB26ai>

Inspect the generated chunks

For this non-partitioned hybrid vector index, Oracle exposes a view named: PARK_DESCRIPTION_HVI$VECTORS

We can inspect the generated chunk text and metadata:

demo@ADB26ai> select *
  2  from PARK_DESCRIPTION_HVI$VECTORS
  3  fetch first 5 rows only;


DOC_ROWID           : AAQVAeABUAAPJSpAAu
DOC_CHUNK_ID        : 1
DOC_CHUNK_COUNT     : 1
DOC_CHUNK_OFFSET    : 1
DOC_CHUNK_LENGTH    : 297
DOC_CHUNK_TEXT      : Headlines screamed "Gold!" The dream of
DOC_CHUNK_EMBEDDING : [4.77597751E-002,7.22050816E-002,3.07750

DOC_ROWID           : AAQVAeABUAAPJSpAA2
DOC_CHUNK_ID        : 1
DOC_CHUNK_COUNT     : 1
DOC_CHUNK_OFFSET    : 1
DOC_CHUNK_LENGTH    : 390
DOC_CHUNK_TEXT      : The ancient geologic landscape of the up
DOC_CHUNK_EMBEDDING : [-4.76894975E-002,9.31795128E-003,7.6873

DOC_ROWID           : AAQVAeABUAAPJSpAA4
DOC_CHUNK_ID        : 1
DOC_CHUNK_COUNT     : 1
DOC_CHUNK_OFFSET    : 1
DOC_CHUNK_LENGTH    : 382
DOC_CHUNK_TEXT      : Lava Beds National Monument is a land of
DOC_CHUNK_EMBEDDING : [4.78412099E-002,2.7518522E-002,6.380993

DOC_ROWID           : AAQVAeABUAAPJSpABD
DOC_CHUNK_ID        : 1
DOC_CHUNK_COUNT     : 1
DOC_CHUNK_OFFSET    : 1
DOC_CHUNK_LENGTH    : 344
DOC_CHUNK_TEXT      : The Delaware River, the largest free-flo
DOC_CHUNK_EMBEDDING : [5.27650416E-002,-3.61868404E-002,4.4181

DOC_ROWID           : AAQVAeABUAAPJSpABO
DOC_CHUNK_ID        : 1
DOC_CHUNK_COUNT     : 1
DOC_CHUNK_OFFSET    : 1
DOC_CHUNK_LENGTH    : 395
DOC_CHUNK_TEXT      : Welcome to Martin Luther King, Jr. Natio
DOC_CHUNK_EMBEDDING : [6.68741763E-002,1.09639429E-001,-1.0090

The view also contains DOC_CHUNK_EMBEDDING, which holds the generated vector. DOC_CHUNK_TEXT contains the text used for each chunk; the original column name, PARK_DESCRIPTION, is not a column in this generated view.

Start with semantic search

Consider the question: Where can I explore ancient fossils and prehistoric mammals?

We can search for descriptions similar in meaning to that question:

demo@ADB26ai> SELECT JSON_SERIALIZE(
  2      DBMS_HYBRID_VECTOR.SEARCH(
  3          JSON('{
  4              "hybrid_index_name": "PARK_DESCRIPTION_HVI",
  5              "vector": {
  6                  "search_text": "Where can I explore ancient fossils and prehistoric mammals?",
  7                  "search_mode": "DOCUMENT",
  8                  "aggregator": "MAX"
  9              },
 10              "return": {
 11                  "topN": 5,
 12                  "values": ["rowid", "score","vector_score"]
 13              }
 14          }')
 15      )
 16      RETURNING CLOB PRETTY
 17  ) final_output ;

FINAL_OUTPUT
--------------------------------------------------------------------------------
[
  {
    "rowid" : "AAQVAeABUAAPJS5AAy",
    "score" : 70.91,
    "vector_score" : 70.91
  },
  {
    "rowid" : "AAQVAeABUAAPJS5AAI",
    "score" : 70.47,
    "vector_score" : 70.47
  },
  {
    "rowid" : "AAQVAeABUAAPJSgABd",
    "score" : 69.25,
    "vector_score" : 69.25
  },
  {
    "rowid" : "AAQVAeABUAAPJSgABI",
    "score" : 69.08,
    "vector_score" : 69.08
  },
  {
    "rowid" : "AAQVAeABUAAPHl8AAF",
    "score" : 69.03,
    "vector_score" : 69.03
  }
]

Although the index supports hybrid search, this query supplies only a vector search specification. It therefore performs semantic search, without a keyword-search component.

DOCUMENT requests document-level results. With MAX, the highest matching chunk score is used to represent each document’s vector score, to see the actual data, we need to connect these rowids back to the table and pull the relevant columns.

demo@ADB26ai> with hits as (
  2      SELECT JSON_SERIALIZE(
  3      DBMS_HYBRID_VECTOR.SEARCH(
  4          JSON('{
  5              "hybrid_index_name": "PARK_DESCRIPTION_HVI",
  6              "vector": {
  7                  "search_text": "Where can I explore ancient fossils and prehistoric mammals?",
  8                  "search_mode": "DOCUMENT",
  9                  "aggregator": "MAX"
 10              },
 11              "return": {
 12                  "topN": 5,
 13                  "values": ["rowid", "score","vector_score"]
 14              }
 15          }')
 16      )
 17      RETURNING CLOB PRETTY
 18  ) final_output
 19  )
 20  select p.park_description
 21  from parks_info p , hits, json_table ( hits.final_output , '$[*]'
 22          columns( rid varchar2(80) path '$.rowid' , score number, vector_score) ) jt
 23  where p.rowid = jt.rid ;

PARK_DESCRIPTION
-----------------------------------------------------------------------------------

In the early 1900s, paleontologists unearthed the Age of Mammals when they found full skeletons of extinct Miocene mammals in the hills of Nebraska -- species previously only known through fragments.
At the same time, an age of friendship began between rancher James Cook and Chief Red Cloud of the Lakota. These two unprecedented events are preserved and protected here... at Agate Fossil Beds.

Beneath a grassy mountain valley in central Colorado lies one of the richest and most diverse fossil deposits in the world. Petrified redwood stumps up to 14 feet wide and thousands of detailed fossil
s of insects and plants reveal the story of a very different, prehistoric Colorado.

150 million years ago, dinosaurs roamed here. Their fossils are still embedded in the rocks. Today, mountains, desert, and rivers flowing in canyons support a variety of life. Petroglyphs reveal the l
ives and connections of Indigenous people to this land. Homesteaders and outlaws found refuge here. Whether your passion is science, adventure, history, or scenery, Dinosaur offers much to explore.

During the Pliocene, this place looked quite different. Lush wetlands, forests, and grasslands provided excellent habitat for a variety of animals. From fascinating, now-extinct creatures like the sab
er-toothed cat, mastodon, and ground sloth, to more familiar animals like horses, beavers, and birds, the scientific study of Pliocene fossils is the key to Hagerman.

In the ridges of southwest Wyoming are some of the best-preserved fossils in the world. They tell the story of ancient life in a warm, wet environment in and around a freshwater lake. Stingrays swam i
n the lake. Turtles basked in the sun. Leaves rustled in the breeze while early horses darted between the trunks. We reveal more of the past with each fossil found.

In my original sample run, the returned descriptions discussed fossil beds, dinosaurs, prehistoric plants, ancient lakes, and extinct mammals. These examples illustrate semantic retrieval, but do not establish that hybrid search improves relevance.

Combine keyword and semantic search

To demonstrate hybrid search explicitly, we can add an Oracle Text expression alongside the semantic question:

demo@ADB26ai> WITH hits AS (
  2      SELECT DBMS_HYBRID_VECTOR.SEARCH(
  3                 JSON('{
  4                     "hybrid_index_name": "PARK_DESCRIPTION_HVI",
  5                     "search_fusion": "UNION",
  6                     "search_scorer": "RRF",
  7                     "vector": {
  8                         "search_text":
  9                             "Where can I explore ancient fossils and prehistoric mammals?",
 10                         "search_mode": "DOCUMENT",
 11                         "aggregator": "MAX"
 12                     },
 13                     "text": {
 14                         "contains": "fossils OR prehistoric"
 15                     },
 16                     "return": {
 17                         "topN": 5,
 18                         "values": [
 19                             "rowid",
 20                             "score",
 21                             "vector_score",
 22                             "text_score"
 23                         ]
 24                     }
 25                 }')
 26             ) AS search_results
 27      FROM dual
 28  )
 29  SELECT p.park_fullname,
 30         p.park_states,
 31         jt.score,
 32         jt.vector_score,
 33         jt.text_score,
 34         p.park_description
 35  FROM hits h,
 36       JSON_TABLE(
 37           h.search_results,
 38           '$[*]'
 39           COLUMNS (
 40               result_position FOR ORDINALITY,
 41               document_rowid VARCHAR2(80) PATH '$.rowid',
 42               score          NUMBER       PATH '$.score',
 43               vector_score   NUMBER       PATH '$.vector_score',
 44               text_score     NUMBER       PATH '$.text_score'
 45           )
 46       ) jt,
 47       parks_info p
 48  WHERE p.ROWID = CHARTOROWID(jt.document_rowid)
 49  ORDER BY jt.result_position;

The output of the above query is turned into a single row view for easier understanding and readability

PARK_FULLNAME    : Hagerman Fossil Beds National Monument
PARK_STATES      : ID
SCORE            : 100
VECTOR_SCORE     : 70.91
TEXT_SCORE       : 9
PARK_DESCRIPTION : During the Pliocene, this place looked quite different. Lush wetlands, forests, and grasslands provided excellent habitat for a variety of animals. From fascinating, now-extinct creatures like the saber-toothed cat, mastodon, and ground sloth, to more familiar animals like horses, beavers, and birds, the scientific study of Pliocene fossils is the key to Hagerman.

PARK_FULLNAME    : Fossil Butte National Monument
PARK_STATES      : WY
SCORE            : 75
VECTOR_SCORE     : 70.47
TEXT_SCORE       : 9
PARK_DESCRIPTION : In the ridges of southwest Wyoming are some of the best-preserved fossils in the world. They tell the story of ancient life in a warm, wet environment in and around a freshwater lake. Stingrays swam in the lake. Turtles basked in the sun. Leaves rustled in the breeze while early horses darted between the trunks. We reveal more of the past with each fossil found.

PARK_FULLNAME    : Florissant Fossil Beds National Monument
PARK_STATES      : CO
SCORE            : 62.5
VECTOR_SCORE     : 69.25
TEXT_SCORE       : 9
PARK_DESCRIPTION : Beneath a grassy mountain valley in central Colorado lies one of the richest and most diverse fossil deposits in the world. Petrified redwood stumps up to 14 feet wide and thousands of detailed fossils of insects and plants reveal the story of a very different, prehistoric Colorado.

PARK_FULLNAME    : Dinosaur National Monument
PARK_STATES      : CO,UT
SCORE            : 55
VECTOR_SCORE     : 69.08
TEXT_SCORE       : 9
PARK_DESCRIPTION : 150 million years ago, dinosaurs roamed here. Their fossils are still embedded in the rocks. Today, mountains, desert, and rivers flowing in canyons support a variety of life. Petroglyphs reveal the lives and connections of Indigenous people to this land. Homesteaders and outlaws found refuge here. Whether your passion is science, adventure, history, or scenery, Dinosaur offers much to explore.

PARK_FULLNAME    : Hovenweep National Monument
PARK_STATES      : CO,UT
SCORE            : 41.67
VECTOR_SCORE     : 64.04
TEXT_SCORE       : 9
PARK_DESCRIPTION : Hovenweep preserves six prehistoric sites built between 1200 and 1300 CE which may have served as ceremonial centers. Explore a variety of structures, including multistory towers perched on canyon rims and balanced on boulders. The construction and attention to detail will leave you marveling at the skill and motivation of the builders.

This query combines a semantic question with a keyword expression, then maps the returned row identifiers to the original park records. (I’ve highlighted the occurrences of the key word matched search)

UNION combines candidates from either search, while RRF uses reciprocal rank fusion to combine their rankings.

Friday, October 16, 2026

Semantic Search in Oracle AI Database - Part III


This is the third post in our series on Oracle AI Vector Search and builds on the examples and data used in the two previous posts. The first was getting started with AI Vector search, the second was using HNSW vector index. In those posts we explored how AI Vector search allows you to find the similar content based on semantic representation or meaning rather than the unstructured data raw values
 
In the first post, we performed exhaustive similarity searches, comparing every item in the dataset to find the closest matches. In contrast, the second post used an In-Memory Neighbor Graph (HNSW) vector index to perform approximate similarity searches, which we found to be much faster than an exhaustive similarity search. Although very fast, an HNSW vector index is an in-memory only index and can require a lot of memory for large datasets. In practice, too much memory for some very large datasets.
 
In this post, we will explore how to use an Inverted File Flat (IVF) partition-based index, which is a type of Neighbor Partition vector index. This is a storage-based index as opposed to the HNSW vector index, which is an in-memory index. The IVF vector index also uses a different search technique than an HNSW index. It is designed to enhance search efficiency by narrowing the search area through the use of neighbor partitions or clusters.
 
The dataset size determines the number of partitions in the index. The center of each partition, or the centroid, represents the average vector for each partition. It is also called the center of gravity of the partition. By default, the number of partitions and their corresponding centroids is set to the square root of the dataset’s size. However, this can be adjusted by specifying the NEIGHBOR PARTITIONS parameter during index creation. The centroids conceptually partition the vector space.
 
For a query vector, the search algorithm identifies the number of nearest centroids, where the default number is the square root of the total number of centroids. This can be adjusted by setting the NEIGHBOR PARTITION PROBES parameter. The higher the number of nearest centroids, the more accurate the search, but that accuracy comes at a cost. The more centroids selected, the more work that needs to be performed. Once the number of partitions is determined, they are thoroughly scanned for the top nearest vectors to the vector specified by the query.
 
More information about how IVF indexes work are available in the docs.
 
To create IVF indexes on CASE_EMBEDDINGS vector column in the table VECTOR_SEARCH_DEMO ,the following statement can be used.

 

demo@ADB26AI> create vector index ivf_demo
  2  on VECTOR_SEARCH_DEMO( CASE_EMBEDDINGS )
  3  organization neighbor partitions
  4* distance cosine ; 

Vector INDEX created.

 

  

This statement will create a global partitioned IVF vector index and will result in the creation of two internal tables:
 
demo@ADB26AI> select table_name
  2  from user_tables
  3* where table_name like 'VECTOR$IVF_DEMO%';
 
TABLE_NAME
_________________________________________________________________
VECTOR$IVF_DEMO$4040231_4231331_0$IVF_FLAT_CENTROIDS
VECTOR$IVF_DEMO$4040231_4231331_0$IVF_FLAT_CENTROID_PARTITIONS
 
These tables are of the format:
 
  • VECTOR$<base table name>_IVF_IDX$<object info>$IVF_FLAT_CENTROIDS, containing the list of identified centroid vectors and associated ids.
  • VECTOR$<base table name>_IVF_IDX$<object info>$IVF_FLAT_CENTROID_PARTITIONS, which is list-partitioned on the centroid ids. Each partition contains the base table vectors closely related (cluster) to the corresponding centroid id for that partition.
 
IVF indexes do need some memory, either from the vector pool if it has been defined or the shared pool, to speed up index creation and during DML operations on the base table. Let’s query the Vector Pool and see how much memory our new index uses.

Details of the pool can be displayed by querying the view V$VECTOR_MEMORY_POOL:

 
demo@ADB26AI> select * from v$vector_memory_pool;
 
POOL            ALLOC_BYTES    USED_BYTES POPULATE_STATUS       CON_ID
____________ ______________ _____________ __________________ _________
1MB POOL           70479593      76546048 OUT OF MEMORY             37
64KB POOL          17619898       1310720 DONE                      37
 
This is far less than our HNSW vector index used in the Using HNSW Vector Indexes post since the index itself is not stored in memory. In fact, if we look at the memory usage of the index as we did in the previous post using the view V$VECTOR_INDEX, we see the following:
 
demo@ADB26AI> select index_name, index_organization, allocated_bytes
  2    , used_bytes, num_vectors, index_dimensions
  3  from v$vector_index
  4* where index_name ='IVF_DEMO';
 
INDEX_NAME    INDEX_ORGANIZATION        ALLOCATED_BYTES    USED_BYTES    NUM_VECTORS    INDEX_DIMENSIONS
_____________ ______________________ __________________ _____________ ______________ ___________________
IVF_DEMO      NEIGHBOR PARTITIONS               2097152         80768        1427029                 384
 
Now we can run the same query that was used in the Using HNSW Vector Indexes post with the APPROX keyword and a target accuracy that matches the index we created:
 
demo@ADB26AI> select case_primary_type , case_description , vd
  2     from (
  3     select case_primary_type
  4     , case_description
  5     , vector_distance(case_embeddings , vector_embedding( MY_DEMO_MODEL using 'LARCENY' as data ) , cosine ) vd
  6     from vector_search_demo t
  7     order by vd
  8     fetch approx first 10 rows only with target accuracy 90 )
  9  group by case_primary_type , case_description ,vd
 10  order by vd
 11* fetch first 15 rows only;
 
CASE_PRIMARY_TYPE    CASE_DESCRIPTION                  VD
____________________ _________________________________ ______________________
OTHER OFFENSE        OTHER CRIME INVOLVING PROPERTY    0.38452393962513975
 
Note that this is an approximate similarity search because we added the APPROX keyword and will use the IVF index we just created. Below is the execution plan:
 
demo@ADB26AI> select * from table( dbms_xplan.display_cursor(format=>'allstats last'));
 
PLAN_TABLE_OUTPUT
---------------------------------------
SQL_ID  2p9222n5fnzg7, child number 1
-------------------------------------
select case_primary_type , case_description , vd    from (    select
case_primary_type    , case_description    ,
vector_distance(case_embeddings , vector_embedding( MY_DEMO_MODEL using
'LARCENY' as data ) , cosine ) vd    from vector_search_demo t    order
by vd    fetch approx first 10 rows only with target accuracy 90 )
group by case_primary_type , case_description ,vd order by vd fetch
first 15 rows only
 
Plan hash value: 1724078458
 
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
| Id  | Operation                                  | Name                                                           | Starts | E-Rows | A-Rows |   A-Time   | Buffers | Reads  |
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT                           |                                                                |      1 |        |      1 |00:00:00.32 |    4977 |      3 |
|   1 |  RESULT CACHE                              | d2jdkwfk05fmrfw35q2m0zwf4c                                     |      1 |        |      1 |00:00:00.32 |    4977 |      3 |
|*  2 |   COUNT STOPKEY                            |                                                                |      1 |        |      1 |00:00:00.32 |    4977 |      3 |
|   3 |    VIEW                                    |                                                                |      1 |     10 |      1 |00:00:00.32 |    4977 |      3 |
|*  4 |     SORT GROUP BY STOPKEY                  |                                                                |      1 |     10 |      1 |00:00:00.32 |    4977 |      3 |
|   5 |      VIEW                                  |                                                                |      1 |     10 |     10 |00:00:00.32 |    4977 |      3 |
|   6 |       VIEW                                 |                                                                |      1 |     10 |     10 |00:00:00.32 |    4977 |      3 |
|   7 |        NESTED LOOPS                        |                                                                |      1 |     10 |     10 |00:00:00.32 |    4977 |      3 |
|   8 |         VIEW                               | VW_IVPSR_F17F1C9B                                              |      1 |     10 |     10 |00:00:00.32 |    4962 |      3 |
|*  9 |          COUNT STOPKEY                     |                                                                |      1 |        |     10 |00:00:00.32 |    4962 |      3 |
|  10 |           VIEW                             | VW_IVPSJ_578B79F1                                              |      1 |    169K|     10 |00:00:00.32 |    4962 |      3 |
|* 11 |            SORT ORDER BY STOPKEY           |                                                                |      1 |    169K|     10 |00:00:00.32 |    4962 |      3 |
|* 12 |             HASH JOIN                      |                                                                |      1 |    169K|   1603 |00:00:00.32 |    4962 |      3 |
|  13 |              PART JOIN FILTER CREATE       | :BF0000                                                        |      1 |      7 |      7 |00:00:00.22 |      22 |      0 |
|  14 |               VIEW                         | VW_IVCR_B5B87E67                                               |      1 |      7 |      7 |00:00:00.22 |      22 |      0 |
|* 15 |                COUNT STOPKEY               |                                                                |      1 |        |      7 |00:00:00.22 |      22 |      0 |
|  16 |                 VIEW                       | VW_IVCN_9A1D2119                                               |      1 |     59 |      7 |00:00:00.22 |      22 |      0 |
|* 17 |                  SORT ORDER BY STOPKEY     |                                                                |      1 |     59 |      7 |00:00:00.22 |      22 |      0 |
|  18 |                   TABLE ACCESS STORAGE FULL| VECTOR$IVF_DEMO$4040231_4271572_0$IVF_FLAT_CENTROIDS           |      1 |     59 |     59 |00:00:00.01 |      22 |      0 |
|  19 |              PARTITION LIST JOIN-FILTER    |                                                                |      1 |   1427K|   1603 |00:00:00.10 |    4940 |      3 |
|  20 |               TABLE ACCESS STORAGE FULL    | VECTOR$IVF_DEMO$4040231_4271572_0$IVF_FLAT_CENTROID_PARTITIONS |      7 |   1427K|   1603 |00:00:00.10 |    4940 |      3 |
|  21 |         TABLE ACCESS BY USER ROWID         | VECTOR_SEARCH_DEMO                                             |     10 |      1 |     10 |00:00:00.01 |      15 |      0 |
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
 
 
Predicate Information (identified by operation id):
---------------------------------------------------
 
   2 - filter(ROWNUM<=15)
   4 - filter(ROWNUM<=15)
   9 - filter(ROWNUM<=10)
  11 - filter(ROWNUM<=10)
  12 - access("VW_IVCR_B5B87E67"."CENTROID_ID"="VTIX_CNPART"."CENTROID_ID")
  15 - filter(ROWNUM<=7)
  17 - filter(ROWNUM<=7)
 
You may notice that we do not see a VECTOR INDEX IVF SCAN access path. In the Using HNSW Vector Indexes post, we saw a VECTOR INDEX HNSW SCAN access path. With an IVF index, we see TABLE ACCESS FULL scans of the two internal tables that were created as part of the IVF index creation. See the description of the internal tables in the IVF Vector Index Creation section above.