В практике разработчика 1С часто возникают задачи, требующие нестандартного подхода к выборке данных. Одной из таких задач является получение случайного набора элементов из справочника, при этом объем выборки должен составлять определенный процент от общего количества записей или фиксированное число. Например, это может понадобиться для проведения маркетинговых акций, инвентаризации или выборочной проверки цен. Стандартный язык запросов 1С не содержит встроенной функции RAND(), поэтому нам приходится использовать комбинацию программных и запросных методов. Рассмотрим подробно, как это реализовать.
Первое, с чем мы сталкиваемся — это отсутствие прямой поддержки случайности в SQL-движке 1С. Если мы просто напишем ВЫБРАТЬ ПЕРВЫЕ 80, система вернет нам первые записи в том порядке, в котором они физически расположены в таблице или отсортированы оптимизатором. При повторном выполнении запроса мы получим тот же самый результат. Это не является случайной выборкой.
Для того чтобы выборка была действительно непредсказуемой, нам нужно внести элемент хаоса. Разберем основные стратегии решения этой задачи.
Если задача стоит в рамках формирования отчета через Систему компоновки данных (СКД), мы можем задействовать мощный механизм вычисляемых полей. Проанализируем этот подход по шагам:
Функция ПолучитьСлучайноеЧисло() Экспорт
ГСЧ = Новый ГенераторСлучайныхЧисел;
Возврат ГСЧ.СлучайноеЧисло(0, 1000000);
КонецФункции
СлучайныйПорядок. В выражении укажем вызов нашей функции: МойОбщийМодуль.ПолучитьСлучайноеЧисло().При каждом формировании отчета СКД будет вызывать функцию для каждой строки, присваивать ей случайный вес и сортировать результат. Таким образом, состав «первых N элементов» будет меняться при каждом запуске.
Когда нам нужно отобрать именно процент (например, 15% от всей номенклатуры в папке «Колбаса»), алгоритм становится чуть сложнее. Выясним, как правильно рассчитать количество и получить данные.
Сначала нам нужно узнать общее количество элементов, подходящих под фильтры пользователя. Затем вычислить целевое число и подставить его в запрос. Рассмотрим пример кода:
// 1. Получаем общее количество подходящих элементов
ЗапросКолво = Новый Запрос;
ЗапросКолво.Текст =
"ВЫБРАТЬ
| КОЛИЧЕСТВО(Номенклатура.Ссылка) КАК Всего
|ИЗ
| Справочник.Номенклатура КАК Номенклатура
|ГДЕ
| Номенклатура.Родитель В ИЕРАРХИИ(&Папка)";
ЗапросКолво.УстановитьПараметр("Папка", ВыбраннаяПапка);
ВыборкаКолво = ЗапросКолво.Выполнить().Выбрать();
ВыборкаКолво.Следующая();
ОбщееКоличество = ВыборкаКолво.Всего;
ПроцентВыборки = 15; // Данные от пользователя
КоличествоКВыборке = Целое(ОбщееКоличество * ПроцентВыборки / 100);
// 2. Формируем запрос с ограничением ПЕРВЫЕ
ТекстЗапроса = "ВЫБРАТЬ ПЕРВЫЕ " + Формат(КоличествоКВыборке, "ЧГ=0") + "
| Номенклатура.Ссылка КАК Ссылка
|ИЗ
| Справочник.Номенклатура КАК Номенклатура
|ГДЕ
| Номенклатура.Родитель В ИЕРАРХИИ(&Папка)
|УПОРЯДОЧИТЬ ПО
| Ссылка"; // Для истинной случайности здесь лучше использовать перемешивание в ТЗ
Важный нюанс: Если мы хотим идеальную случайность вне СКД, лучше выгрузить все ссылки во ТаблицуЗначений, добавить в нее колонку СлучайныйВес, заполнить ее в цикле с помощью ГенераторСлучайныхЧисел, отсортировать таблицу и затем методом Скопировать оставить нужное количество строк.
В сообщениях форума часто звучит вопрос: как совместить случайный отбор с условием отсутствия продаж? Проанализируем наиболее эффективный способ соединения таблиц.
Для решения этой задачи мы используем Левое Соединение справочника с оборотами регистра продаж. Если записи в регистре нет, значение ресурса будет NULL.
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| Номенклатура.Ссылка КАК Номенклатура
|ПОМЕСТИТЬ ВТ_Кандидаты
|ИЗ
| Справочник.Номенклатура КАК Номенклатура
|ГДЕ
| Номенклатура.ЭтоГруппа = ЛОЖЬ
| И Номенклатура.Родитель В ИЕРАРХИИ(&Группа);
|
|////////////////////////////////////////////////////////////////////////////////
|ВЫБРАТЬ
| ВТ_Кандидаты.Номенклатура КАК Номенклатура
|ИЗ
| ВТ_Кандидаты КАК ВТ_Кандидаты
| ЛЕВОЕ СОЕДИНЕНИЕ РегистрНакопления.ВыручкаИПродажи.Обороты(&НачПериода, &КонПериода, , ) КАК ПродажиОбороты
| ПО ВТ_Кандидаты.Номенклатура = ПродажиОбороты.АналитикаУчетаНоменклатуры.Номенклатура
|ГДЕ
| ПродажиОбороты.КоличествоОборот ЕСТЬ NULL";
Запрос.УстановитьПараметр("Группа", Объект.ПапкаДляОтбора);
Запрос.УстановитьПараметр("НачПериода", Объект.ДатаНачала);
Запрос.УстановитьПараметр("КонПериода", Объект.ДатаОкончания);
Результат = Запрос.Выполнить().Выгрузить();
// Далее применяем метод случайного перемешивания и отбора %
Обратите внимание, что использование ЛЕВОЕ СОЕДИНЕНИЕ с условием ЕСТЬ NULL работает быстрее и надежнее на больших объемах данных, чем использование конструкции НЕ В (ВЫБРАТЬ...), хотя современные оптимизаторы SQL часто приводят их к одинаковому плану выполнения.
Давайте соберем все знания в единую последовательность действий, которую можно внедрить в обработку или расширение (например, для документа «Установка цен номенклатуры» в УТ 11):
ТаблицаРезультат.Колонки.Добавить("СлучайныйИндекс", Новый ОписаниеТипов("Число"));
ГСЧ = Новый ГенераторСлучайныхЧисел;
Для Каждого СтрокаТЗ Из ТаблицаРезультат Цикл
СтрокаТЗ.СлучайныйИндекс = ГСЧ.СлучайноеЧисло(0, 100000);
КонецЦикла;
ТаблицаРезультат.Сортировать("СлучайныйИндекс Возр");
КоличествоСтрок = ТаблицаРезультат.Количество() * Процент / 100. Если строк больше, чем нужно, удаляем лишние с конца.Такой подход гарантирует, что пользователь каждый раз будет получать новый, действительно случайный список товаров, который соответствует всем заданным бизнес-критериям. Мы успешно обошли ограничения языка запросов и использовали возможности платформы 1С для решения сложной аналитической задачи.