Oracle: полнотекстовый поиск с условием

Я создал индекс Oracle Text следующим образом:

create index my_idx on my_table (text) indextype is ctxsys.context; 

И тогда я могу сделать следующее:

select * from my_table where contains(text, '%blah%') > 0;

Но допустим, у нас есть другой столбец в этой таблице, скажем, group_id, и вместо этого я хотел выполнить следующий запрос:

select * from my_table where contains(text, '%blah%') > 0 and group_id = 43;

С указанным выше индексом Oracle должен будет искать все элементы, содержащие 'blah', а затем проверять все их group_id.

В идеале я бы предпочел искать только элементы с group_id = 43, поэтому мне нужен такой индекс:

create index my_idx on my_table (group_id, text) indextype is ctxsys.context; 

Похоже на обычный индекс, поэтому для каждого group_id можно выполнять отдельный текстовый поиск.

Есть ли способ сделать что-то подобное в Oracle (я использую 10g, если это важно)?

Изменить (пояснение)

Рассмотрим таблицу с миллионом строк и следующими двумя столбцами, среди прочих, A и B, оба числовые. Допустим, существует 500 различных значений A и 2000 различных значений B, и каждая строка уникальна.

Теперь давайте рассмотрим select ... where A = x and B = y

Насколько я могу судить, индекс по A и B отдельно выполняет поиск по индексу по B, который возвращает 500 разных строк, а затем выполняет объединение/сканирование этих строк. В любом случае необходимо просмотреть не менее 500 строк (кроме того, что базе данных повезет и она найдет нужную строку раньше.

В то время как индекс по (A,B) намного эффективнее, он находит одну строку за один поиск по индексу.

Помещение отдельных индексов на group_id и текст, который я чувствую, оставляет генератору запросов только два варианта.

(1) Используйте индекс group_id и просмотрите все результирующие строки в поисках текста.
(2) Используйте текстовый индекс и просмотрите все результирующие строки в поисках group_id.
(3) Используйте оба индекса и сделать присоединение.

В то время как я хочу:

(4) Используйте индекс (group_id, "text"), чтобы найти текстовый индекс под конкретным group_id и отсканировать этот текстовый индекс для конкретной строки/строк, которые мне нужны. Не требуется сканирование, проверка или объединение, как при использовании индекса на (A,B).


person Clinton    schedule 09.09.2011    source источник
comment
Я не думаю, что вы понимаете, что на самом деле делает contains(text, ...). contains() — это не функция, которую вы используете для фильтрации результатов на основе появления определенного слова. На самом деле он вычисляет своего рода оценку релевантности любого данного текста для столбца, в котором вы его используете.   -  person NullUserException    schedule 14.09.2011
comment
Скажем, у вас есть строка, содержащая text = 'hello world'. Когда вы делаете where contains(text, 'hello') > 0, эта строка может быть включена или нет. Вы уверены, что это именно то, чего вы на самом деле хотите?   -  person NullUserException    schedule 14.09.2011
comment
@NullUserException: Не могли бы вы объяснить в ответе, что на самом деле делают contains(...)catsearch(...)), и выполняет ли кто-либо из них полнотекстовый поиск? (то есть то, что вы обычно получаете, если используете find в текстовом редакторе).   -  person Clinton    schedule 14.09.2011


Ответы (4)


Текст Oracle

1. Вы можете повысить производительность, создав индекс CONTEXT с помощью ФИЛЬТР ПО:

create index my_idx on my_table(text) indextype is ctxsys.context filter by group_id;

В моих тестах filter by определенно улучшил производительность, но все же было немного быстрее, если просто использовать индекс btree для group_id.

2. В индексах CTXCAT используются «подиндексы», и похоже, что они работают так же, как и индексы с несколькими столбцами. Кажется, это вариант (4), который вы ищете:

begin
  ctx_ddl.create_index_set('my_table_index_set');
  ctx_ddl.add_index('my_table_index_set', 'group_id');
