Vector Search no Oracle 26ai: quando o banco entende o que você quer dizer
🇺🇸 Read this post in English: Vector Search on Oracle 26ai: When the Database Understands What You Mean
O problema que o SQL não resolve
Você já precisou encontrar todos os chamados de suporte relacionados a "problemas de memória no banco", mas sem saber exatamente quais palavras o analista usou na hora de abrir o ticket?
Talvez ele tenha escrito "ORA-04031", talvez "shared pool insuficiente", talvez "banco travou por falta de memória". São três formas diferentes de descrever o mesmo problema.
Com SQL tradicional, você escolhe: ou escreve dez condições com LIKE e ainda assim não pega tudo, ou aceita que alguns registros vão escapar. O banco não entende o que você está procurando — ele só compara texto.
É exatamente esse gap que o Vector Search resolve. Em vez de comparar caracteres, o banco compara significados. E o resultado prático é impressionante: uma query com "banco travou por falta de memória" encontra, com distância 0,6453, o documento cujo título é "ORA-04031: memória insuficiente no Shared Pool" — sem nenhuma palavra em comum entre a pergunta e o título.
Neste artigo vou mostrar como isso funciona no Oracle 26ai, como configurar o ambiente com um modelo de embedding local via ONNX, e como construir buscas semânticas em cima dos dados que você já tem no banco.
📝 Este lab foi executado no Oracle 26ai on-premises, usando o modelo all-MiniLM-L12-v2 carregado localmente via ONNX — sem nenhuma chamada externa, sem dependência de API. O Oracle 23ai na nuvem (ADB) suporta a mesma feature via OCI GenAI, com configuração diferente, mas que não será tratado neste artigo.
O que é um vetor e por que isso importa
Embeddings: transformando texto em coordenadas
Um embedding é uma representação numérica de um texto em um espaço de alta dimensão. Em vez de armazenar as palavras, o modelo de embedding transforma o texto em um array de números — tipicamente centenas de dimensões — onde textos semanticamente próximos ficam geometricamente próximos no espaço.
Pense assim: se você plotar palavras em um gráfico onde a posição reflete o significado, "cachorro" e "cão" ficam muito próximos, enquanto "cachorro" e "tablespace" ficam distantes. O Vector Search opera exatamente nessa lógica, mas em 384 dimensões (no caso do modelo que use) em vez de 2.
O resultado prático: quando você busca por "falha de backup", o banco encontra documentos que falam de "ORA-19809", "FRA cheio" e "recovery point" — mesmo que nenhuma dessas palavras apareça na sua query.
O modelo aprendeu, durante o treinamento, que todos esses conceitos habitam a mesma vizinhança semântica.
Similaridade: distância como relevância
A distância entre dois vetores é o que determina a relevância.
O Oracle 26ai suporta cinco métricas, mas a mais usada para texto é a distância cosseno — que mede o ângulo entre dois vetores, independente do tamanho do texto. Quanto menor a distância, mais similares são os textos:
| Métrica | Valor ideal | Uso recomendado |
|---|---|---|
| COSINE | 0 (idêntico) | Textos de tamanhos variados — mais comum |
| EUCLIDEAN | 0 (idêntico) | Vetores normalizados, dados numéricos |
| DOT_PRODUCT | Máximo positivo | Vetores normalizados, mais rápido |
| MANHATTAN | 0 (idêntico) | Dados esparsos |
| HAMMING | 0 (idêntico) | Vetores binários |
Nos resultados do nosso lab, a distância 0,6453 entre "banco travou por falta de memória" e o documento sobre ORA-04031 indica similaridade semântica relevante. Documentos com distância acima de 0,80 raramente são relevantes para a query.
O que o Oracle 26ai oferece
O suporte nativo a vetores no Oracle foi introduzido no 23ai e evoluiu no 26ai. Os componentes principais são:
| Componente | Descrição |
|---|---|
VECTOR |
Tipo de dado nativo para armazenar embeddings diretamente em tabelas |
VECTOR_EMBEDDING |
Função SQL que gera embedding usando um modelo ONNX carregado no banco |
VECTOR_DISTANCE |
Função SQL que calcula a distância entre dois vetores |
VECTOR INDEX (HNSW) |
Índice especializado para busca aproximada de vizinhos mais próximos |
DBMS_VECTOR |
Pacote PL/SQL para carregar e gerenciar modelos ONNX |
DBMS_VECTOR_CHAIN |
Pacote PL/SQL para pipelines de chunking, extração de texto e embeddings via API |
O tipo VECTOR
A coluna VECTOR(384, FLOAT32) armazena um array de 384 números de ponto flutuante diretamente na tabela — sem tabelas auxiliares, sem extensões externas. Fixar as dimensões na definição da coluna é uma boa prática: garante que apenas vetores do modelo correto sejam inseridos.
HNSW: o índice que torna a busca viável
Sem índice, uma busca vetorial compara a query com cada linha da tabela — impraticável em ambiente de produção.
O índice HNSW (Hierarchical Navigable Small World) constrói um grafo de navegação em memória que permite encontrar os vizinhos mais próximos em tempo sub-linear. O parâmetro TARGET ACCURACY 95 equilibra velocidade e precisão — 95% dos resultados corretos são retornados, com latência muito menor que a busca exata.
📝 Oracle 26ai também suporta o índice IVF (Inverted File / Neighbor Partitions), que usa menos memória ao custo de um pouco menos de precisão. Para datasets grandes que não cabem completamente em memória, IVF pode ser a escolha certa.
O modelo de embedding: all-MiniLM-L12-v2 via ONNX
O ONNX (Open Neural Network Exchange) é um formato aberto para modelos de machine learning. O Oracle 26ai suporta o carregamento de modelos ONNX diretamente no banco via DBMS_VECTOR.LOAD_ONNX_MODEL — o modelo fica armazenado no dicionário do Oracle como um objeto de mineração e é invocado pela função SQL VECTOR_EMBEDDING sem nenhuma chamada de rede externa.
O modelo escolhido para este lab é o all-MiniLM-L12-v2, disponibilizado pela Oracle em formato ONNX aumentado — inclui tokenização e pós-processamento integrados. Ele gera vetores de 384 dimensões e suporta múltiplos idiomas, incluindo português.
Foi treinado para capturar similaridade semântica em tarefas de recuperação de informação — exatamente o que precisamos.
| Característica | all-MiniLM-L12-v2 (ONNX) | cohere.embed-multilingual-v3.0 (OCI GenAI) |
|---|---|---|
| Dimensões | 384 | 1024 |
| Execução | Local — dentro do banco | API externa — OCI GenAI |
| Custo por chamada | Zero | Por transação (caracteres) |
| Dependência de rede | Nenhuma | Sim — endpoint OCI GenAI |
| Qualidade multilingual | Boa (fine-tuned) | Alta (treinado em 100+ idiomas) |
| Disponível no ADB | Sim (*) | Sim |
| Disponível no 26ai on-prem | Sim | Sim (requer credencial API Key) |
(*) via DBMS_VECTOR.LOAD_ONNX_MODEL_CLOUD (Object Storage)
A Oracle disponibiliza o modelo neste link, conforme este artigo.
O Lab: configuração do ambiente
Pré-requisito: Vector Memory Area
Antes de criar o primeiro índice vetorial, o Oracle precisa que a vector_memory_size esteja configurada. O parâmetro pode ser alterado dinamicamente no CDB, mas precisa ser gravado no SPFILE para se tornar persistente a reinicializações do banco de dados:
-- Como SYS no CDB root
ALTER SYSTEM SET vector_memory_size = 1040M SCOPE=BOTH;
-- Configurar no PDB
ALTER SESSION SET CONTAINER = RRPDB;
ALTER SYSTEM SET vector_memory_size = 512M SCOPE=BOTH;
-- Confirmar
SELECT name, value
FROM v$spparameter
WHERE name = 'vector_memory_size';
NAME VALUE
------------------------- ---------------
vector_memory_size 536870912
⚠️ O erro ORA-51962 (vector memory area out of space) ocorre quando vector_memory_size = 0. Ele aparece no PDB, mas a causa está no CDB — o que pode confundir quem está configurando pela primeira vez.Carregando o modelo ONNX
Inicialmente é preciso que o modelo ONNX esteja em algum diretório no servidor de banco de dados, e então cria-se um diretório no banco de dados apontando para o diretório no sistema operacional para que o modelo possa ser carregado no dicionário de dados.
SQL> show pdbs
CON_ID CON_NAME OPEN MODE RESTRICTED
---------- -------------- ---------- ----------
2 PDB$SEED READ ONLY NO
3 RRPDB READ WRITE NO
SQL> alter session set container=RRPDB;
Session altered.
SQL> show pdbs
CON_ID CON_NAME OPEN MODE RESTRICTED
---------- -------------- ---------- ----------
3 RRPDB READ WRITE NO
SQL> show user
USER is "SYS"
SQL> CREATE OR REPLACE DIRECTORY onnx_models_dir
2 AS '/home/oracle/app/oracle/onnx_models';
Directory created.
SQL> select *
2 from dba_directories
3 where directory_name like 'ONNX%';
OWNER DIRECTORY_NAME DIRECTORY_PATH ORIGIN_CON_ID
------ ------------------ ----------------------------------- -------------
SYS ONNX_MODELS_DIR /home/oracle/app/oracle/onnx_models 3
1 row selected.
SQL> GRANT READ ON DIRECTORY onnx_models_dir TO rrezende;
Grant succeeded.
SQL> GRANT CREATE MINING MODEL TO rrezende;
Grant succeeded.
SQL> GRANT EXECUTE ON dbms_vector TO rrezende;
Grant succeeded.
SQL> GRANT EXECUTE ON dbms_vector_chain TO rrezende;
Grant succeeded.
Com o diretório Oracle criado e os grants concedidos, o carregamento do modelo é direto:
-- Como RREZENDE
SQL> conn rrezende@rrpdb
Enter password:
Connected.
SQL> BEGIN
2 DBMS_VECTOR.LOAD_ONNX_MODEL(
3 directory => 'ONNX_MODELS_DIR',
4 file_name => 'all_MiniLM_L12_v2.onnx',
5 model_name => 'ALL_MINILM_L12_V2'
6 );
7 END;
8 /
PL/SQL procedure successfully completed.
-- Verificar carregamento
SQL> COL model_name FOR A25
SQL> COL algorithm FOR A10
SQL> COL mining_function FOR A12
SQL> SELECT model_name, algorithm, mining_function
2 FROM user_mining_models
3 WHERE model_name = 'ALL_MINILM_L12_V2';
MODEL_NAME ALGORITHM MINING_FUNCT
------------------ ---------- ------------
ALL_MINILM_L12_V2 ONNX EMBEDDING
1 row selected.Depois de carregado, o modelo é uma função SQL nativa. Testar é simples:
SQL> SELECT VECTOR_EMBEDDING(ALL_MINILM_L12_V2 USING 'Teste do modelo ONNX local' AS DATA)
2 AS embedding_onnx
3 FROM dual;
EMBEDDING_ONNX
--------------------------------------------------------------------------
[-6.71416521E-002,2.61541009E-002,-4.83012293E-003,3.49481567E-003,
SQL> SELECT VECTOR_DIMENSION_COUNT(VECTOR_EMBEDDING(ALL_MINILM_L12_V2 USING 'Teste de dimensões' AS DATA)) AS dimensoes
2 FROM dual;
DIMENSOES
----------
384
SQL> SELECT VECTOR_DIMENSION_COUNT(VECTOR_EMBEDDING(ALL_MINILM_L12_V2 USING 'Test in English' AS DATA)) AS dimensoes_en
2 FROM dual;
DIMENSOES_EN
------------
384
384 dimensões confirmadas — em português e em inglês. O modelo foi projetado para ser multilingual.
Estrutura das tabelas
O lab usa quatro tabelas no schema RREZENDE:
| Tabela | Conteúdo | Embeddings |
|---|---|---|
doc_suporte |
12 notas de atendimento (8 PT + 4 EN) | 1 por documento |
doc_tecnico |
11 documentos técnicos Oracle (5 PT + 6 EN) | 1 por documento |
doc_pdf |
PDFs originais como BLOB | Nenhum (só o arquivo) |
doc_pdf_chunks |
Chunks dos PDFs | 1 por chunk |
A criação das tabelas e inserção de dados estão disponíveis nos scripts do lab:
doc_suporte
doc_tecnico
doc_pdf
A coluna de embedding é definida com dimensão fixa: VECTOR(384, FLOAT32). Isso impede a inserção acidental de vetores de outro modelo — uma boa prática de governança quando o ambiente pode evoluir.
Gerando os embeddings
Com o modelo carregado, gerar os embeddings é um loop PL/SQL simples. Para a tabela doc_suporte:
SQL> BEGIN
2 FOR rec IN (
3 SELECT doc_id, titulo || '. ' || conteudo AS texto
4 FROM rrezende.doc_suporte
5 WHERE embedding IS NULL
6 ) LOOP
7 UPDATE rrezende.doc_suporte
8 SET embedding = VECTOR_EMBEDDING(
9 ALL_MINILM_L12_V2 USING rec.texto AS DATA
10 )
11 WHERE doc_id = rec.doc_id;
12 END LOOP;
13 COMMIT;
14 DBMS_OUTPUT.PUT_LINE('doc_suporte: embeddings gerados com sucesso.');
15 END;
16 /
doc_suporte: embeddings gerados com sucesso.
PL/SQL procedure successfully completed.
SQL> SELECT doc_id, SUBSTR(titulo, 1, 50) AS titulo, VECTOR_DIMENSION_COUNT(embedding) AS dims
2 FROM rrezende.doc_suporte
3 ORDER BY doc_id;
DOC_ID TITULO DIMS
------ -------------------------------------------------- -----
13 Lentidão em queries após reorganização de índices 384
14 Falha de conexão intermitente — TNS-12541 384
15 Crescimento inesperado de tablespace — alerta 85% 384
16 ORA-04031: memória insuficiente no Shared Pool 384
17 Backup RMAN falhou — ORA-19809 limit exceeded 384
18 Relatório de manutenção preventiva — agosto 2026 384
19 Deadlock recorrente no módulo de faturamento 384
20 Migração de tablespace para compressão avançada 384
21 High CPU usage — unoptimized full table scan detec 384
22 Redo log switch storm — I/O saturation 384
23 Data Guard lag exceeding threshold — 45 minutes 384
24 Password expiration causing application outage 384
12 rows selected.📝 Por que concatenar título + conteúdo? O embedding captura o significado semântico do texto inteiro. Incluir o título melhora a qualidade da representação — especialmente em documentos curtos onde o título carrega informação central.
O mesmo loop é repetido para a tabela doc_tecnico, com resultado idêntico: 10 documentos, todos com 384 dimensões.
SQL> BEGIN
2 FOR rec IN (
3 SELECT doc_id, titulo || '. ' || conteudo AS texto
4 FROM rrezende.doc_tecnico
5 WHERE embedding IS NULL
6 ) LOOP
7 UPDATE rrezende.doc_tecnico
8 SET embedding = VECTOR_EMBEDDING(
9 ALL_MINILM_L12_V2 USING rec.texto AS DATA
10 )
11 WHERE doc_id = rec.doc_id;
12 END LOOP;
13 COMMIT;
14 DBMS_OUTPUT.PUT_LINE('doc_tecnico: embeddings gerados com sucesso.');
15 END;
16 /
doc_tecnico: embeddings gerados com sucesso.
PL/SQL procedure successfully completed.
SQL> SELECT doc_id, SUBSTR(titulo, 1, 50) AS titulo, VECTOR_DIMENSION_COUNT(embedding) AS dims
2 FROM rrezende.doc_tecnico
3 ORDER BY doc_id;
DOC_ID TITULO DIMS
------ ------------------------------------------------------- ------
11 Bind Variables: por que seu banco odeia SQL litera 384
12 Estatísticas do otimizador: quando coletar e quand 384
13 AWR e Statspack: diagnóstico de performance no Ora 384
14 Data Guard: conceitos e modos de proteção 384
15 RMAN: estratégia de backup para ambientes críticos 384
16 Index internals: B-tree, Bitmap, and when to use e 384
17 Partitioning strategies for large tables 384
18 Oracle Unified Auditing: tracking who did what and 384
19 Vector data type and AI Vector Search in Oracle 23 384
20 JSON Relational Duality Views — Oracle 23ai 384
10 rows selected.Criando o índice vetorial
Com os embeddings gerados, o próximo passo é criar o índice que torna a busca eficiente. Sem índice, uma query de similaridade faria um full table scan vetorial — comparando a query com cada linha da tabela, uma a uma. Funciona em tabelas pequenas, mas é inviável em ambiente de produção.
O Oracle 26ai oferece dois tipos de índice vetorial:
- HNSW (Hierarchical Navigable Small World) — constrói um grafo de navegação em memória. É o mais rápido para buscas, ideal para datasets que cabem na memória alocada para a Vector Memory Area.
- IVF (Inverted File / Neighbor Partitions) — divide o espaço vetorial em clusters e busca apenas nos clusters mais próximos da query. Usa menos memória que o HNSW, indicado para datasets muito grandes.
Para o lab, usei HNSW com DISTANCE COSINE e TARGET ACCURACY 95:
SQL> CREATE VECTOR INDEX vidx_suporte_onnx
2 ON rrezende.doc_suporte (embedding)
3 ORGANIZATION INMEMORY NEIGHBOR GRAPH
4 DISTANCE COSINE
5 WITH TARGET ACCURACY 95;
Index created.
SQL> CREATE VECTOR INDEX vidx_tecnico_onnx
2 ON rrezende.doc_tecnico (embedding)
3 ORGANIZATION INMEMORY NEIGHBOR GRAPH
4 DISTANCE COSINE
5 WITH TARGET ACCURACY 95;
Index created.
SQL> COL index_name FOR A30
SQL> COL table_name FOR A20
SQL> COL status FOR A10
SQL> SELECT index_name, table_name, status
2 FROM user_indexes
3 WHERE index_type LIKE '%VECTOR%'
4 ORDER BY table_name;
INDEX_NAME TABLE_NAME STATUS
--------------------- --------------- -------
VIDX_SUPORTE_ONNX DOC_SUPORTE VALID
VIDX_TECNICO_ONNX DOC_TECNICO VALID
2 rows selected.⚠️ O índice HNSW utiliza a Vector Memory Area — o parâmetrovector_memory_sizeconfigurado anteriormente. Se o valor for 0 ou insuficiente, o Oracle retornaORA-51962ao tentar criar o índice.
PDFs: o pipeline de chunking
Documentos longos como PDFs precisam de tratamento diferente. Um único embedding para 300 páginas perderia toda a granularidade semântica. A solução é o chunking: dividir o PDF em pedaços menores, gerar um embedding por chunk, e armazenar cada chunk como uma linha.
O Oracle 26ai oferece esse pipeline nativamente via DBMS_VECTOR_CHAIN:
PDF (BLOB)
│
▼ UTL_TO_TEXT — extrai texto do PDF
│
▼ UTL_TO_CHUNKS — divide em chunks de 150 palavras com overlap de 20
│
▼ VECTOR_EMBEDDING — gera embedding por chunk via ONNX local
│
▼ INSERT — grava chunks + embeddings na tabela
O bloco PL/SQL abaixo resolve esse pipeline de uma só vez:
SQL> DECLARE
2 l_chunks_gerados NUMBER := 0;
3 BEGIN
4 FOR pdf IN (
5 SELECT p.doc_id, p.nome_arquivo, p.conteudo_pdf
6 FROM rrezende.doc_pdf p
7 WHERE NOT EXISTS (
8 SELECT 1 FROM rrezende.doc_pdf_chunks c
9 WHERE c.doc_id = p.doc_id
10 )
11 ) LOOP
12 DBMS_OUTPUT.PUT_LINE('Processando: ' || pdf.nome_arquivo);
13 INSERT INTO rrezende.doc_pdf_chunks
14 (doc_id, chunk_seq, chunk_offset, chunk_length, chunk_text, embedding)
15 SELECT
16 pdf.doc_id,
17 JSON_VALUE(c.column_value, '$.chunk_id' RETURNING NUMBER),
18 JSON_VALUE(c.column_value, '$.chunk_offset' RETURNING NUMBER),
19 JSON_VALUE(c.column_value, '$.chunk_length' RETURNING NUMBER),
20 JSON_VALUE(c.column_value, '$.chunk_data'),
21 VECTOR_EMBEDDING(
22 ALL_MINILM_L12_V2
23 USING JSON_VALUE(c.column_value, '$.chunk_data') AS DATA
24 )
25 FROM DBMS_VECTOR_CHAIN.UTL_TO_CHUNKS(
26 DBMS_VECTOR_CHAIN.UTL_TO_TEXT(pdf.conteudo_pdf),
27 JSON('{"by":"words","max":"150","overlap":"20",
28 "split":"recursively","normalize":"all"}')
29 ) c;
30 -- SQL%ROWCOUNT deve ser lido ANTES do COMMIT
31 -- O COMMIT zera o valor de SQL%ROWCOUNT
32 l_chunks_gerados := l_chunks_gerados + SQL%ROWCOUNT;
33 DBMS_OUTPUT.PUT_LINE(' → ' || SQL%ROWCOUNT || ' chunks gerados.');
34 COMMIT;
35 END LOOP;
36 DBMS_OUTPUT.PUT_LINE('Total de chunks: ' || l_chunks_gerados);
37* END;
38 /
Processando: database-concepts.pdf
¿ 2883 chunks gerados.
Processando: SELECT_AI_no_Oracle_23ai_e_26ai-Controlando_o_consumo_de_tokens_por_schema.pdf
¿ 52 chunks gerados.
Total de chunks: 2935
PL/SQL procedure successfully completed.O resultado para os dois PDFs carregados:
SQL> COL nome_arquivo FOR A80
SQL> COL total_chunks FOR 9999
SQL> COL min_chars FOR 9999
SQL> COL max_chars FOR 9999
SQL> COL sem_embedding FOR 9999
SQL> SELECT p.doc_id, p.nome_arquivo, COUNT(c.chunk_id) AS total_chunks, MIN(c.chunk_length) AS min_chars, MAX(c.chunk_length) AS max_chars,
2 SUM(CASE WHEN c.embedding IS NULL THEN 1 ELSE 0 END) AS sem_embedding
3 FROM rrezende.doc_pdf p
4 LEFT JOIN rrezende.doc_pdf_chunks c
5 ON c.doc_id = p.doc_id
6 GROUP BY p.doc_id, p.nome_arquivo
7 ORDER BY p.doc_id;
DOC_ID NOME_ARQUIVO TOTAL_CHUNKS MIN_CHARS MAX_CHARS SEM_EMBEDDING
------- -------------------------------------------------------------------------------- ------------ --------- --------- -------------
1 database-concepts.pdf 2883 158 1181 0
2 SELECT_AI_no_Oracle_23ai_e_26ai-Controlando_o_consumo_de_tokens_por_schema.pdf 52 393 799 0
2 rows selected.2883 chunks do Oracle Database Concepts (12,73 MB, EN) e 52 chunks do artigo anterior desta série sobre SELECT AI (1,08 MB, PT) — este artigo que você provavelmente já leu. 😄
Criando o índice vetorial para os chunks
Com os chunks gerados e os embeddings populados, criamos o índice HNSW para a tabela doc_pdf_chunks:
SQL> CREATE VECTOR INDEX vidx_pdf_chunks_onnx
2 ON rrezende.doc_pdf_chunks (embedding)
3 ORGANIZATION INMEMORY NEIGHBOR GRAPH
4 DISTANCE COSINE
5 WITH TARGET ACCURACY 95;
Index created.Uma observação importante: com 2935 chunks no total, o índice HNSW precisa de mais memória que os índices das tabelas de documentos curtos. Se a vector_memory_size estiver no limite, crie o índice dos chunks por último — ele é o maior dos três.
Após a criação, todos os índices vetoriais do lab estão no ar:
SQL> COL index_name for a20
SQL> COL table_name for a20
SQL> SELECT index_name, table_name, status
2 FROM user_indexes
3 WHERE index_type LIKE '%VECTOR%'
4 ORDER BY table_name;
INDEX_NAME TABLE_NAME STATUS
--------------------- --------------- -------
VIDX_PDF_CHUNKS_ONNX DOC_PDF_CHUNKS VALID
VIDX_SUPORTE_ONNX DOC_SUPORTE. VALID
VIDX_TECNICO_ONNX DOC_TECNICO. VALID
3 rows selected.Com os três índices válidos, o banco está pronto para buscas semânticas em todas as fontes simultaneamente.
Os resultados: o banco entendendo o que você quer dizer
Busca semântica básica
A busca semântica usa VECTOR_DISTANCE com FETCH FIRST N ROWS ONLY — nada de JOIN complicado ou subquery. O índice HNSW garante que o Oracle não percorra todas as linhas.
Para facilitar os testes, armazenei o "assunto" a ser buscado em uma bind variable:
SQL> COL doc_id FOR 999
SQL> COL titulo FOR A55
SQL> COL categoria FOR A15
SQL> COL dist FOR 0.9999
SQL> VARIABLE query VARCHAR2(200)
SQL> EXEC :query := 'banco travou por falta de memória'
PL/SQL procedure successfully completed.
SQL> SELECT doc_id, SUBSTR(titulo, 1, 55) AS titulo, categoria,
2 ROUND(VECTOR_DISTANCE(embedding, VECTOR_EMBEDDING(ALL_MINILM_L12_V2 USING :query AS DATA), COSINE), 4) AS dist
3 FROM rrezende.doc_suporte
4 ORDER BY distancia ASC
5 FETCH FIRST 3 ROWS ONLY;
DOC_ID TITULO CATEGORIA DISTANCIA
------ ---------------------------------------------- -------------- ---------
16 ORA-04031: memória insuficiente no Shared Pool Memória 0.6453
14 Falha de conexão intermitente — TNS-12541 Conectividade 0.7498
17 Backup RMAN falhou — ORA-19809 limit exceeded Backup 0.7544
3 rows selected.Nenhuma das palavras da query aparece no título "ORA-04031: memória insuficiente no Shared Pool" — a query usa "banco", "travou", "falta", "memória". Mas o modelo conhece a vizinhança semântica desses conceitos e posiciona os dois textos a 0,6453 de distância no espaço vetorial.
Outro exemplo que demonstra a capacidade multilingual:
-- Query em inglês, documentos em português
SQL> EXEC :query := 'database performance degradation after maintenance'
PL/SQL procedure successfully completed.
SQL> SELECT doc_id, SUBSTR(titulo, 1, 55) AS titulo, categoria,
2 ROUND(VECTOR_DISTANCE(embedding, VECTOR_EMBEDDING(ALL_MINILM_L12_V2 USING :query AS DATA), COSINE), 4) AS dist
3 FROM rrezende.doc_suporte
4 ORDER BY distancia ASC
5 FETCH FIRST 3 ROWS ONLY;
DOC_ID TITULO CATEGORIA DISTANCIA
------ ----------------------------------------------------- --------------- ---------
13 Lentidão em queries após reorganização de índices Performance 0.5568
23 Data Guard lag exceeding threshold — 45 minutes Alta Disponibil 0.6349
idade
21 High CPU usage — unoptimized full table scan detected Performance 0.6445
3 rows selected.A query em inglês encontrou documentos em português como primeiro resultado. O modelo foi treinado para entender que "performance degradation after maintenance" e "lentidão após reorganização" são o mesmo conceito em idiomas diferentes.
Busca com filtro relacional
Vector Search e SQL convencional se combinam naturalmente — basta adicionar uma cláusula WHERE:
-- Busca semântica restrita a documentos em português
SQL> EXEC :query := 'problema de performance no banco'
PL/SQL procedure successfully completed.
SQL> SELECT doc_id, SUBSTR(titulo, 1, 55) AS titulo, idioma, categoria,
2 ROUND(VECTOR_DISTANCE(embedding, VECTOR_EMBEDDING(ALL_MINILM_L12_V2 USING :query AS DATA), COSINE), 4) AS distancia
3 FROM rrezende.doc_suporte
4 WHERE idioma = 'PT'
5 ORDER BY distancia ASC
6 FETCH FIRST 3 ROWS ONLY;
DOC_ID TITULO IDIOMA CATEGORIA DISTANCIA
------ -------------------------------------------------- ------- -------------- ---------
13 Lentidão em queries após reorganização de índices PT Performance 0.5998
16 ORA-04031: memória insuficiente no Shared Pool PT Memória 0.6987
20 Migração de tablespace para compressão avançada PT Armazenamento 0.7059
3 rows selected.Busca híbrida: semântica + keyword
Termos técnicos específicos como códigos de erro (ORA-04031) podem não ter representação semântica forte no modelo — afinal, é uma string arbitrária. A busca híbrida combina o score semântico com a presença do termo no texto:
SQL> EXEC :query := 'ORA-04031'
PL/SQL procedure successfully completed.
SQL> WITH sem AS (SELECT doc_id, VECTOR_DISTANCE(embedding, VECTOR_EMBEDDING(ALL_MINILM_L12_V2 USING :query AS DATA), COSINE) AS dist_semantica
2 FROM rrezende.doc_suporte
3 ORDER BY dist_semantica ASC
4 FETCH FIRST 10 ROWS ONLY),
5 kw AS (SELECT doc_id, 1 AS match_keyword
6 FROM rrezende.doc_suporte
7 WHERE UPPER(conteudo) LIKE UPPER('%' || :query || '%')
8 OR UPPER(titulo) LIKE UPPER('%' || :query || '%'))
9 SELECT s.doc_id, SUBSTR(d.titulo, 1, 55) AS titulo, d.categoria, ROUND(s.dist_semantica, 4) AS dist_semantica, NVL(k.match_keyword, 0) AS keyword_match,
10 ROUND(s.dist_semantica - (NVL(k.match_keyword, 0) * 0.05), 4) AS score_hibrido
11 FROM sem s
12 JOIN rrezende.doc_suporte d
13 ON d.doc_id = s.doc_id
14 LEFT JOIN kw k
15 ON k.doc_id = s.doc_id
16* ORDER BY score_hibrido ASC
SQL> /
DOC_ID TITULO CATEGORIA DIST_SEMANTICA KEYWORD_MATCH SCORE_HIBRIDO
------ -------------------------------------------------- --------------- -------------- ------------- -------------
16 ORA-04031: memória insuficiente no Shared Pool Memória .5008 1 .4508
14 Falha de conexão intermitente — TNS-12541 Conectividade .5687 0 .5687
17 Backup RMAN falhou — ORA-19809 limit exceeded Backup .651 0 .651
15 Crescimento inesperado de tablespace — alerta 85% Armazenamento .7328 0 .7328
24 Password expiration causing application outage Conectividade .7904 0 .7904
22 Redo log switch storm — I/O saturation Performance .803 0 .803
18 Relatório de manutenção preventiva — agosto 2026 Manutenção .8219 0 .8219
19 Deadlock recorrente no módulo de faturamento Concorrência .8264 0 .8264
23 Data Guard lag exceeding threshold — 45 minutes Alta Disponibil .8383 0 .8383
idade
13 Lentidão em queries após reorganização de índices Performance .8492 0 .8492
10 rows selected.O documento sobre ORA-04031 já era o mais próximo semanticamente (0,5008), mas o keyword_match = 1 — por conter o termo exato no título — reduz ainda mais o score híbrido para 0,4508, reforçando a relevância. Uma combinação que captura o melhor dos dois mundos.
Busca em PDFs: chunks semânticos
A busca em documentos longos mostra o poder do chunking. Com "controle de tokens e custo de LLM", o banco encontra trechos específicos do artigo anterior desta série — mesmo sendo um PDF de 52 páginas:
SQL> EXEC :query := 'controle de tokens e custo de LLM'
PL/SQL procedure successfully completed.
SQL> SELECT c.chunk_id, c.doc_id, p.nome_arquivo, c.chunk_seq,
2 ROUND(VECTOR_DISTANCE(c.embedding, VECTOR_EMBEDDING(ALL_MINILM_L12_V2 USING :query AS DATA), COSINE), 4) AS distancia,
3 CAST(SUBSTR(c.chunk_text, 1, 60) AS VARCHAR2(60)) AS trecho
4 FROM rrezende.doc_pdf_chunks c
5 JOIN rrezende.doc_pdf p
6 ON p.doc_id = c.doc_id
7 ORDER BY distancia ASC
8* FETCH FIRST 5 ROWS ONLY
SQL> /
CHUNK_ID DOC_ID NOME_ARQUIVO CHUNK_SEQ DISTANCIA TRECHO
-------- ------ ------------------------------------------------------------------------------- --------- --------- ------------------------------------------------------------
5869 2 SELECT_AI_no_Oracle_23ai_e_26ai-Controlando_o_consumo_de_tokens_por_schema.pdf 51 0.5064 AI_OPS + PKG_AI_GATEWAY resolve os três problemas de uma vez
5868 2 SELECT_AI_no_Oracle_23ai_e_26ai-Controlando_o_consumo_de_tokens_por_schema.pdf 50 0.5515 naturalmente alinhada com o que aparece na fatura. O
5866 2 SELECT_AI_no_Oracle_23ai_e_26ai-Controlando_o_consumo_de_tokens_por_schema.pdf 48 0.5698 rastreamento de tokens é ainda mais relevante nesse modelo d
5865 2 SELECT_AI_no_Oracle_23ai_e_26ai-Controlando_o_consumo_de_tokens_por_schema.pdf 47 0.5917 COUNT(*) AS chamadas, SUM(tokens_total * 4) AS total_chars,
5867 2 SELECT_AI_no_Oracle_23ai_e_26ai-Controlando_o_consumo_de_tokens_por_schema.pdf 49 0.6006 GROUP BY caller_schema, error_code, error_message
5 rows selected.Os chunks 51, 50 e 48 — todos do artigo PT — foram os mais relevantes, mostrando que o modelo encontrou os trechos certos dentro de um documento real em português.
Busca unificada: múltiplas fontes em uma query
O destaque final do lab: buscar simultaneamente em notas de suporte, documentação técnica e chunks de PDF, com uma única query e o embedding gerado uma só vez:
-- Gerar o embedding da query uma única vez e reutilizar nas três buscas
SQL> EXEC :query := 'falha de backup e recovery no Oracle'
PL/SQL procedure successfully completed.
SQL> WITH q_embed AS (SELECT VECTOR_EMBEDDING(ALL_MINILM_L12_V2 USING :query AS DATA) AS vec
2 FROM dual)
3 SELECT 'Suporte' AS origem, doc_id, TO_NUMBER(NULL) AS chunk_seq,
4 CAST(SUBSTR(titulo, 1, 60) AS VARCHAR2(60)) AS titulo_trecho,
5 ROUND(VECTOR_DISTANCE(embedding, q.vec, COSINE), 4) AS distancia
6 FROM rrezende.doc_suporte, q_embed q
7 WHERE embedding IS NOT NULL
8 UNION ALL
9 SELECT 'Técnico' AS origem, doc_id, TO_NUMBER(NULL) AS chunk_seq,
10 CAST(SUBSTR(titulo, 1, 60) AS VARCHAR2(60)) AS titulo_trecho,
11 ROUND(VECTOR_DISTANCE(embedding, q.vec, COSINE), 4) AS distancia
12 FROM rrezende.doc_tecnico, q_embed q
13 WHERE embedding IS NOT NULL
14 UNION ALL
15 SELECT 'PDF' AS origem, c.doc_id, c.chunk_seq,
16 CAST(SUBSTR(c.chunk_text, 1, 60) AS VARCHAR2(60)) AS titulo_trecho,
17 ROUND(VECTOR_DISTANCE(c.embedding, q.vec, COSINE), 4) AS distancia
18 FROM rrezende.doc_pdf_chunks c, q_embed q
19 WHERE c.embedding IS NOT NULL
20 ORDER BY distancia ASC
21 FETCH FIRST 10 ROWS ONLY;
ORIGEM DOC_ID CHUNK_SEQ TITULO_TRECHO DISTANCIA
---------- ------ ---------- ---------------------------------------------------------------------- ---------
PDF 1 2244 files, the 0.3792
server parameter file
, and control file. A sam
Técnico 15 RMAN: estratégia de backup para ambientes críticos 0.3966
PDF 1 2264 Oracle AI Database Backup and Recovery User's Guide fo 0.3979
PDF 1 2584 unplanned outages of networks, nodes, storage, and databases 0.4036
PDF 1 2270 block corruption 0.4060
D:20260420030906-08'00'
Data Corruption
PDF 1 2272 concepts 0.4194
Zero Data Loss Recovery Appliance
The cloud-scale
PDF 1 2263 Typically, you have multiple ways to solve the preceding pro 0.4206
PDF 1 1663 Oracle AI Database Backup and Recovery User's Guide to learn 0.4257
PDF 1 2278 Copyright © 1993, 2026, Oracle and/or its affiliates. 0.4417
April
PDF 1 2437 D:20260420030906-08'00' 0.4438
undo tablespace
D:20260420030906-0
10 rows selected.O documento técnico sobre RMAN aparece em 2º lugar (distância 0,3966), logo após chunks do Database Concepts que tratam exatamente do mesmo tema. O banco varreu três fontes diferentes — texto livre, documentação estruturada e PDF — e retornou os resultados mais relevantes, ordenados por similaridade semântica.
Um detalhe de implementação importante: a subquery q_embed gera o embedding da query uma única vez e o reutiliza nas três buscas. Sem ela, o banco chamaria VECTOR_EMBEDDING três vezes para o mesmo string — triplicando o trabalho do modelo.
COSINE vs EUCLIDEAN: qual métrica usar?
Neste lab também comparei as duas métricas mais comuns. Para "falha de backup no banco de dados":
SQL> EXEC :query := 'falha de backup no banco de dados'
PL/SQL procedure successfully completed.
SQL> SELECT doc_id, SUBSTR(titulo, 1, 40) AS titulo,
2 ROUND(VECTOR_DISTANCE(embedding, VECTOR_EMBEDDING(ALL_MINILM_L12_V2 USING :query AS DATA), COSINE), 4) AS dist_cosine,
3 ROUND(VECTOR_DISTANCE(embedding, VECTOR_EMBEDDING(ALL_MINILM_L12_V2 USING :query AS DATA), EUCLIDEAN), 4) AS dist_euclidean
4 FROM rrezende.doc_suporte
5 ORDER BY dist_cosine ASC
6 FETCH FIRST 5 ROWS ONLY;
DOC_ID TITULO DIST_COSINE DIST_EUCLIDEAN
------ ----------------------------------------- ----------- --------------
17 Backup RMAN falhou — ORA-19809 limit exc .57 1.0677
18 Relatório de manutenção preventiva — ago .6652 1.1535
19 Deadlock recorrente no módulo de faturam .7451 1.2207
14 Falha de conexão intermitente — TNS-1254 .7544 1.2284
13 Lentidão em queries após reorganização d .7562 1.2298
5 rows selected.O ranking é consistente entre as duas métricas para este dataset — o que é esperado quando os textos têm tamanhos similares.
A diferença aparece em coleções com textos de tamanhos muito variados: COSINE tende a ser mais robusto porque é invariante à magnitude do vetor — dois textos de tamanhos muito diferentes podem ser semanticamente próximos mesmo com normas diferentes.
O que levar pra casa
Vector Search no Oracle 26ai não é uma feature de nicho para projetos de IA. É uma extensão natural da capacidade relacional do banco — você continua usando SQL, continua com suas tabelas normais, continua com ACID e índices. Apenas adiciona uma nova dimensão de busca: o significado.
Os pontos principais:
- Modelo ONNX local: sem dependência externa, sem custo por chamada, sem latência de rede — o modelo roda dentro do banco.
- SQL nativo:
VECTOR_EMBEDDINGeVECTOR_DISTANCEsão funções SQL. Combinam naturalmente comWHERE,JOINe qualquer predicado relacional. - Multilingual de graça: o all-MiniLM-L12-v2 entende português e inglês — uma query em PT encontra documentos em EN e vice-versa.
- Chunking nativo de PDF: o pipeline
UTL_TO_TEXT→UTL_TO_CHUNKS→VECTOR_EMBEDDINGroda inteiramente no banco, sem ferramentas externas. - Busca híbrida: semântica + keyword no mesmo SQL — o melhor de cada abordagem para termos técnicos específicos.
O próximo passo: SELECT AI com RAG
Vector Search resolve o problema da recuperação — encontrar os documentos certos. O próximo artigo da série vai além: usar esses documentos como contexto para um LLM gerar respostas em linguagem natural.
Isso é o RAG (Retrieval-Augmented Generation) — e o Oracle 26ai tem suporte nativo via SELECT AI com a opção vector_index_name no profile.
Participe da conversa
Você já está usando Vector Search no Oracle em produção? Qual modelo de embedding está usando — ONNX local ou API externa? Qual foi o maior obstáculo na configuração?
Deixe um comentário abaixo e me encontre no LinkedIn — esse é exatamente o tipo de assunto que rende uma boa conversa entre DBAs.
Até o próximo artigo.
— Ricardo Rezende (@ricarezende)
Artigos anteriores desta série:
› SELECT AI no Oracle 23ai e 26ai: conversando com o banco em português
› SELECT AI no Oracle 23ai e 26ai: controlando o consumo de tokens por schema