Строки в 1С 8.3 во встроенном языке 1с представляют собой значения примитивного типа Строка . Значения данного типа содержат строку в формате Unicode произвольной длины. Переменные строкового типа являются набором символов заключенных в кавычки.
Пример 1. Создадим строковую переменную с текстом.
СтроковаяПеременная = "Привет мир!";
В данном разделе будут приведены основные функции, позволяющие изменять строки в 1с, либо анализировать информацию содержащуюся в них.
СтрДлина(<Строка>) . Возвращает количество символов содержащихся в строке, переданной в параметре.
Пример 2. Посчитаем количество символов в строке «Привет мир!».
Строка = "Привет мир!"; КоличествоСимволов = СтрДлина(Строка); Сообщить(КоличествоСимволов);
Итогом выполнения данного кода будет вывод на экран количества символов строки: 11.
СокрЛ(<Строка>)
. Отсекает незначащие символы, стоящие слева от первого значащего символа в строке.
Незначащие символы:
Пример 3. Убрать все пробелы с левой стороны строки » мир!» и присоединить к ней строку «Привет «.
Строка = СокрЛ(" мир!"); Строка = "Привет "+Строка; Сообщить(Строка);
Итогом выполнения данного кода будет вывод на экран строки «Привет мир!».
СокрП(<Строка>) . Отсекает незначащие символы, стоящие справа от первого значащего символа в строке.
Пример 4. Сформировать из строк «Привет » и » мир!» фразу «Привет мир!»
Строка = СокрП("Привет ")+" "+СокрЛ(" мир!"); Сообщить(Строка);
СокрЛП(<Строка>)
. Отсекает незначащие символы, стоящие справа от первого значащего символа в строке, также отсекает незначащие символы, стоящие слева от первого значащего символа в строке. Данная функция используется чаще предыдущих двух, так как она более универсальна.
Пример 5. Убрать незначащие символы стоящие слева и справа в наименовании контрагента.
Контрагент = Справочники.Контрагенты.НайтиПоРеквизиту("ИНН", "0777121211"); КонтрагентОбъект = Контрагент.ПолучитьОбъект(); КонтрагентОбъект.Наименование = СокрЛП(КонтрагентОбъект.Наименование); КонтрагентОбъект.Записать();
Лев(<Строка>, <ЧислоСимволов>)
. Получает первые символы строки, число символов указывается в параметре ЧислоСимволов.
Пример 6. Пусть в структуре Сотрудник содержаться имя, фамилия и отчество сотрудника. Получить строку с фамилией и инициалами.
ИнициалИмени = Лев(Сотрудник.Имя, 1); ИнициалОтчества = Лев(Сотрудник.Отчество, 1); ПолноеИмя = Сотрудник.Фамилия + " " + ИнициалИмени + "." + ИнициалОтчества + ".";
Прав(<Строка>, <ЧислоСимволов>) . Получает последние символы строки, число символов указывается в параметре ЧислоСимволов. Если указанное количество символов превышает длину строки, то возвращается вся строка.
Пример 7. Пусть в конце строковой переменной записана дата в формате «ггггммдд», получить строку с датой и преобразовать ее к типу Дата .
Строка = "Текущая дата: 20170910"; СтрокаДата = Прав(Строка, 8); Дата = Дата(СтрокаДата);
Сред(<Строка>, <НачальныйНомер>, <ЧислоСимволов>) . Получает подстроку из строки переданной в параметре Строка , начиная с символа номер которого указан в параметре НачальныйНомер и длиной переданной в параметр ЧислоСимволов. Нумерация символов в строке начинается с 1. Если в параметре НачальныйНомер указано значение, меньшее или равное нулю, то параметр принимает значение 1. Если параметр ЧислоСимволов не указан, то выбираются символы до конца строки.
Пример 8. Пусть в строковой переменной начиная с девятой позиции содержится код региона, следует получить его и записать в отдельную строку.
Строка = "Регион: 99 г. Москва"; Регион = Сред(Строка, 9, 2);
СтрНайти(<Строка>, <ПодстрокаПоиска>, <НаправлениеПоиска>, <НачальнаяПозиция>, <НомерВхождения>) . Осуществляет поиск указанной подстроки в строке, возвращает номер позиции первого символа найденной подстроки. Рассмотрим параметры данной функции:
Пример 9. В строке «Привет мир!» определить позицию последнего вхождения символа «и».
НомерПозиции = СтрНайти("Привет мир!", "и", НаправлениеПоиска.СКонца); Сообщить(НомерПозиции);
Итогом выполнения данного кода будет вывод на экран номера последнего вхождения символа «и»: 9.
ВРег(<Строка>) . Преобразует все символы указанной строки в 1с 8 к верхнему регистру.
Пример 10. Преобразовать строку «привет мир!» к верхнему регистру.
СтрокаВрег = ВРег("привет мир!"); Сообщить(СтрокаВрег);
Итогом выполнения данного кода будет вывод на экран строки «ПРИВЕТ МИР!»
НРег(<Строка>) . Преобразует все символы указанной строки в 1с 8 к нижнему регистру.
Пример 11. Преобразовать строку «ПРИВЕТ МИР!» к нижнему регистру.
СтрокаНрег = НРег("ПРИВЕТ МИР!"); Сообщить(СтрокаВрег);
Итогом выполнения данного кода будет вывод на экран строки «привет мир!»
ТРег(<Строка>) . Преобразует строку следующим образом: первый символ каждого слова переводится в верхний регистр, остальные символы слова переводятся в нижний регистр.
Пример 12. Сделать заглавными первые буквы слов в строке «привет мир!».
СтрокаТрег = ТРег("привет мир!"); Сообщить(СтрокаТрег);
Итогом выполнения данного кода будет вывод на экран строки «Привет Мир!»
Символ(<КодСимвола>) . Получает символ по его коду в кодировке Unicod.
Пример 13. Добавим слева и справа в строку «Привет Мир!» символ ★
СтрокаСоЗвездами = Символ("9733")+"Привет Мир!"+Символ("9733"); Сообщить(СтрокаСоЗвездами);
Итогом выполнения данного кода будет вывод на экран строки «★Привет Мир!★»
КодСимвола(<Строка>, <НомерСимвола>) . Получает код символа в кодировке Unicode из строки указанной в первом параметре, расположенного в позиции указанной во втором параметре.
Пример 14. Узнать код последнего символа в строке «Привет Мир!».
Строка = "Привет Мир!"; КодСимвола = КодСимвола(Строка, СтрДлина(Строка)); Сообщить(КодСимвола);
Итогом выполнения данного кода будет вывод на экран кода символа «!» — 33.
ПустаяСтрока(<Строка>) . Проверяет состоит ли строка только из незначащих символов, то есть является ли она пустой.
Пример 15. Проверить является ли пустой строка состоящая из трех пробелов.
Пустая = ПустаяСтрока(" "); Сообщить(Пустая);
Итогом выполнения данного кода будет вывод на экран слова «Да» (строковое выражение логического значения Истина ).
СтрЗаменить(<Строка>, <ПодстрокаПоиска>, <ПодстрокаЗамены>) . Находит в исходной строке все вхождения подстроки поиска и заменяет ее на подстроку замены.
Пример 16. В строке «Привет Мир!» заменить слово «Мир» на слово «Друзья».
Строка = СтрЗаменить("Привет Мир!", "Мир", "Друзья"); Сообщить(Строка);
Итогом выполнения данного кода будет вывод на экран строки «Привет Друзья!»
СтрЧислоСтрок(<Строка>) . Позволяет посчитать количество строк в многострочной строке. Для перехода на новую строку в 1с 8 используется символ ПС (символ перевода строки).
Пример 17. Определить число строк в тексте:
«Первая строка
Вторая строка
Третья строка»
Число = СтрЧислоСтрок("Первая строка"+Символы.ПС +"Вторая строка"+Символы.ПС +"Третья строка"); Сообщить(Число);
Итогом выполнения данного кода будет вывод на экран количества строк в тексте: 3
СтрПолучитьСтроку(<Строка>, <НомерСтроки>) . Получает строку в многострочной строке по ее номеру. Нумерация строк начинается с 1.
Пример 18. Получить последнюю строку в тексте:
«Первая строка
Вторая строка
Третья строка»
Текст = "Первая строка"+Символы.ПС +"Вторая строка"+Символы.ПС +"Третья строка"; ПоследняяСтрока = СтрПолучитьСтроку(Текст, СтрЧислоСтрок(Текст)); Сообщить(ПоследняяСтрока);
Итогом выполнения данного кода будет вывод на экран строки «Третья строка».
СтрЧислоВхождений(<Строка>, <ПодстрокаПоиска>) . Возвращает число вхождений указанной подстроки в строку. Функция чувствительна к регистру.
Пример 19. Определить сколько раз входит в строку «Строки в 1с 8.3 и 8.2» буква «с», вне зависимости от ее регистра.
Строка = "Строки в 1с 8.3 и 8.2"; ЧислоВхождений = СтрЧислоВхождений(Врег(Строка), "С"); Сообщить(ЧислоВхождений);
Итогом выполнения данного кода будет вывод на экран числа вхождений: 2.
СтрНачинаетсяС(<Строка>, <СтрокаПоиска>) . Проверяет начинается ли строка переданная в первом параметре, со строки во втором параметре.
Пример 20. Определить начинается ли ИНН выбранного контрагента с цифры 1. Пусть в переменной Контрагент Контрагенты .
ИНН = Контрагент.ИНН; НачинаетсяСЕдиницы = СтрНачинаетсяС(ИНН, "1"); Если НачинаетсяСЕдиницы Тогда //Ваш код КонецЕсли;
СтрЗаканчиваетсяНа(<Строка>, <СтрокаПоиска>) . Проверяет заканчивается ли строка переданная в первом параметре, на строку во втором параметре.
Пример 21. Определить заканчивается ли ИНН выбранного контрагента на цифру 2. Пусть в переменной Контрагент хранится ссылка на элемент справочника Контрагенты .
ИНН = Контрагент.ИНН; ЗаканчиваетсяНаДвойку = СтрЗаканчиваетсяНа(ИНН, "2"); Если ЗаканчиваетсяНаДвойку Тогда //Ваш код КонецЕсли;
СтрРазделить(<Строка>, <Разделитель>, <ВключатьПустые>) . Разделяет строку на части по указанным символам-разделителям и записывает полученные строки в массив. В первом параметре хранится исходная строка, во втором строка содержащая разделитель, в третьем указывается, нужно ли записывать в массив пустые строки (по умолчанию Истина ).
Пример 22. Пусть у нас есть строка содержащая числа разделенные символом «;», получить из строки массив чисел.
Строка = "1; 2; 3"; Массив = СтрРазделить(Строка, ";"); Для Сч = 0 По Массив.Количество() - 1 Цикл Попытка Массив[Сч] = Число(СокрЛП(Массив[Сч])); Исключение Массив[Сч] = 0; КонецПопытки КонецЦикла;
В результате выполнения будет получен массив с числами от 1 до 3-х.
СтрСоединить(<Строки>, <Разделитель>) . Преобразует массив строк из первого параметра в строку, содержащую все элементы массива через разделитель, указанный во втором параметре.
Пример 23. Используя массив чисел из предыдущего примера, получить исходную строку.
Для Сч = 0 По Массив.Количество() - 1 Цикл Массив[Сч] = Строка(Массив[Сч]); КонецЦикла; Строка = СтрСоединить(Массив, "; ");
Механизмов для работы со строками в запросах 1С мало. Во-первых, строки можно складывать. Во-вторых, от строки можно взять подстроку. В-третьих, строки можно сравнивать, в том числе по шаблону. Вот пожалуй и всё, что можно делать со строками.
Для сложения строк в запросе используется операция «+». Складывать можно только строки ограниченной длины.
ВЫБРАТЬ "Наименование: " + Контрагенты.Наименование КАК Колонка1 ИЗ Справочник.Контрагенты КАК Контрагенты ГДЕ Контрагенты.Ссылка = &Ссылка
ПОДСТРОКА(<Строка>, <НачальнаяПозиция>, <Длина>)
Аналог функции Сред() из объектной модели. Функция Подстрока() может применяться к данным строкового типа и позволяет выделить фрагмент <Строки> , начинающийся с символа номер <НачальнаяПозиция> (символы в строке нумеруются с 1) и длиной <Длина> символов. Результат вычисления функции имеет строковый тип переменной длины, причем длина будет считаться неограниченной, если <Строка> имеет неограниченную длину и параметр <Длина> не является константой или превышает 1024.
Если длина строки меньше, чем указана во втором параметре, то функция вернет пустую строку.
Внимание! Использование функции ПОДСТРОКА() с целью приведения строк неограниченной длины к строкам ограниченной длины не рекомендуется. Вместо нее лучше использовать операцию приведения типа ВЫРАЗИТЬ().
Если нужно убедиться, что строковый реквизит соответствует определённым критериям мы его сравниваем:
ВЫБРАТЬ Контрагенты.Наименование КАК Колонка1 ИЗ Справочник.Контрагенты КАК Контрагенты ГДЕ Контрагенты.Наименование = "Газпром"
А что, если нужно более хитрое сравнение? Не просто на равенство или неравенство, а на подобие определенному шаблону? Вот как раз для этого и создана функция ПОДОБНО.
ПОДОБНО — Оператор проверки строки на подобие шаблону. Аналог LIKE в SQL.
Оператор ПОДОБНО позволяет сравнить значение выражения, указанного слева от него, со строкой шаблона, указанной справа. Значение выражения должно иметь тип строка. Если значение выражения удовлетворяет шаблону – результатом оператора будет ИСТИНА, иначе – ЛОЖЬ.
Следующие символы в строке шаблона являются служебными и имеют смысл, отличный от символа строки:
Любой другой символ означает сам себя и не несет никакой дополнительной нагрузки. Если в качестве самого себя необходимо записать один из перечисленных символов, то ему должен предшествовать <Спецсимвол>. Сам <Спецсимвол> (любой подходящий символ) определяется в этом же операторе после ключевого слова СПЕЦСИМВОЛ.
Главная Заметки из ЗазеркальяРеализовано в версии 8.3.3.641.
Мы серьёзно улучшили ввод по строке. Для этого нам пришлось реализовать новый мощный механизм поиска в поле ввода. Теперь он может быстро работать по миллионам записей, содержащихся в базе данных.
Мы проанализировали задачи поиска, возникающие у пользователей. Провели "ревизию" имеющихся в платформе механизмов, которые используют различные алгоритмы поиска. В результате ввод по строке приобрёл качественно новые возможности.
Теперь пользователи могут искать в любом месте названия, а не только по началу строки. Они могут использовать при этом полнотекстовый поиск и даже выполнять поиск с помощью фонового задания. Для того чтобы полнотекстовый поиск эффективно работал при вводе по строке, мы дополнительно оптимизировали этот механизм поиска и повысили его производительность.
Все новые свойства, позволяющие настраивать ввод по строке, мы собрали на отдельной закладке окна редактирования объекта конфигурации:
Например, вы можете указать, что поиск будет выполняться в любом месте строки, а не только в её начале:
Тогда пользователь может набирать любые фрагменты слов, а не только те символы, с которых начинается искомая строка:
Использование полнотекстового поиска при вводе по строке включается отдельным свойством:
С помощью полнотекстового поиска пользователи могут среди больших объёмов данных быстро находить подходящие данные по любому из слов, содержащихся, например, в наименовании:
Также они могут искать и по нескольким известным словам. Незаконченные слова будут автоматически дополняться возможными сочетаниями:
Если объём данных велик, то в обоих случаях вы можете указать, что поиск должен выполняться с помощью фонового задания:
Тогда рядом с полем ввода для пользователей будет отображаться анимированная картинка, похожая на картинку, которая отображается при фоновом выполнении отчета:
Все перечисленные свойства вы можете, при необходимости, переопределить в процессе выполнения прикладного решения.
На клиенте - в клиентских обработчиках событий поля ввода АвтоПодбор и ОкончаниеВводаТекста :
На сервере - в модуле менеджера того объекта, в данных которого выполняется поиск. В обработчике события ОбработкаПолученияДанныхВыбора :
Естественно, в этом месте, на сервере, нельзя переопределить способ выполнения поиска "Непосредственно" или "Фоновым заданием" . Потому что исполнение кода уже передано на сервер.
Строка — один из примитивных типов данных в системах 1С:Предприятие 8. Переменные с типом строка содержат текст.
Значения переменных типа строка заключаются в двойные кавычки. Несколько переменных данного типа можно складывать.
Пер1
=
"Слово 1"
;
Пер2
=
"Слово 2"
;
Пер3
=
Пер1
+
" "
+
Пер2
;
В итоге Пер3 будет иметь значение «Слово 1 Слово 2″.
Кроме того, в системах 1С:Предприятие 8 предусмотрены функции для работы со строками. Рассмотрим основные:
ВвестиСтроку(<Строка>, <Подсказка>, <Длина>, <Многострочность>) — функция предназначена для вывода диалогового окна, в котором пользователь может указать значение переменной типа Строка . Параметр <Строка> является обязательным и содержит имя переменной, в которую будет записана введенная строка. Параметр <Подсказка> необязательный — это заголовок диалогового окна. Параметр <Длина> необязательный, показывает максимальную длину вводимой строки. По умолчанию равен нулю, что означает неограниченную длину. Параметр <Многострочность> необязательный. Определяет режим ввода многострочного текста: Истина — ввод многострочного текста с разделителями строк; Ложь — ввод простой строки.
Строку можно ввести и, зная код символа в кодировке Unicode:
Символ(<КодСимвола>) — код вводится в виде числа.
Буква= Символ(1103 ) ; // Я
Существует и обратная функция, позволяющая узнать код какого-либо символа.
КодСимвола(<Строка>, <НомерСимвола>) — возвращает номер указанного символа в кодировке Unicode в виде числа.
ВРег(<Строка>) — преобразует все символы строки в верхний регистр.
НРег(<Строка>) — преобразует все символы строки в нижний регистр.
ТРег(<Строка>) — преобразует все символы строки в титульный регистр. То есть первые буквы во всех словах преобразуется в верхний регистр, а остальные буквы — в нижний.
Найти(<Строка>, <ПодстрокаПоиска>) — находит номер символа вхождения подстроки поиска. Например:
Найти ("Строка" , "ока" ) ; // 4
СтрНайти(<Строка>, <ПодстрокаПоиска>, <НаправлениеПоиска>, <НачальнаяПозиция>, <НомерВхождения>) — находит номер символа вхождения подстроки поиска, номер вхождения указывается в соответствующем параметре. При этом поиск начинается с символа, номер которого указан в параметре НачальнаяПозиция. Поиск возможен с начала или с конца строки. Например:
Номер4 Вхождения= СтрНайти ("Обороноспособность" , "о" , НаправлениеПоиска. СНачала, 1 , 4 ) ; // 7
СтрЗаменить(<Строка>, <ПодстрокаПоиска>, <ПодстрокаЗамены>) – находит в исходной строке все вхождения подстроки поиска и заменяет ее на подстроку замены.
СтрЗаменить ("Строка" , "ока" , "" ) ; // Стр
ПустаяСтрока(<Строка>) – проверяет строку на наличие значащих символов. Если значащих символов нет, или вообще никаких символов нет, то возвращается значение Истина . В противном случае — Ложь .
СтрЧислоВхождений(<Строка>, <ПодстрокаПоиска>) – вычисляет число вхождений подстроки поиска в исходной строке.
СтрЧислоВхождений ("учиться, учиться и еще раз учиться" , "учиться" , "" ) ; // 3
СтрШаблон(<Строка>, <ЗначениеПодстановки1>…<ЗначениеПодстановкиN> — подставляет параметры в строку по номеру. Строка должна содержать маркеры подстановки вида: «%1..%N». Нумерация маркеров начинается с 1. Если значение параметра Неопределено , подставляется пустая строка.
СтрШаблон ("Параметр 1 = %1, Параметр 2 = %2" , "1" , "2" ) ; // Параметр 1= 1, Параметр 2 = 2
Лев(<Строка>, <ЧислоСимволов>) – возвращает первые сначала символы строки.
Прав(<Строка>, <ЧислоСимволов>) – возвращает последние символы строки.
Сред(<Строка>, <НачальныйНомер>, <ЧислоСимволов>) – возвращает строку длиной в <ЧислоСимволов>, начиная с символа <НачальныйНомер>.
СокрЛ(<Строка>) — отсекает незначащие символы, стоящие слева от первого значащего символа в строке.
СокрП(<Строка>) — отсекает незначащие символы, стоящие справа от последнего значащего символа в строке.
СокрЛП(<Строка>) – отсекает незначащие символы, стоящие слева от первого значащего символа в строке и справа от последнего значащего символа в строке.
СтрПолучитьСтроку(<Строка>, <НомерСтроки>) – получает строку многострочной строки по номеру.
СтрДлина(<Строка>) – возвращает количество символов в строке.
СтрЧислоСтрок(<Строка>) – возвращает число строк в многострочной строке. Строка считается новой, если она отделена от предыдущей символом перевода строки.
СтрСравнить(<Строка1>, <Строка2> ) – сравнивает две строки без учета регистра. Функция работает аналогично объекту СравнениеЗначений . Возвращает:
СтрСравнить("Первая строка" , "Вторая строка" ) ; // 1
Меня зовут Павел Баркетов, я работаю в компании «Софтпоинт». Мы уже более 10 лет занимаемся решением задач оптимизации производительности. И несмотря на большое количество решенных задач, их количество не уменьшается, а растет в геометрической прогрессии. Объемы данных увеличиваются, и задачи по оптимизации работы с этими данными усложняются. Этот процесс неизбежен.
Тема статьи - нетривиальные подходы к оптимизации . Будут рассмотрены два аспекта :
Первая тема - поиск по подстроке. За этот год я сталкивался несколько раз с проблемами по этой операции. Приходишь в страховую компанию за продлением полиса, тебя предлагают найти по номеру телефона. Понятно, что это не классический поиск по полному номеру телефона, потому что пользователь мог завести номер через восьмерку, через семерку или еще как-то, следовательно, ищут по фрагментам номера. При этом используются долговременные операции поиска - в ряде ситуаций задержка может быть несколько секунд, а может доходить и до минут.
Начну с первого примера, когда поиск осуществляется по начальным символам. Это - частный случай поиска по подстроке, когда пользователь точно знает, что искомое значение начинается с определенных символов.
Поиск по начальным символам реализуется в 1С с помощью команды ПОДОБНО (или в английском варианте, LIKE) с указанием значения с «%» в конце («%» обозначает последовательность любых других символов). Например, мы ищем:
Наименование ПОДОБНО "ивано%"
Обратите внимание, что если у вас в системе существует индекс по этому полю, то в SQL-запросе для этого поиска будет использоваться Index Seek - это поиск по индексу.
Условие «ПОДОБНО поисковой строке» эквивалентно поиску в диапазоне значений . В частном случае, когда мы ищем «ивано%» - это эквивалентно поиску в диапазоне фамилий, которые начинаются на «ивано», и, заканчивая «иванп» (потому что символ «п» идет после символа «о»).
Современные оптимизаторы самостоятельно преобразуют запрос LIKE на запрос поиска по диапазону . Следовательно, если у вас в системе существует индекс по этому полю, вы при интерпретации запроса в термины SQL получите именно такой результат - оптимизатор представит запрос с LIKE в виде поиска по диапазону.
Таким образом, можно осуществлять классический быстрый поиск с использованием индекса (Index Seek). С этим проблем нет или решить их можно простым способом.
Теперь возьмем пример посложнее, когда неизвестно, в каком именно месте строки стоит наше искомое значение, и реализуется поиск по вхождению строки. В этом случае в запросе «ПОДОБНО» «%» стоит с двух сторон.
При преобразовании такого запроса в SQL мы видим, что изменяется только команда (в значении используется уже два «%»).
Рассмотрим подробнее план выполнения. Здесь мы видим тот же Index Seek, но в данном случае он не работает эффективно.
Дело в том, что индекс по наименованию справочника, который мы рассматриваем, состоит из нескольких полей.
И поэтому, когда в плане выполнения отображается «Index Seek», это означает, что поиск делается по первому полю разделителя - на слайде выше можно увидеть, что поиск по нашему искомому значению Desc абсолютно не используется.
Что делать в этой ситуации? У меня на практике было очень часто, что пользователям запрещали использовать запросы на вхождение. И пользователи в ряде случае сами не использовали этот функционал, потому что время выполнения очень значительное, а надо продолжать работать. Поэтому им приходилось выкручиваться другими способами - выбирали в списках, пытались найти по первым символам и так далее.
Но это приводит к недовольству функционалом и неправильному восприятию системы. Пользователь понимает, что система с чем-то не может справиться и не работает как положено. Это неправильно.
Давайте теперь рассмотрим нетривиальный подход к решению этой задачи .
Обозначим ряд допусков:
Пример искомой строки «алексе» записали в форму и будем с ее помощью тестировать.
Например, пользователь ищет клиента с фамилией «Солдатов». Это - восемь символов, значит, будет три фрагмента длиной в шесть символов, которые мы ищем в служебной структуре. Далее объединяем это все в запросе. Таким образом, получается дополнительная фильтрация.
В результате мы избавляемся от знака «%» (т.е. впереди этих фрагментов всегда будет нужный нам символ), и при выполнении внутреннего запроса будет идти Index Seek, за который мы и боролись.
На практике получается очень интересная история - ускорение в десятки, в сотни раз . Причем, все это можно сделать средствами 1С, что очень приятно. Переписывать логику не потребуется, пользователь порадуется, что у него ускорился запрос поиска. В примере ускорение с 4 секунд до 0,05 секунды, а если бы у нас изначально запрос выполнялся две минуты, он стал бы исполняться менее секунды.
Механизм, что я вам показал, не является каким-то экспериментальным примером, это уже работает у реальных клиентов.
Теперь я расскажу кратко о подготовительных мероприятиях.
Заполнение регистра можно делать как средствами 1С, так и с помощью SQL.
Могу сказать, что заполнение такой структуры для 17-ти миллионов значений занимает где-то 20-25 минут. Естественно, пользователи в этот момент не должны изменять значения справочника.
Если мы рассчитаем для одного миллиона значений где-то 100 символов по 6 во фрагменте, получится где-то 4,7 Гб. Нужно запланировать, чтобы это место у вас было. Если у вас в справочнике, например, 100 миллионов значений, то вы должны запланировать место, которое будет доступно на диске.
Всегда ли этот метод будет работать быстро?
На это влияет статистика популярности фрагментов .
Таким образом, появляется статистика популярности по фрагментам.
Обратите внимание, что если популярность фрагментов низкая (100 элементов), то мы получаем ускорение - 0,1 секунду.
Если подстрока достаточно популярная (50 тысяч элементов), то мы получаем деградацию , причем гораздо большую, чем если бы не было оптимизации.
Таким образом, необходимо сделать улучшенную схему выполнения запроса , в которой мы сначала бы получили значение популярности подзапроса. Это делается тремя-пятью строчками в 1С. При этом мы точно знаем, что если строка непопулярная, то идет по первой ветке, а если популярная, то по второй.
Как работает ускорение? Идет запрос поиска из формы, далее мы обращаемся к регистру сведений со статистикой, получаем элемент и дальше выбираем, что использовать - классический или ускоренный запрос.
А теперь давайте рассмотрим, как выполняется SQL-запрос на SQL-сервере.
На слайде представлена упрощенная схема:
На что похожа реализованная нами схема?
Исходя из этой логики, можно сказать, что этот процесс раскрывает смысл того, для чего нам индексы, статистика и оптимизатор.
Кто не знал, для чего нужно обслуживать статистику в SQL, загляните в эту логику, и вы поймете, что если она неправильная или неактуальная, то мы пойдем по неправильной ветке. Запрос будет тормозить. Понимаем, для чего качественно и правильно обслуживать статистику - это влияет на производительность, на индекс.
Если индекса нет - будем сканировать все значения.
Таким образом, мы создали хоть примитивный, но свой оптимизатор. Можно сказать, что прощупали «на пальцах» то, как это делает MS SQL и другие СУБД, причем создав свои структуры.
Перейду ко второй теме - ускорение больших документов.
Мы в производственных задачах часто сталкиваемся с какими-то регламентными процедурами, как: закрытие месяца, отчет агенту, расчет себестоимости. Эти тяжелые, массивные документы проводятся и заполняются значительное количество времени. А когда мы заглядываем в отладчик и делаем на этих операциях трассировку, то видим, что 1С построчно вставляет значения в какую-то таблицу и на это уходит основное время . И ничего с этим поделать нельзя. Единственная рекомендация, которую можно предложить - это ускорить диск (эффективность этого решения очень сомнительная и требует предварительного анализа).
Предлагаю вернуться в историю и рассмотреть, как это делалось в 1С, начиная с 8.0 до 8.3 - это делалось построчно . SQL-сервер каждый раз анализировал запрос, его обрабатывал, создавал план выполнения, добавлял, отправлял команду в сторону 1С об успешности и получал следующий запрос. И такими step by step шли запросы от 1С сервера приложения к MS SQL.
Понятно, что если у вас 40 записей в документе, то проблем возникнуть не должно. Если записей у вас становится 10 тысяч и более (бывают организации, где в регламентных документах миллион записей), то этот процесс занимает очень длительное время. Одна запись обрабатывается очень быстро, но в документе их слишком много. На что уходят накладные расходы? На сеть, на выполнение запроса, на обратный сигнал, на обработку этого сигнала в системе 1С - итого, сумма четырех этапов. Все этапы суммируются, умножаются на миллион строк, и получаются наши длительные ожидания. Понятно, что это не ужасно.
В 1С, начиная с 8.3, сделаны улучшения. Теперь запрос для вставки во временные таблицы и в регистры сведений подготавливается на SQL-сервере, и его дальнейшее выполнение происходит с помощью классических RPC-вызовов, где сам провайдер доступа 1С (Native или OLE DB) группирует записи и вставляет их по N строк (как правило 100 строк) .
Таким образом, достигается ускорение от 30% до 300%. Но это все равно недостаточно, потому что сегодня у вас 10 тысяч строк, завтра 20 тысяч строк. Это не принципиальное решение проблемы, вы все равно с ней столкнетесь, но только через полгода/год.
Какая наиболее быстрая вставка в SQL-сервер, да и вообще в любую СУБД?
Это BULK INSERT . В 1С BULK INSERT используется, но для других задач. Работу с «большими» документами также хотелось бы ускорить путем укрупнения вставок INSERT и добавления записей единым массивом в базу данных SQL-сервера.
Посмотрим, какой достигается эффект. В рассматриваемом примере получено ускорение где-то в 5 раз, но можно ускориться и в 10 раз . Теоретически основная проблема для того, чтобы это ускорялось значительно сильнее - это скорость диска. Диск может является узким местом.
Также важно помнить про такой критерий, как индексы . Если бы мы вставляли BULK INSERT в таблицу без обновления индексов, то мы бы получили значительное ускорение (результат менее чем за секунду). Здесь мы получаем 69 секунд за счет того, что каждая вставка в таблицу требует REFRESH индекса.
В любом случае, этот способ позволяет достичь эффекта в 5-10 раз.
Плюс здесь не рассматриваются такие возможности, как партиционирование, секционирование. Можно было бы улучшить ситуацию, если бы мы знали, что BULK INSERT вставляется в актуальный период, а неактуальный мы вынесли бы в другую партицию. Это был бы еще больший эффект . Получается, что ускорение очень хорошее.
Таким образом, возможности оптимизации безграничны . Единственное - не увлекаться. До оптимизации всегда имеет смысл посчитать, будет ли предполагаемый эффект или нет. Также я бы советовал в каких-то ситуациях «подниматься» над проблемой, использовать не классические методы оптимизации запроса, а какие-то совсем иные, которые могут принести более значительный результат.
****************
Данная статья написана по итогам доклада (), прочитанного на конференции INFOSTART EVENT 2017 COMMUNITY.