1 2
И это снова о поиске в избранное  новое горячее всё    подписка   модер. 
От: akasoft 
Дата: 18.06.06 16:49
Оценка:6 (2)
Поиск информации в ЛБД Януса занимает дольше времени, чем поиск на сайте и переход к найденным сообщениям в Янусе. Это есть ненормально. И с этим надо что-то делать. Дальше рассмотрено одно из возможных решений.

Постановка проблемы


Поиск в Янусе реализован с помощью LIKE-запросов ко всей ЛБД целиком, что при большом количестве сообщений (уже при >300) выливается в длительные ожидания. В то же время, LIKE-поиск довольно "прост" в составлении поискового запроса.

Что хотелось бы от поиска?

  1. Существенного ускорения оного.
  2. Возможность прерывания хода поиска с выдачей промежуточных результатов.
  3. Подсветки искомых слов в теле сообщения, по примеру MS Document Explorer или HTML Help.
  4. Регистронезависимый поиск.
  5. Хотелось бы исключать из поиска символы разметки форматтера.
  6. Хотелось бы искать фрагменты со специальными символосочетаниями программистов — "=", "==", ";" и пр. операторами.

Всё это вполне возможно.

К тому же, в связи с выбором способа хранения тел сообщений в сжатом виде, от простого LIKE-поиска придётся отказаться. В пользу ещё более простого LIKE-поиска.

Поисковый лисапет движка


Как обычно, я начну втирать про построение собственного простейшего велосипеда поиска, и буду обещать, что он будет работать быстрее, нежели существующий.

Сначала про идею. Идея заключается в построении 2-х (двух) таблиц: словаря и списка вхождения слов.

dictionary {
int id, // первичный ключ
string word // одно слово, число либо оператор - всё то, что позволяется искать
}

wordlist {
int id, // первичный ключ
int wid, // ссылка на на слово (id из dictionary)
int mid, // ссылка на на сообщение (id из messages)
int count // количество повторений слова в сообщении, для статистики
}


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

Затем мы сможем воспользоваться обычным LIKE-поиском, только искать мы будем по гораздо компактной табллице словаря, а результат можем отбирать и показывать порциями. Это в совокупности позволит нам существенно ускорить поиск и получить возможность прервать его или посмотреть часть результатов.

Для того, чтобы иметь возможность искать по сообщению, его нужно подключить к словарю. Вначале в сообщении выделяем поисковые слова, затем для каждого слова (включая повторы) проверяем наличие его в словаре, и в случае отсутствия добавляем новое слово в словарь. Затем по имеющемуся id слова и mid сообщения проверяем наличие записи в списке вхождения слов, и опять же в случае наличия увеличиваем поле count на единичку, иначе добавляем новую запись с count == 1.

Можно оптимизировать эту процедуру, изначально объединив повторяющиеся слова в сообщении в один элемент списка — пару {слово, количество}, что позволит минимизировать количество запросов на обработку одного слова.

Так же полагаю, что будет полезно совместить поиск по телу сообщения с поиском по теме сообщения. Хотя, можно сделать отдельный словарь или флажок, но зачем?

Отделяем зёрна от плевел


Итак, у нас есть одно сообщение. Необходимо получить его набор поисковых слов. Пробельными принято считать собственно пробел, табуляцию, символы пунктуации и начало и конец сообщения. Кроме того, в сообщениях используется разметка в "[тег]" и "[/тег]", которую необходимо исключить из поиска. Но хотелось бы искать сообщения, где есть цитаты, замечания модераторов, URI и картинки (в том числе по самому URI для ссылок и картинок), фрагметны кода на выбранных языках и т.п.

Т.е. понятно, что появляются предопределённые псевдопоисковые слова-флажки, которые заранее определены в программе, заранее добавлены в словарь и при подключении сообщения к словарю мы смотрим наличие тегов оформления и заносим информацию в wordlist с учётом количества цитат, фрагментов кода и пр. Сюда же можно отнести и операторы во фрагментах кода, и иные метаслова, которые мы хотим позволить искать. Важно понимать, что, например, задав к поиску открывающию и закрывающую квадратные скобки, набрав "[ ]", мы не найдём сообщения с разметкой, а только сообщения, где эти символы встречаются не в контексте разметки.

Таким образом, наклёвывается порядок обработки сообщения. Инициализируем пустой список слов для сообщения. Анализируем тело сообщения на предмет наличия разметки, и добавляем в список слов сообщения найдённые виды разметки. Затем разметку исключаем, забивая её пробелами, за исключением "хитрого" тега url, где сохраняем всё то, что идёт после "[[url=" и до "]". Помним и о том, что двойная открывающая скобка "[[" отменяет действие тега. Все теги разметки идут как флажки в расширенных настройках поиска.

На втором этапе анализируем наличие символов пунктуации и операторов — точек, слешей, запятых, и т.п. и они тоже заносятся в список слов сообщения. Хотя, какие это слова. После чего эти символы также забиваются пробелами.

Наконец, на третьем этапе пробелы усекаются до одного и у нас наконец-то остаются собственно слова. Поскольку переносов мы не делаем, то и заморачиваться с дефисами мы тоже не будем. Для простоты дела. Собственно строим список обычных слов и их частоту повторения в сообщении.

Теперь мы готовы к подключению сообщения к словарю.

Подключаем словарь и ... отлучаем от него!


Для предопределённых слов разметки и пунктуации возможна оптимизация, т.к. искать такое слово в словаре не нужно, ибо оно там есть всегда. Получаем их id и подключаем. Обычные слова искать нужно. Да, слова все юникодные. Для каждого слова получаем его id, и добавляем в wordlist запись о нём.

Зачем подключается словарь? Тут просто всё, чтобы иметь возможность поиска.

Когда подключается словарь? Тут есть несколько подходов.

Во-первых, подключать можно сразу по окончании синхронизации. Это может быть как накладно, так и не очень. В сутки приходит 2-3 тысячи сообщений при полной подписке. Во-вторых, можно завести список неподключенных сообщений, и активировать подключение по команде пользователя или по расписанию. Само собой, по неподключенным сообщениям искать нельзя. В третьих, изначально у пользователя есть большущая ЛБД с кучей неподключенных сообщений. Это поле для работы, которую нужно тоже учесть наряду с новыми сообщениями. Ну и конечно, очень хочется, чтобы механизмус подключал сообщения в фоновом режиме во время простоя при просмотре-чтении пользователем других сообщений. В стиле "а Васька слушает, да ест".

Поэтому плавно вытекает добавление в messages поля indict

messages {
...
bool indict // true - подключено, false - нет
}


При получении нового сообщения в поле просто пишется false, и поисковый движок опознает и подключит такое сообщение. Просто? Да!

Но что делать, когда получено обновлённое сообщение? А если сообщение, не дай Б-г, подлежит удалению?

Нужен механизм отключения сообщений от словаря. Он весьма прост — достаточно удалить из списка записи с нужным mid:

DELETE FROM wordlist WHERE mid = @mid;


Таким образом, при удалении сообщения мы выполняем этот запрос и удаляем собственно сообщение, а при обновлении сообщения (модерирование) мы выполняем этот запрос и сбрасываем флаг messages.indict в false.

Go-o-o-o-ogle Ja-a-a-anus! Гавкус-гавкус!!!


Собственно, у нас есть словарь, механизм выделения слов и подключения сообщений к словарю, всё это работает параллельно и асинхронно, никому не мешает, а при необходимости пользователь может отключить поиск и съэкономить место на диске, или перестроить словарь заново.

"Но Холмс, как мы будем пользоваться этим богатством?"

А пользоваться мы будем по старому! С применением LIKE! В этом финт ушами.

Человек вводит поисковую строку "большая пьян харьков c#" и ставит галочку искать сообщения с картинками (тег img).

Движок обнаруживает 4 обычных слова ("большая", "пьян", "харьков", "c"), один диезик "#" и одно псевдослово. Рисуем запросину:

SELECT *
FROM dictionary
WHERE word LIKE "%большая%" OR word LIKE "%пьян%" OR word LIKE "%харьков%"
    OR word LIKE "%c%" OR word LIKE "#" OR word LIKE "[img]"


Получаем id слов. Здесь можно позволить пользователю нажать кнопочку Cancel.

Если ничего не найдено — радуем пользователя соответствующим сообщением.

Определяем количество сообщений, подпадающих под этот набор слов

SELECT Count(*) FROM wordlist WHERE wid IN (@wid) GROUP BY mid;


Опять Cancel, если что.

Делаем выборку последних по дате (свежих) 10 (десяти) сообщений (с учётом настройки количества для поиска) или всех, показываем, позволяем прервать процесс и сразу или в фоне догружаем остаток.

Смотрим


Конечно, хотелось бы встроить механизм выделения искомых слов в форматтер, но это можно сделать и не внедряясь в оный. В Янусе уже давно используется своя надстройка над форматтером. Поэтом добавляется ещё один span class в janus.css, где проставляется цвет символов и фон нашего выделения, ну и любые иные атрибуты на вкус. И затем поиском заменяются вхождения слов на <span class="hot">слово</span>.

Отдельно стоит отметить подсветку ссылок, если в URI найдено искомое, и аналогично картинок при тех же условиях. Тут надо сочинить по отдельному классу.


Кстати! Неплохо бы вообще оттенять ссылки цветом фона, а не только префиксом иконки. По моему, лучше смотрится.

Заключение


Вот мы и изобрели очередной велосипед. Я надеюсь, что всё понятно изложил. И мало у кого остались сомнения в возможности такой реализации поиска в Янусе. Был бы реструктуризатор, я бы уже попробовал. Но про реструктуризатор будет отдельная тема, ибо тесно связано это дело с выбором хранилища для сообщений Януса.

Если тут будет пробегать Влад, и его заинтересует это сообщение как возможный материал для журнала, я подумаю над этим. Хотя, конечно, хотелось бы вначале это реализовать в Янусе, ибо описание работающего велосипеда отличается от описания сферического коня в безвоздушном пространстве.

Ну и конечно нужно отметить, что АВК уже приводил ключевое слово "Lucene" из Апача, и даже уверял
Автор: AndrewVK
Дата: 13.06.06
, что есть работающий порт на .net.
... << RSDN@Home 1.2.0 alpha rev. 652>> SQL Express 2005
Re: И это снова о поиске в избранное  новое    модер. 
От: Lloyd 
Дата: 18.06.06 17:05
Здравствуйте, akasoft, Вы писали:

A>Поиск информации в ЛБД Януса занимает дольше времени, чем поиск на сайте и переход к найденным сообщениям в Янусе. Это есть ненормально. И с этим надо что-то делать. Дальше рассмотрено одно из возможных решений.


А вы вот это смотрели: Lucene.Net?
Re: И это снова о поиске в избранное  новое    модер. 
От: Petrovich_Alex 
Дата: 18.06.06 17:54
Здравствуйте, akasoft, Вы писали:

A>Поиск информации в ЛБД Януса занимает дольше времени, чем поиск на сайте и переход к найденным сообщениям в Янусе. Это есть ненормально. И с этим надо что-то делать. Дальше рассмотрено одно из возможных решений.



Cделай локальный http сервер для rsdn@home. (нужен http интерфейс для rsdn@home..)

к примеру:

http://locahost/rsdn/all/
http://locahost/rsdn/java/
http://locahost/rsdn/janus/

И всё... и весь поиск готов.

И каждый потом сможет чем угодно его индексировать.
Я вот могу гугло-декстопом...

А если дашь возможность еще и html интерфейс менять, или расширять сервер скриптами(питон,js,php и т.д.) то всё, ты бог...
а если нет, то и на этом будут "большое спасибо". (а интервейс и Greasemonkey можно подправить..)
... << RSDN@Home 1.1.4 stable rev. 510>>
Re: И это снова о поиске в избранное  новое    модер. 
От: AndrewVK модератор 
Дата: 19.06.06 09:56
Здравствуйте, akasoft, Вы писали:

A>Ну и конечно нужно отметить, что АВК уже приводил ключевое слово "Lucene" из Апача, и даже уверял
Автор: AndrewVK
Дата: 13.06.06
, что есть работающий порт на .net.


Так может для начала нужно было посмотреть, прежде чем выдумывать то же самое? Или у Lucene есть фатальный недостаток?
... << RSDN@Home 1.2.0 alpha rev. 642>>
Re[2]: И это снова о поиске в избранное  новое    модер. 
От: akasoft 
Дата: 19.06.06 14:01
Здравствуйте, AndrewVK, Вы писали:

AVK>Так может для начала нужно было посмотреть, прежде чем выдумывать то же самое?


А показать? Сслыку бы дал на то, что сам видел где.

То, что Ллойд выше написал — посмотрел, ему ответил.

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

AVK>Или у Lucene есть фатальный недостаток?


