CTX_DOC в PL/SQL — полное руководство с 50 примерами

🟢 CTX_DOC в PL/SQL. Введение

Пакет CTX_DOC — набор сервисов представления документов Oracle Text:
FILTER, MARKUP, HIGHLIGHT, SNIPPET, GIST, THEMES, TOKENS,
а также POLICY_* версии (без предварительного индекса), PKENCODE, SET_KEY_TYPE,
LANGUAGES/STEMS/NOUN_PHRASES/PART_OF_SPEECH и SENTIMENT.

Введение

Ниже — 50 практических примеров: подготовка таблиц и индекса, фильтрация и разметка,
подсветка совпадений и сниппеты, темы и токены, policy-варианты без индекса, анализ языка и тональности,
а также лучшие практики (forward index, save copy). Во всех PL/SQL-блоках нет строки / после END;.

Подготовка демо-данных и базовой конфигурации

CREATE TABLE docs (
  id   NUMBER PRIMARY KEY,
  text CLOB
);
INSERT INTO docs (id, text) VALUES (1, 'New CPU and GPU benchmarks for gaming laptops with graphics cards.');
INSERT INTO docs (id, text) VALUES (2, 'Order pizza, burgers, and fries — fast food menu and restaurant deals.');
INSERT INTO docs (id, text) VALUES (3, 'Healthy vegan bowls, salads and fresh ingredients for diet.');
COMMIT;
BEGIN
  CTX_DDL.CREATE_PREFERENCE('lex_en','BASIC_LEXER');
  CTX_DDL.SET_ATTRIBUTE('lex_en','MIXED_CASE','YES');

  CTX_DDL.CREATE_PREFERENCE('wl_en','BASIC_WORDLIST');
  CTX_DDL.SET_ATTRIBUTE('wl_en','STEMMER','ENGLISH');

  CTX_DDL.CREATE_PREFERENCE('stop_en','BASIC_STOPLIST');
  CTX_DDL.ADD_STOPWORD('stop_en','and');

  CTX_DDL.CREATE_SECTION_GROUP('sg_html','HTML_SECTION_GROUP');
  CTX_DDL.ADD_FIELD_SECTION('sg_html','title','TITLE', TRUE);
END;
CREATE INDEX idx_docs ON docs(text)
INDEXTYPE IS CTXSYS.CONTEXT PARAMETERS (
  'LEXER lex_en WORDLIST wl_en STOPLIST stop_en SECTION GROUP sg_html SAVE COPY FILTERED'
);
BEGIN
  -- Простая политика для policy_* процедур (без предварительного индекса)
  CTX_DDL.CREATE_POLICY(
    policy_name   => 'p_text',
    filter        => NULL,
    section_group => 'SG_HTML',
    lexer         => 'LEX_EN',
    stoplist      => 'STOP_EN',
    wordlist      => 'WL_EN'
  );
END;

50 примеров по CTX_DOC

1. FILTER: получить HTML-версию документа в CLOB (in-memory)

DECLARE
  outdoc CLOB;
BEGIN
  CTX_DOC.FILTER('IDX_DOCS', '1', outdoc, plaintext => FALSE);
  DBMS_OUTPUT.PUT_LINE('Length='||DBMS_LOB.GETLENGTH(outdoc));
  DBMS_LOB.FREETEMPORARY(outdoc);
END;

2. FILTER: получить plaintext-версию документа в таблицу

CREATE TABLE filtertab (query_id NUMBER, document CLOB);

BEGIN
  CTX_DOC.FILTER('IDX_DOCS', '2', 'FILTERTAB', 1, plaintext => TRUE);
END;

3. FILTER: использовать сохранённую копию (SAVE COPY)

DECLARE
  outdoc CLOB;
BEGIN
  CTX_DOC.FILTER('IDX_DOCS', '1', outdoc, plaintext => TRUE,
                 use_saved_copy => CTX_DOC.SAVE_COPY_FALLBACK);
  DBMS_LOB.FREETEMPORARY(outdoc);
END;

4. POLICY_FILTER: фильтрация без индекса по политике

DECLARE
  outdoc CLOB;
  in_text CLOB := 'Sample <b>HTML</b> with tags and entities &amp; symbols.';
BEGIN
  CTX_DOC.POLICY_FILTER('P_TEXT', in_text, outdoc, plaintext => TRUE);
  DBMS_LOB.FREETEMPORARY(outdoc);
END;

5. IFILTER: фильтровать BLOB (заменяется POLICY_FILTER)

DECLARE
  b  BLOB;  t CLOB;
