TSQL "НРАВИТСЯ" или регулярные выражения? - PullRequest
3 голосов
/ 19 января 2009

У меня есть куча (750 КБ) записей в одной таблице, и я должен видеть, что они находятся в другой таблице. Вторая таблица содержит миллионы записей, а данные выглядят примерно так:

Исходная таблица
9999-A1B-1234X, причем средняя часть может быть длиннее трех цифр

Целевой стол
DescriptionPhrase9999-A1B-1234X(9 pages) - да, слова и слова в поле.

В настоящее время я запускаю приложение .net, которое загружает исходные записи, затем просматривает их и выполняет поиск (используя функцию tsql), чтобы определить, есть ли записи. Если да, исходная таблица обновляется с положительным значением. Если нет, то запись остается одна.

приложение обрабатывает около 1000 записей в час. Когда я сделал это в качестве курсора Sproc на сервере SQL, я почти получил ту же скорость.

Какие-нибудь идеи, если бы регулярные выражения или любая другая методология заставили бы это идти быстрее?

Ответы [ 8 ]

5 голосов
/ 19 января 2009

Как насчет того, чтобы делать все это в БД, а не загружать записи в ваше приложение .Net:

UPDATE source_table s SET some_field = true WHERE EXISTS
(
     SELECT target_join_field FROM target_table t 
     WHERE t.target_join_field LIKE '%' + s.source_join_field + '%'
)

Это сократит общее количество запросов с 750 тыс. Запросов на обновление до 1 обновления.

3 голосов
/ 20 января 2009

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

Кроме того, условие (b.field вроде '%' + a.field + '%') необходимо будет проверить каждую запись в b (миллионы) для каждой записи в a (750 000). Это составляет более 750 миллиардов сравнений строк. Не отлично.

Здесь не поможет и внутреннее чувство "индексного материала". Индекс поддерживает порядок, поэтому первые символы определяют позицию в индексе, а не те, которые вас интересуют.

Первая идея

По этой причине я бы на самом деле подумал о создании другой таблицы и разборе значения long / messy во что-то более приятное. В качестве примера можно было бы просто удалить любой текст с последнего символа '(' и далее. (Предполагается, что все значения следуют этому шаблону). Это упростит условие запроса до (b.field наподобие '%' + a.field)

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

Может показаться, что вы потратили столько времени, но в этом случае небольшая выгода принесла бы большую награду. (Например, несколько часов работы позволяют сократить вдвое сравнения с 750 миллиардов до 375 миллиардов. И если вы сможете получить индекс для воспроизведения, вы можете уменьшить его в тысячу раз благодаря поискам по индексу, а не только упорядоченным таблицам ...)

Вторая идея

Предполагая, что вы действительно копируете целевую таблицу во временную таблицу, вы можете извлечь дополнительную выгоду из обработки их в блоках по 1000, также удалив соответствующие записи из целевой таблицы. (Это имеет смысл только в том случае, если вы удаляете значимую сумму из целевой таблицы. Таким образом, после того, как все 750 000 записей были проверены, целевая таблица теперь [например] вдвое меньше того размера, с которого она началась.)

EDIT:
Модифицированная вторая идея

  1. Поместите всю целевую таблицу во временную таблицу.

  2. Предварительная обработка значений, насколько это возможно, для ускорения сравнения строк или даже ввода индексов в игру.

  3. Поочередно просматривайте каждую запись из исходной таблицы. Используйте следующую логику в вашем цикле ...

    УДАЛИТЬ поле target WHERE LIKE '%' + @source_field + '%' IF (@@ row_count = 0) [нет совпадений] ELSE [Соответствует]

Непрерывное удаление ускоряет запрос в каждом цикле, и вы используете только один запрос к данным (вместо одного для поиска совпадений и второй для удаления совпадений)

3 голосов
/ 19 января 2009

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

Если у вас есть данные, с которыми вы можете сопоставить, вам не нужно ни указывать "% somestuff%", который не может использовать индексы, либо курсор, оба из которых снижают производительность. Это должно быть заданием на основе набора, если вы правильно спроектировали. Если дизайн плохой и не может быть изменен на хороший дизайн, я не вижу хорошего способа добиться хорошей производительности с помощью t-SQl, и я бы попробовал маршрут регулярного выражения. Не зная, сколько разных prharses и структуры каждого из них, я не могу сказать, будет ли маршрут регулярного выражения простым или даже возможным. Но, за исключением редизайна (что я настоятельно рекомендую вам сделать), я не вижу другой возможности.

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

1 голос
/ 19 января 2009

Попробуйте это -

update SourceTable  
set ContainsBit = 1  
from SourceTable t1      
  join (select TargetField from dbo.TargetTable t2) t2   
    on charindex(t1.SourceField, t2.TargetField) > 0
0 голосов
/ 21 августа 2009

Попробуйте запрос обновления Dan R сверху:

update SourceTable  
set ContainsBit = 1  
from SourceTable t1      
 join (select TargetField 
       from dbo.TargetTable t2) t2   
 on charindex(t1.SourceField, t2.TargetField) > 0

В качестве альтернативы, если своевременность этого важна, а это sql 2005 или более поздняя версия, тогда это будет классическое использование вычисляемого столбца с использованием кода SQL CLR с регулярными выражениями - нет необходимости в отдельном приложении.

0 голосов
/ 19 января 2009

Святой дым, какие замечательные ответы!

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

Текущий UDF:

Create function CountInTrim 
(@caseno varchar255)
 returns int
as 
Begin
declare @reccount int
select @reccount = count(recId) from targettable where title like '%' + @caseNo +'%'
return @reccount
end

Обычно, если есть количество записей, то есть совпадение, и приложение .net обновляет запись. Sproc на основе курсора имел ту же логику.

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

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

DescriptionPhrase1999-A1C-12345(5 pages)
Phrase/Two2000-A1C2F-5432S(27 Pages)
DescPhraseThree2002-B2B-2345R(8 pages)

Синтаксический анализ стал немного сложнее, поэтому я подумал, что мне будет проще.

Я собираюсь попробовать один оператор update, а затем при необходимости вернусь к регулярному выражению в clr.

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

0 голосов
/ 19 января 2009

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

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

Я бы запустил его локально на быстрой машине, используя подпрограмму, написанную на чем-то вроде 'C' / C ++ или другом «легковесном» языке, который имеет грубую вычислительную мощность, и выписал бы таблицу первичных ключей, которые «соответствовали» который я бы затем загрузить обратно на сервер SQL и использовать его в качестве основы для запроса на обновление (т. е. таблица источника обновлений, где идентификатор в выберите идентификатор из временной таблицы).

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

Судя по звукам sql, вы, возможно, пытаетесь выполнить 750 000 сканирований таблиц по многомиллионной таблице записей.

Расскажите подробнее о проблеме.

0 голосов
/ 19 января 2009

Прежде всего, убедитесь, что у вас есть индекс для этого столбца в таблице поиска. Второе - сделать LIKE без знака% на левой стороне. Проверьте план выполнения, чтобы убедиться, что вы не выполняете сканирование таблицы в каждой строке.

Как правильно заметил Ле Дорфье, если вы используете UDF, у вас мало надежд.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...