Ты же знаешь — такой недостаток есть у каждого ПО.
... << RSDN@Home 1.2.0 alpha rev. 652>> SQL Express 2005
Re[2]: И это снова о поиске в избранное  новое    модер. 
От: akasoft 
Дата: 19.06.06 14:01
Здравствуйте, Lloyd, Вы писали:

L>А вы вот это смотрели: Lucene.Net?


Посмотрел. Скачал 700 кил, перевёл автоматом в 2005 Студию, без ошибок. Скомпилировал и запустил примеры. Англоязычный текст ищет. При вводе русского слова (кирилицей на запрос Query: ) вылетает и ничего не ищет. А ещё юникод, блин.

Индекс по файлам строит быстро, и даже запоминает пути. Возможно, что есть возможность связать тексты и mid сообщений и скормить их индексатору.

Ты сам с этой штукой работал? Каковы впечатления? Поддержка кирилицы/юникода в данном конкретном порте?

Есть ли ещё где-нибудь описание принципов работы, кроме как на официальном сайте Lucene под Яву? Примеры там какие. Конечно, предпочтительны языки Шарп и русский.
... << RSDN@Home 1.2.0 alpha rev. 652>> SQL Express 2005
Re: И это снова о поиске в избранное  новое    модер. 
От: orange_ 
Дата: 13.08.06 19:06
Здравствуйте, akasoft, Вы писали:

<skip>

Очень даже интересно. Но вот воплощать такое некому и некогда.
Re[3]: И это снова о поиске в избранное  новое    модер. 
От: Igor Sukhov rsdn 
Дата: 13.11.06 22:12
Оценка:20 (3) +2
Здравствуйте, akasoft, Вы писали:

A>Здравствуйте, Lloyd, Вы писали:


L>>А вы вот это смотрели: Lucene.Net?


A>Посмотрел. Скачал 700 кил, перевёл автоматом в 2005 Студию, без ошибок. Скомпилировал и запустил примеры. Англоязычный текст ищет. При вводе русского слова (кирилицей на запрос Query: ) вылетает и ничего не ищет. А ещё юникод, блин.


Все ищет — только надо использовать RussianAnalyzer.

A>Индекс по файлам строит быстро, и даже запоминает пути. Возможно, что есть возможность связать тексты и mid сообщений и скормить их индексатору.


В общем, сделал я Lucent-based поиск для Януса — ищет очень быстро даже на ноутбуке в базе из 1.5 миллионов сообщений (450 мегабайт построенного индекса).
если интересно, могу запостить исходники.
... << RSDN@Home 1.2.0 alpha rev. 0>>
* thriving in a production environment *
Re[4]: И это снова о поиске в избранное  новое    модер. 
От: akasoft 
Дата: 14.11.06 10:01
Здравствуйте, Igor Sukhov, Вы писали:

IS>В общем, сделал я Lucent-based поиск для Януса — ищет очень быстро даже на ноутбуке в базе из 1.5 миллионов сообщений (450 мегабайт построенного индекса).

IS>если интересно, могу запостить исходники.

Ты его интегрировал в Янус, или отдельной утилитой сделал? Индекс где хранишь? А сообщения — из ЛБД берёшь? SQL Express или Акцесс?
... << RSDN@Home 1.2.0 alpha rev. 667>> SQLE 2005
олюцернивание в избранное  новое    модер. 
От: Igor Sukhov rsdn 
Дата: 14.11.06 20:34
Оценка:46 (4)
Здравствуйте, akasoft, Вы писали:

A>Здравствуйте, Igor Sukhov, Вы писали:


IS>>В общем, сделал я Lucent-based поиск для Януса — ищет очень быстро даже на ноутбуке в базе из 1.5 миллионов сообщений (450 мегабайт построенного индекса).

IS>>если интересно, могу запостить исходники.

A>Ты его интегрировал в Янус, или отдельной утилитой сделал? Индекс где хранишь? А сообщения — из ЛБД берёшь? SQL Express или Акцесс?


По порядку:

Поиск встроен в сам Янус, а точнее, старая реализация (powered by like % statement) поиска заменена на новую, гораздо более прогрессивную – понимающую русские словоформы, знающую про русские stop words и гораздо более скоростную. Смысла во внешнем поиске не вижу, так как после того как искомое сообщений найдены, часто требуется изучить всю тему, или ответить на вопрос. Очевидно, что из Януса это сделать намного проще.

Индексирование и сам поиск используют lucene.net версии 1.9.1. Исходники поискового движка взять на сайте lucene .net и обязательно убедится, что файлы в папке Analysys\RU\ проекта включены в компиляцию)

Индекс хранится в виде файлов, в специальной папочке. Если компьютер слабенький а сообщений много — папку с индексом удобно расположить рядом с файлами данных локальной базы сообщений, чтобы упростить процедуру backup-а, т.к. построение индекса с нуля может занять несколько часов. Другое дело, что если компьютер слабый а сообщений куча и вдруг у вас навернулась система и побились файлы БД – то нужно очищать карму. Вот так вот хранится индекс.

БД – понятно, что MS SQL, т.к. делал я все для себя. Реализация – новый метод в DatabaseManager и четыре совсем новых класса, один из которых – data accessor к таблице messages и,. Для Access надо сделать скопировать скопировать последний и выполнить следующее действие: replace”Sql” на “OleDb”.


Коротко, как эти 4 класса взаимодействуют с Янусом.

Класс MessageIndexer вычитывает порциями данные из БД и создает индекс из полей таблицы messages. Первичное индексирование производится по явному действию пользователя, новые же сообщения индексируются после каждой синхронизации.

Класс MessageSearcher ищет в ранее созданном индексе, вытягивает список идентификаторов сообщений, которые удовлетворяют условиям поиска. Список идентификаторов передается в DatabaseManager.GetSearchMessages3 – ну а далее все как было раньше.


Исходный код:

using System;
using System.Collections.Generic;
using System.Text;

using System.Data;
using System.Data.SqlClient;

namespace Rsdn.Janus.Search
{
    /// <summary>
    /// Класс содержит методы для получение данных об сообщениях из локальной базе сообщений..
    /// </summary>
    static class  MessageDataAccessor
    {
        static readonly string _connectionString = Config.Instance.ConnectionString;