BEGIN
  DBMS_LOB.CREATETEMPORARY(t, TRUE);
  -- b должен содержать бинарный документ (PDF/DOC/XLS и т.п.)
  CTX_DOC.IFILTER(b, t);
  DBMS_LOB.FREETEMPORARY(t);
END;

6. FILTER: фильтрация с query_id в таблицу

CREATE TABLE filtertab2 (query_id NUMBER, document CLOB);

BEGIN
  CTX_DOC.FILTER('IDX_DOCS', '3', 'FILTERTAB2', 1001, plaintext => FALSE);
END;

7. FILTER: через ROWID (SET_KEY_TYPE)

DECLARE
  outdoc CLOB;
BEGIN
  CTX_DOC.SET_KEY_TYPE('ROWID');
  CTX_DOC.FILTER('IDX_DOCS', (SELECT ROWID FROM docs WHERE id=1), outdoc, TRUE);
  CTX_DOC.SET_KEY_TYPE('PRIMARY KEY');
  DBMS_LOB.FREETEMPORARY(outdoc);
END;

8. FILTER: composite PK через PKENCODE

DECLARE
  outdoc CLOB;
BEGIN
  -- если бы PK был составным:
  -- vkey := CTX_DOC.PKENCODE('A', 'B', 'C');
  -- CTX_DOC.FILTER('IDX_DOCS', vkey, outdoc);
  NULL;
END;

9. POLICY_FILTER: обработка BFILE

DECLARE
  outdoc CLOB;
  bf BFILE := BFILENAME('DOCS_DIR','manual.html');
BEGIN
  CTX_DOC.POLICY_FILTER('P_TEXT', bf, outdoc, plaintext => TRUE);
  DBMS_LOB.FREETEMPORARY(outdoc);
END;

10. FILTER: аварийное поведение при отсутствии SAVE COPY

DECLARE
  outdoc CLOB;
BEGIN
  CTX_DOC.FILTER('IDX_DOCS', '2', outdoc, plaintext => TRUE,
                 use_saved_copy => CTX_DOC.SAVE_COPY_ERROR);
  DBMS_LOB.FREETEMPORARY(outdoc);
END;

11. MARKUP: разметка совпадений (HTML)

DECLARE
  html_out CLOB;
BEGIN
  CTX_DOC.MARKUP('IDX_DOCS', '1', 'gpu AND cpu', html_out, plaintext => FALSE, tagset => 'HTML_DEFAULT');
  DBMS_LOB.FREETEMPORARY(html_out);
END;

12. MARKUP: разметка по теме (ABOUT)

DECLARE
  html_out CLOB;
BEGIN
  CTX_DOC.MARKUP('IDX_DOCS', '1', 'about(computers)', html_out, plaintext => FALSE, tagset => 'HTML_DEFAULT');
  DBMS_LOB.FREETEMPORARY(html_out);
END;

13. MARKUP_CLOB_QUERY: передать запрос CLOB

DECLARE
  q   CLOB;
  out CLOB;
BEGIN
  DBMS_LOB.CREATETEMPORARY(q, TRUE);
  DBMS_LOB.WRITEAPPEND(q, LENGTH('pizza OR burgers'), 'pizza OR burgers');
  CTX_DOC.MARKUP_CLOB_QUERY('IDX_DOCS','2', q, out, plaintext => TRUE, tagset => 'TEXT_DEFAULT');
  DBMS_LOB.FREETEMPORARY(q);
  DBMS_LOB.FREETEMPORARY(out);
END;

14. POLICY_MARKUP: без индекса по политике

DECLARE
  out CLOB;
  txt CLOB := 'Vegan bowls and salads — tasty and healthy';
BEGIN
  CTX_DOC.POLICY_MARKUP('P_TEXT', txt, 'vegan AND salads', out, plaintext => TRUE, tagset => 'TEXT_DEFAULT');
  DBMS_LOB.FREETEMPORARY(out);
END;

15. MARKUP: навигационный tagset

DECLARE
  out CLOB;
BEGIN
  CTX_DOC.MARKUP('IDX_DOCS','1','gpu', out, plaintext => FALSE, tagset => 'HTML_NAVIGATE');
  DBMS_LOB.FREETEMPORARY(out);
END;

16. MARKUP: кастомные start/end теги

DECLARE
  out CLOB;
BEGIN
  CTX_DOC.MARKUP('IDX_DOCS','2','pizza', out, plaintext => TRUE,
                  starttag => '[HIT]', endtag => '[/HIT]');
  DBMS_LOB.FREETEMPORARY(out);
END;

17. MARKUP: игнор фильтров-предикатов

DECLARE
  out CLOB;
