Поиск в базе данных для похожих строк [дубликат]

По вопросу «что мне делать с этим» может быть много ответов.

Более «формальный» способ предотвращения таких ошибок при разработке применяя дизайн по контракту в вашем коде. Это означает, что при разработке вы должны установить инварианты класса и / или даже предпосылки для функции и .

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

Таким образом, вы можете избежать случаев NullReferenceException, которые являются результатом нарушения установленных ограничений. Например, если вы используете свойство объекта X в классе, а затем попытаетесь вызвать один из его методов, а X имеет нулевое значение, то это приведет к NullReferenceException:

public X { get; set; }

public void InvokeX()
{
    X.DoSomething(); // if X value is null, you will get a NullReferenceException
}

Но если вы установите «свойство X никогда не должно иметь нулевого значения» в качестве предпосылки для метода, вы можете предотвратить описанный ранее сценарий:

//Using code contracts:
[ContractInvariantMethod]
protected void ObjectInvariant () 
{
    Contract.Invariant ( X != null );
    //...
}

По этой причине Код Контракт существует для приложений .NET.

В качестве альтернативы дизайн по контракту может быть применен с использованием утверждений .

ОБНОВЛЕНИЕ: Стоит отметить, что этот термин был придуман Бертраном Майером в связи с его дизайном языка программирования Эйфеля .

65
задан OMG Ponies 6 May 2011 в 00:46
поделиться

3 ответа

Я выяснил, что расстояние Левенштейна может быть хорошим, когда вы ищете полную строку против другой полной строки, но когда вы ищете ключевые слова в строке, этот метод не возвращает (иногда) желаемые результаты. Кроме того, функция SOUNDEX не подходит для языков, отличных от английского, поэтому она весьма ограничена. Вы можете уйти с LIKE, но это действительно для базовых поисков. Вы можете захотеть взглянуть на другие методы поиска того, чего вы хотите достичь. Например:

Вы можете использовать Lucene в качестве базы поиска для ваших проектов. Он реализован на большинстве основных языков программирования, и он будет довольно быстрым и универсальным. Этот метод, вероятно, лучший, поскольку он не только ищет подстроки, но и транспонирование, префиксы и суффиксы букв (все вместе). Тем не менее, вам нужно сохранить отдельный индекс (используя CRON для обновления его из независимого скрипта раз в то время как работает хотя).

Или, если вы хотите решение MySQL, полнотекстовая функциональность довольно хороша, и, конечно, быстрее, чем хранимая процедура. Если ваши таблицы не являются MyISAM, вы можете создать временную таблицу, а затем выполнить полнотекстовый поиск:

CREATE TABLE IF NOT EXISTS `tests`.`data_table` (
  `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
  `title` varchar(2000) CHARACTER SET latin1 NOT NULL,
  `description` text CHARACTER SET latin1 NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB  DEFAULT CHARSET=utf8 COLLATE=utf8_bin AUTO_INCREMENT=1 ;

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

** ПРИМЕЧАНИЕ **: тип столбца должен быть latin1_bin, чтобы выполнить поиск с учетом регистра, а не регистр, нечувствительный к latin1. Для строк unicode я рекомендовал бы utf8_bin для чувствительности к регистру и utf8_general_ci для поиска без учета регистра.

DROP TABLE IF EXISTS `tests`.`data_table_temp`;
CREATE TEMPORARY TABLE `tests`.`data_table_temp`
   SELECT * FROM `tests`.`data_table`;

ALTER TABLE `tests`.`data_table_temp`  ENGINE = MYISAM;

ALTER TABLE `tests`.`data_table_temp` ADD FULLTEXT `FTK_title_description` (
  `title` ,
  `description`
);

SELECT *,
       MATCH (`title`,`description`)
       AGAINST ('+so* +nullam lorem' IN BOOLEAN MODE) as `score`
  FROM `tests`.`data_table_temp`
 WHERE MATCH (`title`,`description`)
       AGAINST ('+so* +nullam lorem' IN BOOLEAN MODE)
 ORDER BY `score` DESC;

DROP TABLE `tests`.`data_table_temp`;

Подробнее об этом читайте с справочной страницы MySQL API

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

** ОБНОВЛЕНИЕ **

Использование Lucene для в вашем поиске вам просто нужно создать задание cron (все веб-хосты имеют эту «функцию»), где это задание будет просто исполнять скрипт PHP (ig «cd / path / to / script; php searchindexer.php»), который будет обновить индексы. Причина в том, что индексирование тысяч «документов» (строк, данных и т. Д.) Может занять несколько секунд, даже минут, но это необходимо для того, чтобы все поисковые запросы выполнялись как можно быстрее. Поэтому вам может потребоваться создать задание задержки, которое будет выполняться сервером. Это может быть на ночь, или в следующий час, это зависит от вас. PHP-скрипт должен выглядеть примерно так:

$indexer = Zend_Search_Lucene::create('/path/to/lucene/data');

Zend_Search_Lucene_Analysis_Analyzer::setDefault(
  // change this option for your need
  new Zend_Search_Lucene_Analysis_Analyzer_Common_Utf8Num_CaseInsensitive()
);

$rowSet = getDataRowSet();  // perform your SQL query to fetch whatever you need to index
foreach ($rowSet as $row) {
   $doc = new Zend_Search_Lucene_Document();
   $doc->addField(Zend_Search_Lucene_Field::text('field1', $row->field1, 'utf-8'))
       ->addField(Zend_Search_Lucene_Field::text('field2', $row->field2, 'utf-8'))
       ->addField(Zend_Search_Lucene_Field::unIndexed('someValue', $someVariable))
       ->addField(Zend_Search_Lucene_Field::unIndexed('someObj', serialize($obj), 'utf-8'))
  ;
  $indexer->addDocument($doc);
}

// ... you can get as many $rowSet as you want and create as many documents
// as you wish... each document doesn't necessarily need the same fields...
// Lucene is pretty flexible on this

$indexer->optimize();  // do this every time you add more data to you indexer...
$indexer->commit();    // finalize the process

Тогда это в основном то, как вы ищите (основной поиск):

$index = Zend_Search_Lucene::open('/path/to/lucene/data');

// same search options
Zend_Search_Lucene_Analysis_Analyzer::setDefault(
   new Zend_Search_Lucene_Analysis_Analyzer_Common_Utf8Num_CaseInsensitive()
);

Zend_Search_Lucene_Search_QueryParser::setDefaultEncoding('utf-8');

$query = 'php +field1:foo';  // search for the word 'php' in any field,
                                 // +search for 'foo' in field 'field1'

$hits = $index->find($query);

$numHits = count($hits);
foreach ($hits as $hit) {
   $score = $hit->score;  // the hit weight
   $field1 = $hit->field1;
   // etc.
}

Вот отличные сайты о Lucene в Java , PHP и .Net .

В заключение у каждого метода поиска есть свои плюсы и минусы:

  • Вы упомянули Sphinx search , и он выглядит очень хорошо, если вы можете заставить деамона работать на вашем веб-хосте.
  • Zend Lucene требует задания cron для повторной индексации базы данных. Хотя это совершенно прозрачно для пользователя, это означает, что любые новые данные (или удаленные данные!) Не всегда синхронизируются с данными в вашей базе данных и поэтому не будут отображаться сразу при поиске пользователей.
  • Поиск MySQL FULLTEXT хорош и быстр, но не даст вам всей силы и гибкости первых двух.

Пожалуйста, не стесняйтесь комментировать, если я что-то забыл / пропустил.

79
ответ дан Timo Huovinen 25 August 2018 в 02:58
поделиться

Кажется, что ваше определение сходства - это семантическое сходство. Поэтому, чтобы построить такую ​​функцию подобия, вы должны использовать семантические схожесть. Обратите внимание, что объем работы по этой проблеме может варьироваться от нескольких часов до нескольких лет, поэтому рекомендуется принять решение о объеме до начала работы. Я не понял, какие данные у вас есть, чтобы построить отношение подобия. Я предполагаю, что у вас есть доступ к набору данных документов и набору данных запросов. Вы можете начать с совпадения слов (например, условной вероятности). Вы быстро обнаружите, что вы получите список стоп-слов , поскольку они связаны с большинством слов просто потому, что они очень популярны. Использование лифта условной вероятности позаботится о словах остановки, но сделает отношение, подверженное ошибкам, небольшим числом (большинство ваших случаев). Вы можете попробовать Jacard , но поскольку он симметричен, будет много отношений, которые он не найдет. Тогда вы можете рассмотреть отношения, которые появляются только на небольшом расстоянии от базового слова. Вы можете (и должны) рассматривать базу отношений на общем корпусе (например, в Википедии) и на конкретных пользователях (например, его электронные письма).

Очень скоро у вас будет много мер сходства, когда все меры будут хорошими и имеют некоторое преимущество перед другими.

Чтобы объединить такие меры, я хотел бы свести проблему к проблеме классификации.

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

  • Использовать источники известных связанных слов (например, старые добрые категории Википедии) для позитивов
  • Большая часть слова неизвестна связанные друг с другом.

Затем используйте все меры, которые у вас есть как функции пар. Теперь вы находитесь в области контролируемой проблемы классификации. Создайте классификатор в наборе данных, оцененный в соответствии с вашими потребностями и получите меру подобия, которая соответствует вашим потребностям.

3
ответ дан DaL 25 August 2018 в 02:58
поделиться

1. Сходство

Для Levenshtein в MySQL я нашел это, от www.codejanitor.com/wp/2007/02/10/levenshtein-distance-as-a-mysql-stored-function

SELECT 
    column, 
    LEVENSHTEIN(column, 'search_string') AS distance 
FROM table 
WHERE 
    LEVENSHTEIN(column, 'search_string') < distance_limit
ORDER BY distance DESC

2. Содержит, нечувствительность к регистру

Используйте инструкцию LIKE MySQL, которая по умолчанию нечувствительна к регистру. % является подстановочным знаком, поэтому может быть любая строка до и после search_string.

SELECT 
    *
FROM 
    table
WHERE 
    column_name LIKE "%search_string%"

3. Содержащий, чувствительный к регистру

Справочник по MySQL помогает:

Набор символов и сортировка по умолчанию - latin1 и latin1_swedish_ci, поэтому неважные сопоставления строк являются случайными по умолчанию нечувствителен. Это означает, что при поиске с col_name LIKE 'a%' вы получаете все значения столбцов, начинающиеся с A или a. Чтобы сделать этот регистр чувствительным, убедитесь, что один из операндов имеет чувствительную к регистру или двоичную сортировку. Например, если вы сравниваете столбец и строку, у которых есть набор символов latin1, вы можете использовать оператор COLLATE, чтобы заставить либо операнд иметь сортировку latin1_general_cs, либо latin1_bin ...

Моя настройка MySQL не поддерживает latin1_general_cs или latin1_bin, но для меня было полезно использовать сортировку utf8_bin, поскольку двоичный utf8 чувствителен к регистру:

SELECT 
    *
FROM 
    table
WHERE 
    column_name LIKE "%search_string%" COLLATE utf8_bin

2. / 3. отсортировано по Левенштейну Расстояние

SELECT 
    column, 
    LEVENSHTEIN(column, 'search_string') AS distance // for sorting
FROM table 
WHERE 
    column_name LIKE "%search_string%"
    COLLATE utf8_bin // for case sensitivity, just leave out for CI
ORDER BY
    distance
    DESC
20
ответ дан Matt 25 August 2018 в 02:58
поделиться
Другие вопросы по тегам:

Похожие вопросы: