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

я создал текстовый индекс Oracle следующим образом:

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_ids.

в идеале, я бы предпочел только поиск предметов с 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 строк (помимо того, что базе данных повезло и рано найти нужную строку.

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

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

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

тогда как я хочу:

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

4 ответов


Текст Oracle

1-Вы можете улучшить производительность, создав индекс контекста с помощью НА:

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 последовательных gets. Но с другой стороны, создание индекса CTXCAT заняло почти 11 минут и использовало 1,8 ГБ пространства.

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

многоколоночные индексы против индексных соединений

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

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

однако это не относится к текстовым индексам Oracle. Вы можете присоединиться к индексу контекста с индексом btree (a "точечный"?), но производительность плохая.


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

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


короткая версия: в этом нет необходимости. Оптимизатор запросов достаточно умен, чтобы решить, какой лучший способ выбрать ваши данные. Просто создайте индекс 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      

есть индекс btree на group_id. Чтобы убедиться, что индекс будет использоваться, запустите его как 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 делает все возможное для оптимизации оптимизатора, поэтому вам не нужно.


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

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

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

Если группа имеет нормальный индекс и не имеет низкого cardinality, получение этого набора должно быть быстрым. Тогда вы снова присоединитесь к этому набору с 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 для оптимизации соединения, а затем appy содержит предикат только для группы 43 строк.