BEGIN
  -- SDATA/HASPATH/INPATH-предикаты игнорируются в MARKUP/HIGHLIGHT/SNIPPET
  CTX_DOC.MARKUP('IDX_DOCS','1','gpu AND SDATA(price > 100)', out, tagset => 'HTML_DEFAULT');
  DBMS_LOB.FREETEMPORARY(out);
END;

18. MARKUP: plaintext-вывод

DECLARE
  out CLOB;
BEGIN
  CTX_DOC.MARKUP('IDX_DOCS','3','diet OR healthy', out, plaintext => TRUE, tagset => 'TEXT_DEFAULT');
  DBMS_LOB.FREETEMPORARY(out);
END;

19. HIGHLIGHT: получить оффсеты совпадений (in-memory)

DECLARE
  t CTX_DOC.HIGHLIGHT_TAB;
BEGIN
  CTX_DOC.HIGHLIGHT('IDX_DOCS','1','gpu', t, plaintext => TRUE);
  FOR i IN 1..t.COUNT LOOP
    DBMS_OUTPUT.PUT_LINE('offset='||t(i).offset||' len='||t(i).length);
  END LOOP;
END;

20. HIGHLIGHT: HTML-оффсеты для применения к FILTER(HTML)

DECLARE
  t CTX_DOC.HIGHLIGHT_TAB;
BEGIN
  CTX_DOC.HIGHLIGHT('IDX_DOCS','1','about(computers)', t, plaintext => FALSE);
  -- применить t к HTML из FILTER (см. пример 1)
END;

21. POLICY_HIGHLIGHT: без индекса

DECLARE
  t CTX_DOC.HIGHLIGHT_TAB;
  txt CLOB := 'Order pizza and fries for dinner';
BEGIN
  CTX_DOC.POLICY_HIGHLIGHT('P_TEXT', txt, 'pizza', t, plaintext => TRUE);
  DBMS_OUTPUT.PUT_LINE('hits='||t.COUNT);
END;

22. HIGHLIGHT: учёт регистров с лексером

DECLARE
  t CTX_DOC.HIGHLIGHT_TAB;
BEGIN
  CTX_DOC.HIGHLIGHT('IDX_DOCS','2','Pizza', t, plaintext => TRUE);
  DBMS_OUTPUT.PUT_LINE('hits='||t.COUNT);
END;

23. HIGHLIGHT: длинный запрос (CLOB)

DECLARE
  q   CLOB; t CTX_DOC.HIGHLIGHT_TAB;
BEGIN
  DBMS_LOB.CREATETEMPORARY(q, TRUE);
  DBMS_LOB.WRITEAPPEND(q, 20, 'pizza OR burgers OR fries');
  CTX_DOC.HIGHLIGHT_CLOB_QUERY('IDX_DOCS','2', q, t, plaintext => TRUE);
  DBMS_LOB.FREETEMPORARY(q);
END;

24. HIGHLIGHT: UCS-2 оффсеты — безопасная нарезка

DECLARE
  t CTX_DOC.HIGHLIGHT_TAB;
  s CLOB;
BEGIN
  CTX_DOC.FILTER('IDX_DOCS','2', s, plaintext => TRUE);
  CTX_DOC.HIGHLIGHT('IDX_DOCS','2','pizza', t, plaintext => TRUE);
  -- при нарезке по оффсетам учитывайте UCS-2 кодпоинты
  DBMS_LOB.FREETEMPORARY(s);
END;

25. HIGHLIGHT: запись результатов в таблицу

CREATE TABLE highlighttab (query_id NUMBER, offset NUMBER, length NUMBER);

BEGIN
  CTX_DOC.HIGHLIGHT('IDX_DOCS','1','gpu', 'HIGHLIGHTTAB', 42, plaintext => TRUE);
END;

26. POLICY_HIGHLIGHT: HTML-оффсеты

DECLARE
  t CTX_DOC.HIGHLIGHT_TAB;
  txt CLOB := '<p>Fast <b>GPU</b> and CPU in laptop</p>';
BEGIN
  CTX_DOC.POLICY_HIGHLIGHT('P_TEXT', txt, 'gpu AND cpu', t, plaintext => FALSE);
  DBMS_OUTPUT.PUT_LINE('HTML hits='||t.COUNT);
END;

27. SNIPPET: получить сниппет с подсветкой (in-memory)

DECLARE
  snippet CLOB;
BEGIN
  CTX_DOC.SNIPPET('IDX_DOCS','1','gpu OR cpu', snippet, starttag => '<b>', endtag => '</b>');
  DBMS_LOB.FREETEMPORARY(snippet);
END;

28. SNIPPET: сохранить в таблицу

CREATE TABLE snippettab (query_id NUMBER, snippet CLOB);

BEGIN
  CTX_DOC.SNIPPET('IDX_DOCS','2','pizza', 'SNIPPETTAB', 7, starttag => '<b>', endtag => '</b>');
END;

29. POLICY_SNIPPET: без индекса

DECLARE
  out CLOB;
  txt CLOB := 'Healthy vegan bowls are tasty and good for diet';
BEGIN
  CTX_DOC.POLICY_SNIPPET('P_TEXT', txt, 'vegan AND diet', out, starttag => '<b>', endtag => '</b>');
  DBMS_LOB.FREETEMPORARY(out);
END;

30. SNIPPET: поведение с XML секциями

-- При XML_SECTION_GROUP пользовательские теги удаляются,
-- структура игнорируется, возможны "захваты" соседнего текста.
-- Используйте NULL_SECTION_GROUP для предсказуемой выдачи сниппетов.

31. SNIPPET: ограничить длину фрагментов (separator)

DECLARE
  out CLOB;
BEGIN
  CTX_DOC.SNIPPET('IDX_DOCS','1','gpu', out, starttag => '<b>', endtag => '</b>', separator => '<b>…</b>');
  DBMS_LOB.FREETEMPORARY(out);
END;

32. SNIPPET: с учётом SAVE COPY PLAINTEXT

DECLARE
  out CLOB;
BEGIN
  CTX_DOC.SNIPPET('IDX_DOCS','3','salads OR bowls', out);
  DBMS_LOB.FREETEMPORARY(out);
END;

33. SNIPPET: по длинному запросу (CLOB)

DECLARE
  q   CLOB; out CLOB;
BEGIN
  DBMS_LOB.CREATETEMPORARY(q, TRUE);
  DBMS_LOB.WRITEAPPEND(q, 50, 'salads OR bowls OR fresh OR ingredients');
  CTX_DOC.SNIPPET_CLOB_QUERY('IDX_DOCS','3', q, out, starttag => '[', endtag => ']');
  DBMS_LOB.FREETEMPORARY(q);
  DBMS_LOB.FREETEMPORARY(out);
END;

34. SNIPPET: HTML против PLAINTEXT

DECLARE
  out_html CLOB; out_text CLOB;
BEGIN
  CTX_DOC.SNIPPET('IDX_DOCS','2','pizza', out_html, plaintext => FALSE);
  CTX_DOC.SNIPPET('IDX_DOCS','2','pizza', out_text, plaintext => TRUE);
  DBMS_LOB.FREETEMPORARY(out_html);
  DBMS_LOB.FREETEMPORARY(out_text);
END;

35. THEMES: single themes в память

DECLARE
  t CTX_DOC.THEME_TAB;
BEGIN
  CTX_DOC.THEMES('IDX_DOCS','1', t, full_themes => FALSE, num_themes => 10);
  DBMS_OUTPUT.PUT_LINE('themes='||t.COUNT);
END;

36. THEMES: full themes в таблицу

CREATE TABLE ctx_themes (query_id NUMBER, theme VARCHAR2(2000), weight NUMBER);

BEGIN
  CTX_DOC.THEMES('IDX_DOCS','1', 'CTX_THEMES', 11, full_themes => TRUE, num_themes => 20);
END;

37. POLICY_THEMES: без индекса

DECLARE
  t CTX_DOC.THEME_TAB;
  txt CLOB := 'GPU and CPU performance for gaming computers';
BEGIN
  CTX_DOC.POLICY_THEMES('P_TEXT', txt, t, full_themes => FALSE, num_themes => 5);
  DBMS_OUTPUT.PUT_LINE('themes='||t.COUNT);
END;

38. TOKENS: извлечь токены (in-memory)

DECLARE
  tk CTX_DOC.TOKEN_TAB;
BEGIN
  CTX_DOC.TOKENS('IDX_DOCS','2', tk);
  DBMS_OUTPUT.PUT_LINE('tokens='||tk.COUNT);
END;

39. TOKENS: с тезаурусом DEFAULT (synonyms)

DECLARE
  tk CTX_DOC.TOKEN_TAB;
BEGIN
  CTX_DOC.TOKENS('IDX_DOCS','1', tk, thes_name => 'DEFAULT', thes_toktype => 'SYN');
  DBMS_OUTPUT.PUT_LINE('tokens='||tk.COUNT);
END;

40. TOKENS: broader terms (BT) в таблицу

CREATE TABLE tokenstab (query_id NUMBER, token VARCHAR2(255), thes_tokens VARCHAR2(4000), offset NUMBER, length NUMBER);