end;
/

create index my_idx2 on my_table(text) indextype is ctxsys.ctxcat
    parameters('index set my_table_index_set');

select * from my_table where catsearch(text, 'blah', 'group_id = 43') > 0

Это, вероятно, самый быстрый способ. Использование приведенного выше запроса к 120 МБ случайного текста, аналогичного вашему сценарию A и B, потребовало только 18 согласованных операций получения. Но с другой стороны, создание индекса CTXCAT заняло почти 11 минут и использовало 1,8 ГБ места.

(Примечание: кажется, что Oracle Text здесь работает правильно, но я не знаком с Text и не могу гарантировать, что это не ненадлежащее использование этих индексов, как сказал @NullUserException.)

Многостолбцовые индексы и объединение индексов

Для ситуации, которую вы описываете в своем редактировании, обычно не будет существенной разницы между использованием индекса на (A, B) и объединением отдельных индексов на A и B. Я построил несколько тестов с данными, похожими к тому, что вы описали, и для присоединения к индексу требуется только 7 согласованных операций получения по сравнению с 2 согласованными операциями получения для индекса с несколькими столбцами.

Причина этого в том, что Oracle извлекает данные блоками. Блок обычно имеет размер 8 КБ, а индексный блок уже отсортирован, поэтому вы, вероятно, сможете уместить значения от 500 до 2000 в несколько блоков. Если вы беспокоитесь о производительности, обычно имеет значение только ввод-вывод для чтения и записи блоков. Независимо от того, должен ли Oracle объединять несколько тысяч строк, это несущественное количество процессорного времени.

Однако это не относится к индексам Oracle Text. Вы можете соединить индекс CONTEXT с индексом btree ("битмап и"?), но производительность низкая.

person Jon Heller    schedule 16.09.2011

Я бы поставил индекс на group_id и посмотрел, достаточно ли этого. Вы не говорите, сколько строк мы говорим или какая производительность вам нужна.

Помните, что порядок, в котором обрабатываются предикаты, не обязательно совпадает с порядком, в котором вы написали их в запросе. Не пытайтесь перехитрить оптимизатора, если у вас нет для этого реальной причины.

person eaolson    schedule 10.09.2011
comment
В самом деле. Оптимизатор должен иметь возможность оценить, какой индекс будет наиболее подходящим для использования между индексом CONTEXT и индексом B-TREE. Статистика поможет выбрать хороший план выполнения. - person Benoit; 10.09.2011

Краткая версия: в этом нет необходимости. Оптимизатор запросов достаточно умен, чтобы решить, как лучше выбрать ваши данные. Просто создайте индекс btree для group_id, т.е.:

CREATE INDEX my_group_idx ON my_table (group_id); 

Длинная версия: я создал скрипт (testperf.sql), который вставляет 136 строк фиктивных данных.

DESC my_table;

Name     Null     Type      
-------- -------- --------- 
ID       NOT NULL NUMBER(4) 
GROUP_ID          NUMBER(4) 
TEXT              CLOB      

На group_id есть индекс btree. Чтобы убедиться, что индекс действительно будет использоваться, запустите это как пользователь dba:

EXEC DBMS_STATS.GATHER_TABLE_STATS('<YOUR USER HERE>', 'MY_TABLE', cascade=>TRUE);

Вот сколько строк имеет каждый group_id и соответствующий процент:

GROUP_ID               COUNT                  PCT                    
---------------------- ---------------------- ---------------------- 
1                      1                      1                      
2                      2                      1                      
3                      4                      3                      
4                      8                      6                      
5                      16                     12                     
6                      32                     24                     
7                      64                     47                     
8                      9                      7         

Обратите внимание, что оптимизатор запросов будет использовать индекс только в том случае, если он считает это хорошей идеей, то есть вы извлекаете до определенного процента строк. Итак, если вы спросите у него план запроса на:

SELECT * FROM my_table WHERE group_id = 1;
SELECT * FROM my_table WHERE group_id = 7;

Вы увидите, что для первого запроса будет использоваться индекс, тогда как для второго запроса будет выполнено полное сканирование таблицы, так как имеется слишком много строк для эффективного индекса при group_id = 7.

Теперь рассмотрим другое условие - WHERE group_id = Y AND text LIKE '%blah%' (поскольку я не очень знаком с ctxsys.context).

SELECT * FROM my_table WHERE group_id = 1 AND text LIKE '%ipsum%';

Взглянув на план запроса, вы увидите, что он будет использовать индекс для group_id. Обратите внимание, что порядок ваших условий не важен:

SELECT * FROM my_table WHERE text LIKE '%ipsum%' AND group_id = 1;

Создает тот же план запроса. И если вы попытаетесь запустить тот же запрос на group_id = 7, вы увидите, что он возвращается к полному сканированию таблицы:

SELECT * FROM my_table WHERE group_id = 7 AND text LIKE '%ipsum%';

Обратите внимание, что Oracle автоматически собирает статистику каждый день (она запланирована каждую ночь и по выходным), чтобы постоянно повышать эффективность оптимизатора запросов. Короче говоря, Oracle делает все возможное для оптимизации оптимизатора, так что вам это не нужно.

person NullUserException    schedule 11.09.2011

У меня нет под рукой экземпляра Oracle для тестирования, и я не использовал полнотекстовое индексирование в Oracle, но в целом у меня была хорошая производительность с встроенными представлениями, которые могут быть альтернативой сортировке индекса, который вы имели в виду. Допустим ли следующий синтаксис, когда используется contains()?

Это встроенное представление дает вам значения PK строк в группе 43:

             (
             select T.pkcol
             from T
             where group = 43
             )

Если группа имеет нормальный индекс и не имеет низкой кардинальности, получение этого набора должно быть быстрым. Затем вы снова соедините этот набор с T:

           select * from T
           inner join
            (
             select T.pkcol
             from T
             where group = 43
             ) as MyGroup

           on T.pkcol = MyGroup.pkcol
           where contains(text, '%blah%') > 0

Будем надеяться, что оптимизатор сможет использовать индекс PK для оптимизации соединения, а затем применить предикат contains только к строкам группы 43.

person Tim    schedule 10.09.2011
comment
Привет, Тим, я не понимаю, как можно использовать текстовый индекс только для строк группы 43? Забыв на данный момент о тексте оракула, если у меня есть отдельные индексы, скажем, для столбцов A и B, если я хочу сделать select ... where A = x and B = y, я должен либо (1) использовать индекс A и проверить все элементы B или (2) использовать индекс B и проверьте все элементы A. Чтобы этот запрос был эффективным, вам действительно нужен индекс (A,B) (или (B,A)). Я не понимаю, как Oracle Text меняет эту реальность? - person Clinton; 11.09.2011
comment
@Clinton: если соединение выполняется до применения условия where, то вы ошибаетесь; если соединение выполняется после применения условия where, то вы правы. Это должно быть достаточно легко узнать. Просто поместите индекс на groupid. - person Tim; 11.09.2011
comment
@Tim: Добавление встроенного представления, подобного этому, обычно не имеет никакого значения. Oracle может объединить встроенное представление или поместить предикаты во встроенное представление. Такое поведение не соответствует стандарту SQL и приводит к некоторым интересным проблемам преобразования данных, но обычно значительно повышает производительность. Как указывали другие, если вы напишете запрос простым способом, Oracle, скорее всего, перепишет его для быстрой работы. И если вам действительно нужно, чтобы Oracle выполнял действия в определенном порядке, вы должны использовать другие приемы или подсказки (например, добавить ROWNUM во встроенное представление). - person Jon Heller; 12.09.2011