        /// <summary>
        /// По указанному интервалу идентификаторов, считывает из БД данные из таблицы сообщений 
        /// и создает из этих данных список MessageSearchInfo объектов.
        /// </summary>
        /// <param name="firstId">Начальный Id.</param>
        /// <param name="lastId">Конечный Id.</param>
        /// <remarks>Список отсортирован по возрастанию идентификаторов сообщений.</remarks>
        /// <returns></returns>
        public static List<MessageSearchInfo> GetMessages(int firstId, int lastId)
        {
            List<MessageSearchInfo> result = new List<MessageSearchInfo>();

            using (SqlConnection connection = new SqlConnection(_connectionString))
            {
                string sqlText = "select * from [messages] where mid between @firstId and @lastId order by mid";


                using (SqlCommand command = new SqlCommand(sqlText, connection))
                {
                    command.CommandTimeout = 10000000;

                    command.Parameters.Add(new SqlParameter("@firstId", firstId));
                    command.Parameters.Add(new SqlParameter("@lastId", lastId));


                    connection.Open();

                    SqlDataReader sqlDataReader = command.ExecuteReader();


                    int iMid = sqlDataReader.GetOrdinal("mid");
                    int iAuthor = sqlDataReader.GetOrdinal("usernick");
                    int iAuthorId = sqlDataReader.GetOrdinal("uid");
                    int iSubject = sqlDataReader.GetOrdinal("subject");
                    int iText = sqlDataReader.GetOrdinal("message");
                    int iGid = sqlDataReader.GetOrdinal("gid");
                    int iDateTime = sqlDataReader.GetOrdinal("dte");
                    
                    while (sqlDataReader.Read())
                    {
                        MessageSearchInfo msi =
                        new MessageSearchInfo
                        (
                            sqlDataReader.GetInt32(iMid),

                            sqlDataReader.GetString(iAuthor),
                            sqlDataReader.GetInt32(iAuthorId),

                            sqlDataReader.GetString(iSubject),
                            sqlDataReader.IsDBNull(iText) ? "" : sqlDataReader.GetString(iText),

                            sqlDataReader.GetInt32(iGid),

                            sqlDataReader.GetDateTime(iDateTime)
                        );

                        result.Add(msi);
                    }

                }


            }


            return result;
        }

    


        /// <summary>
        /// Возвращает максимальный Id сообщения из тех что есть в локальной базе сообщений.
        /// </summary>
        /// <returns></returns>
        public static int GetMaxMessageId()
        {
            using (SqlConnection connection = new SqlConnection(_connectionString))
            {
                string sqlText = "select max(mid) from [messages]";


                using (SqlCommand command = new SqlCommand(sqlText, connection))
                {
                    connection.Open();

                    return (int)command.ExecuteScalar();
                }
            }
        }

        /// <summary>
        /// Возвращает количество сообщений в локальной базе сообщений.
        /// </summary>
        /// <returns></returns>
        public static int GetMessageCount()
        {
            using (SqlConnection connection = new SqlConnection(_connectionString))
            {
                string sqlText = "select count(mid) from [messages]";


                using (SqlCommand command = new SqlCommand(sqlText, connection))
                {
                    connection.Open();

                    return (int)command.ExecuteScalar();
                }
            }
        }

    }
}



using System;
using System.Collections.Generic;
using System.ComponentModel;

using System.Text;

using Lucene.Net.Index;
using Lucene.Net.Documents;
using Lucene.Net.Analysis;
using Lucene.Net.Analysis.Standard;
using Lucene.Net.Analysis.RU;
using Lucene.Net.Search;
using Lucene.Net.QueryParsers;

namespace Rsdn.Janus.Search
{
    /// <summary>
    /// Класс содержит набор методов для создания и обновления индекса сообщений.
    /// </summary>
    class MessageIndexer
    {
        private string _indexPath;


        /// <summary>
        /// Конструктор - инициализируем путем до папки где хранится, или будет хранится, индекс.
        /// </summary>
        /// <param name="indexPath"></param>
        public MessageIndexer(string indexPath)
        {
            _indexPath = indexPath;
        }

        /// <summary>
        /// На это событие подписываемся, чтобы отслеживать прогресс при индексации большого
        /// количества сообщений - например при первичной индексации базы.
        /// 
        /// При обработке этого события имеется возможность остановить процесс индексации.
        /// </summary>
        public event CancelEventHandler Progress;
        
        /// <summary>
        /// Обновляем индекс, добавляя в него новые, еще не проиндексированные, сообщения.
        /// </summary>
        /// <returns>Количество новых проиндексированных сообщений.</returns>
        public int UpdateIndex()
        {
            int maxMidInDb = MessageDataAccessor.GetMaxMessageId();
        
            const int step = 9999;

            int firstId         = GetMaxMidIndexer() + 1;
            int lastId          = firstId + step;
            int indexedCount    = 0;

            while (firstId <= maxMidInDb)
            {
                List<MessageSearchInfo> portion = MessageDataAccessor.GetMessages(firstId, lastId);

                // Do index
                //
                Index(portion);

                CancelEventArgs ea = new CancelEventArgs();

                if (null != Progress)
                {
                    Progress(this, ea);
                }               

                firstId         = lastId + 1;
                lastId          = firstId + step;
                indexedCount    += portion.Count;

                if (ea.Cancel)
                {
                    break;
                }
            }

            return indexedCount;
        }

        
        /// <summary>
        /// Индексируем данные списка MessageSearchInfo объектов.
        /// </summary>
        /// <param name="listMessages"></param>
        private void Index(List<MessageSearchInfo> listMessages)
        {
            IndexWriter writer;

            try
            {
                writer = new IndexWriter(_indexPath, new RussianAnalyzer(), false);
            }
            catch
            {
                writer = new IndexWriter(_indexPath, new RussianAnalyzer(), true);
            }

            foreach (MessageSearchInfo msi in listMessages)
            {
                writer.AddDocument( CreateDocument(msi) );
            }

            writer.Close();
        }

     
        /// <summary>
        /// Создаем Lucene-кий документ (объект хранящий поля индексируемой сущности).
        /// </summary>
        /// <param name="msi"></param>
        /// <returns>Созданный документ.</returns>
        private static Document CreateDocument(MessageSearchInfo msi)
        {
            Document doc = new Document();

            doc.Add(new Field("id", msi.Id.ToString(), true, true, false));

            doc.Add(Field.Text("authorNickname", msi.AuthorNickname));
            doc.Add(new Field("authorId", msi.AuthorId.ToString(), true, true, false));

            doc.Add(Field.Text("subject", msi.Subject));

            // DO NOT STORE message text in the index.
            //
            doc.Add(new Field("text", msi.Text, false, true, true));
            doc.Add(new Field("forumId", msi.ForumId.ToString(), true, true, false));

            doc.Add(new Field("creationDateTime", DateField.DateToString(msi.CreationDateTime), Field.Store.YES, Field.Index.UN_TOKENIZED));

            return doc;
        }


        /// <summary>
        /// Возвращаем количество документов в индексе
        /// (проиндексированных за все время сообщений).
        /// </summary>
        /// <returns></returns>
        public int GetDocumentCount()
        {
            try
            {
                IndexSearcher searcher = new IndexSearcher(_indexPath);

                int documentCount = searcher.MaxDoc();
              
                searcher.Close();

                return documentCount;
            }
            catch
            {
                return 0;
            }
        }


        /// <summary>
        /// Возвращает максимальный Id сообщения находящегося в индексе.
        /// </summary>
        /// <returns></returns>
        private int GetMaxMidIndexer()
        {
            try
            {
                IndexSearcher searcher = new IndexSearcher(_indexPath);

                int maxDoc = searcher.MaxDoc();
                int maxMid = 0;

                if (maxDoc > 0)
                {
                    Document maxDocument = searcher.Doc(maxDoc - 1);

                    maxMid = int.Parse(maxDocument.Get("id"));
                }

                searcher.Close();

                return maxMid;
            }
            catch
            {
                return 0;
            }
        }

       
    }
}



using System;
using System.Collections.Generic;
using System.Text;

using Lucene.Net.Index;
using Lucene.Net.Documents;
using Lucene.Net.Analysis;
using Lucene.Net.Analysis.Standard;
using Lucene.Net.Analysis.RU;
using Lucene.Net.Search;
using Lucene.Net.QueryParsers;

namespace Rsdn.Janus.Search
{
    /// <summary>
    /// Класс содержит набор методов для поиска использующего Lucent движок.
    /// </summary>
    class MessageSearcher
    {
        private readonly string _indexPath;

        /// <summary>
        /// Конструктор - инициализируем путем до папки где хранится индекс.
        /// </summary>
        /// <param name="indexPath"></param>
        public MessageSearcher(string indexPath)
        {
            _indexPath = indexPath;
        }


        /// <summary>
        /// Ищем сообщения по заданным параметрам и возвращаем список идентификаторов
        /// сообщений.
        /// </summary>
        /// <param name="query">Текст который ищем. См. формат запросов на сайте lucene.</param>
        /// <param name="searchText">Искать ли в тексте сообщений.</param>
        /// <param name="searchSubject">Искать ли в заголовках сообщений.</param>
        /// <param name="searchAuthorNickname">Искать ли в никах авторов сообщений.</param>
        /// <param name="forumId">Если -1 - искать во всех форумах, иначе искать сообщения только в указанном форуме.</param>
        /// <returns>Спиков идентификаторов сообщений (идентификаторы - строки, не числа!) удовлетворяющих условиям поиска.</returns>
        public List<string> Search(
                                    string query,

                                    bool    searchText,
                                    bool    searchSubject,
                                    bool    searchAuthorNickname,

                                    int     forumId
                                    )
        {
            List<string> result = new List<string>();

            BooleanQuery bq = new BooleanQuery();

            if (searchText)
            {
                bq.Add(QueryParser.Parse(query, "text", new RussianAnalyzer()), false, false);
            }

            if (searchSubject)
            {
                bq.Add(QueryParser.Parse(query, "subject", new RussianAnalyzer()), false, false);
            }

            if (searchAuthorNickname)
            {
                bq.Add(QueryParser.Parse(query, "authorNickname", new RussianAnalyzer()), false, false);
            }

            Filter filter = null;

       
            if (-1 != forumId)
            {
                TermQuery tq = new TermQuery(new Term("forumId", forumId.ToString()));

                filter = new QueryFilter(tq);
            }


            IndexSearcher searcher = new IndexSearcher(_indexPath);

            Hits hits = (filter != null) ? searcher.Search(bq, filter) : searcher.Search(bq);


            for (int i = 0; i < hits.Length(); i++)
            {
                Document doc = hits.Doc(i);

                result.Add(doc.Get("id"));
            }

            searcher.Close();

            return result;
        }

    }
}



using System;
using System.Collections.Generic;
using System.Text;

namespace Rsdn.Janus.Search
{
    /// <summary>
    /// DTO с полями сообщения (табл. messages в БД) из которых будем строить индекс.
    /// </summary>
    class MessageSearchInfo
    {
        /// <summary>
        /// Id сообщения (столбец uid в БД)
        /// </summary>
        public int Id;

        /// <summary>
        /// Ник автора сообщения (столбец userNick в БД)
        /// </summary>
        public string AuthorNickname;

        /// <summary>
        /// Id автора сообщения (столбец uid в БД)
        /// </summary>
        public int AuthorId;

        /// <summary>
        /// Заголовок сообщения (столбец subj в БД)
        /// </summary>
        public string Subject;

        /// <summary>
        /// Текст сообщения (столбец text в БД)
        /// </summary>
        public string Text;

        /// <summary>
        /// Id форума (столбец gid в БД)
        /// </summary>
        public int ForumId;

        /// <summary>
        /// Время создания сообщения (столбец dte в БД)
        /// </summary>
        public DateTime CreationDateTime;

        public MessageSearchInfo
            (
                int id,

                string authorNickname,
                int authorId,
            
                string subject,
                string text,

                int forumId,
                DateTime creationDateTime


            )
        {
            this.Id = id;

            this.AuthorNickname = authorNickname;
            this.AuthorId = authorId;


            this.Subject = subject;
            this.Text = text;

            this.ForumId = forumId;

            this.CreationDateTime = creationDateTime;
        }

    }
}


    public static List<LinearTreeMsg> GetSearchMessages3(
            List<string> idList,
            bool SearchInMarked,
            bool SearchInMyMessages,
            bool SearchAnyWords,
            bool SearchInQuestions,
            DateTime from,
            DateTime to,
            SortType sortType)
        {
            ArrayList sqlParams = new ArrayList();

            string strWhere = String.Empty;

            #region Обработка "моих сообщений"

            if (SearchInMyMessages)
            {
                if (strWhere.Trim().Length > 0)
                    strWhere += " AND ";

                strWhere += "([uid] = @uid)";
                sqlParams.Add("@uid"); sqlParams.Add(Config.Instance.SelfId);
            }

            #endregion

            #region Обработка вопросов

            if (SearchInQuestions)
            {
                if (strWhere.Trim().Length > 0)
                    strWhere += " AND ";

                strWhere += "([tid] = 0)";
            }

            #endregion

            #region Обработка пометок пользователя

            if (SearchInMarked)
            {
                if (strWhere.Trim().Length > 0)
                    strWhere += " AND ";

                strWhere += "([ismarked] = " + SqlBuilder.True() + ")";
            }

            #endregion

            #region Обработка дат - from, to

            if (from.Ticks != 0)
            {
                // Сбросить время, оставить только дату
                from = new DateTime(from.Year, from.Month, from.Day);

                if (strWhere.Trim().Length > 0)
                    strWhere += " AND ";

                strWhere += "([dte] >= @fromdate)";
                sqlParams.Add("@fromdate"); sqlParams.Add(from);
            }

            if (to.Ticks != 0)
            {
                // Сбросить время, оставить только дату
                to = new DateTime(to.Year, to.Month, to.Day);

                if (strWhere.Trim().Length > 0)
                    strWhere += " AND ";

                strWhere += "([dte] <= @todate)";
                sqlParams.Add("@todate"); sqlParams.Add(to);
            }

            #endregion

   
            StringBuilder sb = new StringBuilder(10 * idList.Count);

            foreach (string messageId in idList)
            {
                sb.Append(messageId).Append(",");
            }

            sb.Remove(sb.Length - 1, 1);

             if (strWhere.Trim().Length > 0)
             {
                    strWhere += " AND ";
             }


             strWhere += String.Format("mid in({0})", sb.ToString());


            string commandText = string.Format(@"
                SELECT
                    [mid],
                    [gid],
                    [tid],
                    [pid],
                    [dte],
                    [uid],
                    [usernick],
                    [uclass],
                    [subject],
                    [isread],
                    [ismarked],
                    (SELECT Sum([rate]*[rby]) FROM [rating] WHERE [mid]=m.[mid] AND [rate] > 0) AS [rate],
                    (SELECT COUNT(*) FROM [rating] WHERE [mid]=m.[mid] AND [rate] = {0})    AS [smiles],
                    (SELECT COUNT(*) FROM [rating] WHERE [mid]=m.[mid] AND [rate] = {1})    AS [agree],
                    (SELECT COUNT(*) FROM [rating] WHERE [mid]=m.[mid] AND [rate] = {2})    AS [p1rate],
                    (SELECT COUNT(*) FROM [rating] WHERE [mid]=m.[mid] AND [rate] = {3})    AS [disagree],
                    (SELECT Sum([rate]*[rby]) FROM [rating] WHERE [tid]=m.[mid] AND [rate] > 0) AS [themerate],
                    (SELECT {4} FROM [subscribed_forums] WHERE [id]=m.[gid])              AS [Forum]
                FROM [messages] m
                {5}
                {6}",
                (int)MessageRates.Smile,
                (int)MessageRates.Agree,
                (int)MessageRates.Plus1,
                (int)MessageRates.DisAgree,
                Config.Instance.ForumDisplayConfig.ShowFullForumNames ? "[descript]" : "[name]",
                strWhere.Length != 0 ? "WHERE " + strWhere : String.Empty,
                DataSorter.SortCriteria(sortType, SortType.ByIdDesc));

            return JanusDB.ExecuteList<LinearTreeMsg>(commandText, sqlParams.ToArray());
        }



Ну, вот почти и все. Хотя нет – вот что надо еще сделать, не забыл конечно добавить Lucene.Net.Dll в references проекта Janus:

Изменения в SearchDummyForm.cs (метод btnSearch_Click):

            using (ProgressFormManager pfm = new ProgressFormManager(
                    SR.Search.Searching))
            {
                List<LinearTreeMsg> res = new List<LinearTreeMsg>();

                if (searchText.Length == 0)
                {
                    res = DatabaseManager.GetSearchMessages2(
                                         (Config.Instance.AdvancedSearch ? Config.Instance.SearchForumId : -1),
                                         Config.Instance.SearchText,
                                         Config.Instance.SearchInText,
                                         Config.Instance.SearchInSubject,
                                         Config.Instance.SearchAuthor,
                                         Config.Instance.SearchInMarked,
                                         Config.Instance.SearchInMyMessages,
                                         Config.Instance.SearchAnyWord,
                                         Config.Instance.SearchInQuestions,
                                         searchFromDate.Checked ? searchFromDate.Value : new DateTime(0),
                                         searchToDate.Checked ? searchToDate.Value : new DateTime(0),
                                         Config.Instance.SearchSortCriteria);
                }
                else
                {
                    MessageSearcher searcher = new MessageSearcher(_indexPath);



                    List<string> result = searcher.Search(Config.Instance.SearchText.Trim(),
                                                            Config.Instance.SearchInText,
                                                            Config.Instance.SearchInSubject,
                                                            Config.Instance.SearchAuthor,
                                                            (Config.Instance.AdvancedSearch ? Config.Instance.SearchForumId : -1));

                    if (result.Count > 0)
                    {
                        res = DatabaseManager.GetSearchMessages3(
                                                                result,
                                                                Config.Instance.SearchInMarked,
                                                                Config.Instance.SearchInMyMessages,
                                                                Config.Instance.SearchAnyWord,
                                                                Config.Instance.SearchInQuestions,
                                                                searchFromDate.Checked ? searchFromDate.Value : new DateTime(0),
                                                                searchToDate.Checked ? searchToDate.Value : new DateTime(0),
                                                                Config.Instance.SearchSortCriteria);
                    }
                }



                if (searchInOverquoting.Checked)
                    FilterOverquoting(res);



Изменения в Synchronizer.cs (метод Sync)

                if (ApplicationManager.Instance.OutboxManager.DownloadTopics.Count > 0)
                {
                    ApplicationManager.Instance.Logger.LogInfo(SR.Sync.DownloadTopics);
                    JanusTopicRequest topicRequest = PrepareTopicRequest();
                    JanusTopicResponse topicResponse = GetTopicResponse(svc, topicRequest);
                    ProcessTopicResponse(topicResponse);
                }

                // Update index
                //
                MessageIndexer messageIndexer = new MessageIndexer(this._indexPath);

                string indexedMsg = String.Format("Проиндексировано {0} сообщений.", messageIndexer.UpdateIndex());

                ApplicationManager.Instance.Logger.Log(indexedMsg);


                OnSyncFinished();



Не смотрите на _indexPath — это обман зрения, на самом деле это должно называться Config.Instance.SearchIndexPath

Ну и напоследок — known features:

1. Начальное создание индекса – это долгий процесс, многие эстеты, в том числе считают что для этого должно иметь отдельный пункт меню, нарошный диалог с индиктором прогресса и кнопку – чтобы превать потенциально длительную операцию. Я, сумел подавить свою тягу к прекрасному. Если вы не сумеете – обратите внимание на MessageDataAccessor.GetMessageCount, MessageIndexer.Progress и Progress.GetDocumentCount

2. Если сообщение удаляют – то ссылка на него отстается в индексе. Это проблема тех кто пишет такие сообщения – я не виноват.

3. Если сообщение исправляют или переносят в другой форум (т.е. меняется или текст, заголовок, ник автора, или id форума) – то индекс в данном случае, также не обновляется. Простое решение – это искать сразу во всех форумах и не искать те слова, которые обычно удаляют модераторы. Более сложное решение – переделать логику индексирования – т.е. передавать в нее id только что полученных сообщений и в случае если в индексе уже есть сообщения с таким id – обновлять поля (см. Document.RemoveFields & Document.AddField). Экзотическое решение — это приехать обратно в Сидней, переписать БД и папку с индексом на RAID массив, удалить файлы индекса и вызвать синхронизацию чтобы пересоздать индекс с нуля. Но это конечно на любителя



Писалось в один проход, без рефакторинга — в коде возможно наличие рудиментов.
... << RSDN@Home 1.2.0 alpha rev. 0>>
* thriving in a production environment *
Re: олюцернивание в избранное  новое    модер. 
От: Holms 
Дата: 14.11.06 23:00
Оценка: +1
Здравствуйте, Igor Sukhov, Вы писали:

интересно, а можно получить вашу версию простым смертным?
Спасибо
... << RSDN@Home 1.1.4 stable SR1 rev. 568>>
The life is relative and reversible.
Re[2]: олюцернивание в избранное  новое    модер. 
От: Igor Sukhov rsdn 
Дата: 15.11.06 16:55
Здравствуйте, Holms, Вы писали:

H>интересно, а можно получить вашу версию простым смертным?

H>Спасибо

Хороший вопрос. А что ты используешь в качестве базы для Януса – если MSSQL,
то все что нужно — это скачать последние исходники Януса (используйте поиск
чтобы найти ссылку =), добавить мои изменения в проект и пересобрать его еще раз.

Все что для этого нужно — VS2005 и 10 минут времени.
... << RSDN@Home 1.2.0 alpha rev. 0>>
* thriving in a production environment *
Re: олюцернивание в избранное  новое    модер. 
От: akasoft 
Дата: 15.11.06 19:41
Здравствуйте, Igor Sukhov, Вы писали:

Как я понимаю, все поля в Люцене должны быть текстовыми, в т.ч. числовые. Строки должны быть токенизированы, если мы хотим искать по словам, а если не хотим, то нет. А что даёт сохранение поля в индексе?

В твоём примере id сохраняется, текст нет, см. выделенное ниже. Если не сохранять, то типа экономия места? А можно ли при этом использовать фильтры? Раз Люцена так хорошо ищет, так может загнать в её документ все поисковые поля, условия поиска задавать фильтрами, а в исходном коде оставить только выборку по найденным mid.

        private static Document CreateDocument(MessageSearchInfo msi)
        {
            Document doc = new Document();

            doc.Add(new Field("id", msi.Id.ToString(), true, true, false));

            doc.Add(Field.Text("authorNickname", msi.AuthorNickname));
            doc.Add(new Field("authorId", msi.AuthorId.ToString(), true, true, false));

            doc.Add(Field.Text("subject", msi.Subject));

            // DO NOT STORE message text in the index.
            //
            doc.Add(new Field("text", msi.Text, false, true, true));
            doc.Add(new Field("forumId", msi.ForumId.ToString(), true, true, false));

            doc.Add(new Field("creationDateTime", DateField.DateToString(msi.CreationDateTime), Field.Store.YES, Field.Index.UN_TOKENIZED));

            return doc;
        }


Хм, что-то у меня последнее время сильное желание вытащить тексты сообщений из ЛБД, оставив там только индекс. Например, последней идеей было таки вытащить тела сообщений в текстовые файлы вида <mid>.txt, но их засунуть в RAR- или ZIP-архив. Экономия места, простота доступа. а ЛБД оставить только "шапки".

Это я к тому, что если Люцена позволяет хранить поля, в т.ч. текст сообщений, то может в ней и хранить. Хотя, тела она не сжимает, эти её индексы из демки ещё потом в 2 раза пакуются...
... << RSDN@Home 1.2.0 alpha rev. 666>> SQL Express 2005
Re[3]: олюцернивание в избранное  новое    модер. 
От: Holms 
Дата: 15.11.06 21:38
Здравствуйте, Igor Sukhov, Вы писали:

IS>Хороший вопрос. А что ты используешь в качестве базы для Януса – если MSSQL,

IS>то все что нужно — это скачать последние исходники Януса (используйте поиск
IS>чтобы найти ссылку =), добавить мои изменения в проект и пересобрать его еще раз.

IS>Все что для этого нужно — VS2005 и 10 минут времени.

использую то что идет в поставке с Янусом, т.е. Акцес.
Под простым смертным я имел ввиду тех к кого нету VS2005, ведь можно же выложить где-то саму програму и хелп для настройки SQL
Спасибо
... << RSDN@Home 1.1.4 stable SR1 rev. 568>>
The life is relative and reversible.
Re[2]: олюцернивание в избранное  новое    модер. 
От: Igor Sukhov rsdn 
Дата: 15.11.06 23:27
Здравствуйте, akasoft, Вы писали:

A>Здравствуйте, Igor Sukhov, Вы писали:


A>Как я понимаю, все поля в Люцене должны быть текстовыми, в т.ч. числовые. Строки должны быть токенизированы, если мы хотим искать по словам, а если не хотим, то нет. А что даёт сохранение поля в индексе?


Да — поля надо перевести в строки. Если поле сохранить — его можно будет получить из Document в оригинальном виде,
т.е. если не экономить на размере индекса, можно сформировать dataset результатов, не обращаясь к БД.

A>В твоём примере id сохраняется, текст нет, см. выделенное ниже. Если не сохранять, то типа экономия места? А можно ли при этом использовать фильтры? Раз Люцена так хорошо ищет, так может загнать в её документ все поисковые поля, условия поиска задавать фильтрами, а в исходном коде оставить только выборку по найденным mid.


Как я написал — у меня была такая идея. Но — во первых это сложнее в реализации, а во вторых у меня есть смутные подозрания что на индексе
в 4ГБ люцен может медленнее раза в 1.5 чем на индексе объемом в 10 раз меньше и как следствие медленнне чем текущая реализация (Люцене+БД).

Сомнения были вызваны моими экспериментами на фильтрами — я так не понял, как сделать фильтр (наследник Filter)
фильтрующий по более чем одному условию — нашел что надо сделать ChainedFilter — но его в .NET реализации нету.

Но это не самое страшное — узкое место это фильтрация по временному интервалу. Документация перебивает саму себя,
предлагая использовать то DateFilter то RangeFilter, ни то ни другое — естественно ни работает — или отфильтровывало
все или не фильтровало ничего . В news-ах есть множество сообщений про то что поиск по промежутку дат работает
чрезвычайно медленно, хотя я все таки сделал такую фильтрацию через Query — но результатов так и не дождался.
Есть подозрения на то что если у дат прибить временную составляющую — все будет работать быстрее, но мне кажется
это тот случай, когда орешек крепче тубуса микроскопа. Тем более что под рукой есть молоток — сиквел.

Так что авторам Люцене есть пока что доводить до ума и на их месте я бы начал с документации и нормальных примеров,
но видимо портировать юнит-тесты им интереснее

A>
A>        private static Document CreateDocument(MessageSearchInfo msi)
A>        {
A>            Document doc = new Document();

A>            doc.Add(new Field("id", msi.Id.ToString(), true, true, false));

A>            doc.Add(Field.Text("authorNickname", msi.AuthorNickname));
A>            doc.Add(new Field("authorId", msi.AuthorId.ToString(), true, true, false));

A>            doc.Add(Field.Text("subject", msi.Subject));

A>            // DO NOT STORE message text in the index.
A>            //
A>            doc.Add(new Field("text", msi.Text, false, true, true));
A>            doc.Add(new Field("forumId", msi.ForumId.ToString(), true, true, false));

A>            doc.Add(new Field("creationDateTime", DateField.DateToString(msi.CreationDateTime), Field.Store.YES, Field.Index.UN_TOKENIZED));

A>            return doc;
A>        }
A>


A>Хм, что-то у меня последнее время сильное желание вытащить тексты сообщений из ЛБД, оставив там только индекс. Например, последней идеей было таки вытащить тела сообщений в текстовые файлы вида <mid>.txt, но их засунуть в RAR- или ZIP-архив. Экономия места, простота доступа. а ЛБД оставить только "шапки".


В чем выражается простота доступа? Не говоря уже о том, что приложение может слететь только из за того, что файл был случайно открыт, удален, скопирован, и так далее. Я не вижу простоты — вижу работу ручками с файлами , вижу пустой catch , вижу тормоза ... чур меня

A>Это я к тому, что если Люцена позволяет хранить поля, в т.ч. текст сообщений, то может в ней и хранить. Хотя, тела она не сжимает, эти её индексы из демки ещё потом в 2 раза пакуются...

Возможность сохранить поля непосредственно в Люцене дана нам свыше как оптимизация и ей не следует злоупотреблять.
... << RSDN@Home 1.2.0 alpha rev. 0>>
* thriving in a production environment *
Re[4]: олюцернивание в избранное  новое    модер. 
От: Igor Sukhov rsdn 
Дата: 15.11.06 23:27
Здравствуйте, Holms, Вы писали:

H>Здравствуйте, Igor Sukhov, Вы писали:


IS>>Хороший вопрос. А что ты используешь в качестве базы для Януса – если MSSQL,

IS>>то все что нужно — это скачать последние исходники Януса (используйте поиск
IS>>чтобы найти ссылку =), добавить мои изменения в проект и пересобрать его еще раз.

IS>>Все что для этого нужно — VS2005 и 10 минут времени.

H>использую то что идет в поставке с Янусом, т.е. Акцес.
H>Под простым смертным я имел ввиду тех к кого нету VS2005, ведь можно же выложить где-то саму програму и хелп для настройки SQL
H>Спасибо

Я бы мог поделиться своим exe-ник, но у меня в нем есть несколько хаккерских модификаций которые будут работать только на моем ноутбуке.
Поэтому выкладывать не буду. Вот если akasoft подружит поиск с Access-ом, думаю можно будет выложить на сайте.
... << RSDN@Home 1.2.0 alpha rev. 0>>
* thriving in a production environment *
Re[5]: олюцернивание в избранное  новое    модер. 
От: Holms 
Дата: 16.11.06 01:54
Здравствуйте, Igor Sukhov, Вы писали:

IS>Я бы мог поделиться своим exe-ник, но у меня в нем есть несколько хаккерских модификаций которые будут работать только на моем ноутбуке.

IS>Поэтому выкладывать не буду. Вот если akasoft подружит поиск с Access-ом, думаю можно будет выложить на сайте.
ок, ясно, будем ждать
... << RSDN@Home 1.1.4 stable SR1 rev. 568>>
The life is relative and reversible.
Re[2]: олюцернивание в избранное  новое    модер. 
От: AndrewVK модератор 
Дата: 16.11.06 09:16
Здравствуйте, akasoft, Вы писали:

A>А что даёт сохранение поля в индексе?


Это так называемые index includes. Позволяет иметь часть данных без доболнительного обращения к источнику данных (это практически всегда лишнее дерганье башкой диска).

A>Это я к тому, что если Люцена позволяет хранить поля, в т.ч. текст сообщений, то может в ней и хранить.


Ага. И при порче или перестроении индекса терять все данные .
... << RSDN@Home 1.2.0 alpha rev. 642>>
Re[3]: олюцернивание в избранное  новое    модер. 
От: akasoft 
Дата: 16.11.06 10:51
Здравствуйте, AndrewVK, Вы писали:

AVK>Это так называемые index includes. Позволяет иметь часть данных без доболнительного обращения к источнику данных (это практически всегда лишнее дерганье башкой диска).


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

AVK>Ага. И при порче или перестроении индекса терять все данные .


Ага, можно подумать, что в других случаях при порче есть выбор.
... << RSDN@Home 1.2.0 alpha rev. 667>> SQLE 2005
Re[3]: олюцернивание в избранное  новое    модер. 
От: akasoft 
Дата: 16.11.06 10:53
Здравствуйте, Igor Sukhov, Вы писали:

В примере с поиском файлов дата вообще переводится в некое подобие unix timestamp, и уже по этому числу в строковом представлении фильтруется. М.б. имеет смысл поступить так же?
... << RSDN@Home 1.2.0 alpha rev. 667>> SQLE 2005
1 2