BEGIN
  CTX_DOC.TOKENS('IDX_DOCS','1', 'TOKENSTAB', 'DEFAULT', 'BT', 77);
END;

41. GIST: параграфный краткий пересказ

DECLARE
  out CLOB;
BEGIN
  CTX_DOC.GIST('IDX_DOCS','1', out, glevel => 'P', pov => 'GENERIC', numParagraphs => 3);
  DBMS_LOB.FREETEMPORARY(out);
END;

42. GIST: тематический пересказ (POV)

DECLARE
  out CLOB;
BEGIN
  CTX_DOC.GIST('IDX_DOCS','1', out, glevel => 'S', pov => 'computers', numParagraphs => 6);
  DBMS_LOB.FREETEMPORARY(out);
END;

43. POLICY_LANGUAGES: определить язык текста

DECLARE
  langs CTX_DOC.LANGUAGE_TAB;
  txt CLOB := 'Этот документ на русском языке. This sentence is in English.';
BEGIN
  CTX_DOC.POLICY_LANGUAGES('P_TEXT', txt, langs);
  FOR i IN 1..langs.COUNT LOOP
    DBMS_OUTPUT.PUT_LINE(langs(i).language||' score='||langs(i).score);
  END LOOP;
END;

44. POLICY_STEMS: получить основы слов

DECLARE
  stems CTX_DOC.STEM_GROUP_TAB;
  txt CLOB := 'computers computing computed';
BEGIN
  CTX_DOC.POLICY_STEMS('P_TEXT', txt, stems, language => 'ENGLISH');
  DBMS_OUTPUT.PUT_LINE('stems groups='||stems.COUNT);
END;

45. POLICY_NOUN_PHRASES: существительные словосочетания

DECLARE
  np CTX_DOC.NOUN_PHRASE_TAB;
  txt CLOB := 'The gaming laptop battery life and keyboard layout';
BEGIN
  CTX_DOC.POLICY_NOUN_PHRASES('P_TEXT', txt, np, language => 'ENGLISH');
  DBMS_OUTPUT.PUT_LINE('noun phrases='||np.COUNT);
END;

46. POLICY_PART_OF_SPEECH: разметка частей речи

DECLARE
  pos CTX_DOC.POS_TAB;
  txt CLOB := 'Modern GPUs handle graphics efficiently';
BEGIN
  CTX_DOC.POLICY_PART_OF_SPEECH('P_TEXT', txt, pos, language => 'ENGLISH');
  DBMS_OUTPUT.PUT_LINE('pos tags='||pos.COUNT);
END;

47. SENTIMENT: сегментный скоринг

DECLARE
  seg CTX_DOC.SENTIMENT_SEGMENT_TAB;
  txt CLOB := 'The pizza is great but delivery was slow';
BEGIN
  CTX_DOC.SENTIMENT('P_TEXT', txt, seg);
  DBMS_OUTPUT.PUT_LINE('segments='||seg.COUNT);
END;

48. SENTIMENT_AGGREGATE: общий скоринг документа

DECLARE
  agg NUMBER;
  txt CLOB := 'I love the fresh salads and hate the long queue';
BEGIN
  CTX_DOC.SENTIMENT_AGGREGATE('P_TEXT', txt, agg);
  DBMS_OUTPUT.PUT_LINE('aggregate sentiment='||agg);
END;

49. Лучшие практики: forward index и save copy

-- Для ускорения MARKUP/HIGHLIGHT/SNIPPET используйте forward index;
-- Для ускорения FILTER/GIST/THEMES/TOKENS — SAVE COPY (PLAINTEXT/FILTERED)
-- Пример уже показан в создании индекса: 'SAVE COPY FILTERED'.

50. Очистка демо-объектов

BEGIN
  CTX_DDL.DROP_POLICY('P_TEXT');
END;

DROP INDEX idx_docs;
DROP TABLE filtertab PURGE;
DROP TABLE filtertab2 PURGE;
DROP TABLE highlighttab PURGE;
DROP TABLE snippettab PURGE;
DROP TABLE tokenstab PURGE;
DROP TABLE ctx_themes PURGE;
DROP TABLE docs PURGE;

Заключение

CTX_DOC закрывает большинство задач представления и анализа документов в Oracle Text:
фильтрация, разметка и подсветка, сниппеты, гисты, темы и токены, policy-варианты без индекса,
NLP-хелперы и сентимент-анализ. Подбирайте SAVE COPY и forward index под ваши нагрузки.

Ссылки на вендора


 

Понравилась статья? Поделиться с друзьями: