Как отобрать случайный процент элементов справочника в 1С

Программист 1С v8.3 (Управляемые формы) 1С:Управление торговлей Управленческий учет Торговля и дистрибуция
← На главную

В практике разработчика 1С часто возникают задачи, требующие нестандартного подхода к выборке данных. Одной из таких задач является получение случайного набора элементов из справочника, при этом объем выборки должен составлять определенный процент от общего количества записей или фиксированное число. Например, это может понадобиться для проведения маркетинговых акций, инвентаризации или выборочной проверки цен. Стандартный язык запросов 1С не содержит встроенной функции RAND(), поэтому нам приходится использовать комбинацию программных и запросных методов. Рассмотрим подробно, как это реализовать.

Проблема псевдослучайности в запросах 1С

Первое, с чем мы сталкиваемся — это отсутствие прямой поддержки случайности в SQL-движке 1С. Если мы просто напишем ВЫБРАТЬ ПЕРВЫЕ 80, система вернет нам первые записи в том порядке, в котором они физически расположены в таблице или отсортированы оптимизатором. При повторном выполнении запроса мы получим тот же самый результат. Это не является случайной выборкой.

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

Способ 1: Использование СКД и функций общих модулей

Если задача стоит в рамках формирования отчета через Систему компоновки данных (СКД), мы можем задействовать мощный механизм вычисляемых полей. Проанализируем этот подход по шагам:

  1. Создание функции в общем модуле: Нам понадобится экспортная функция в общем модуле (с флагом «Вызов сервера»), которая будет возвращать случайное число.
    
    Функция ПолучитьСлучайноеЧисло() Экспорт
        ГСЧ = Новый ГенераторСлучайныхЧисел;
        Возврат ГСЧ.СлучайноеЧисло(0, 1000000);
    КонецФункции
    
  2. Настройка СКД: На закладке «Вычисляемые поля» добавим поле СлучайныйПорядок. В выражении укажем вызов нашей функции: МойОбщийМодуль.ПолучитьСлучайноеЧисло().
  3. Сортировка: На закладке «Настройки» перейдем в «Упорядочивание» и добавим наше новое поле.

При каждом формировании отчета СКД будет вызывать функцию для каждой строки, присваивать ей случайный вес и сортировать результат. Таким образом, состав «первых N элементов» будет меняться при каждом запуске.

Способ 2: Реализация отбора в процентах через программный код

Когда нам нужно отобрать именно процент (например, 15% от всей номенклатуры в папке «Колбаса»), алгоритм становится чуть сложнее. Выясним, как правильно рассчитать количество и получить данные.

Сначала нам нужно узнать общее количество элементов, подходящих под фильтры пользователя. Затем вычислить целевое число и подставить его в запрос. Рассмотрим пример кода:


// 1. Получаем общее количество подходящих элементов
ЗапросКолво = Новый Запрос;
ЗапросКолво.Текст = 
"ВЫБРАТЬ
|	КОЛИЧЕСТВО(Номенклатура.Ссылка) КАК Всего
|ИЗ
|	Справочник.Номенклатура КАК Номенклатура
|ГДЕ
|	Номенклатура.Родитель В ИЕРАРХИИ(&Папка)";
ЗапросКолво.УстановитьПараметр("Папка", ВыбраннаяПапка);
ВыборкаКолво = ЗапросКолво.Выполнить().Выбрать();
ВыборкаКолво.Следующая();

ОбщееКоличество = ВыборкаКолво.Всего;
ПроцентВыборки = 15; // Данные от пользователя
КоличествоКВыборке = Целое(ОбщееКоличество * ПроцентВыборки / 100);

// 2. Формируем запрос с ограничением ПЕРВЫЕ
ТекстЗапроса = "ВЫБРАТЬ ПЕРВЫЕ " + Формат(КоличествоКВыборке, "ЧГ=0") + "
|	Номенклатура.Ссылка КАК Ссылка
|ИЗ
|	Справочник.Номенклатура КАК Номенклатура
|ГДЕ
|	Номенклатура.Родитель В ИЕРАРХИИ(&Папка)
|УПОРЯДОЧИТЬ ПО
|	Ссылка"; // Для истинной случайности здесь лучше использовать перемешивание в ТЗ

Важный нюанс: Если мы хотим идеальную случайность вне СКД, лучше выгрузить все ссылки во ТаблицуЗначений, добавить в нее колонку СлучайныйВес, заполнить ее в цикле с помощью ГенераторСлучайныхЧисел, отсортировать таблицу и затем методом Скопировать оставить нужное количество строк.

Способ 3: Фильтрация товаров, которые не продавались

В сообщениях форума часто звучит вопрос: как совместить случайный отбор с условием отсутствия продаж? Проанализируем наиболее эффективный способ соединения таблиц.

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


Запрос = Новый Запрос;
Запрос.Текст = 
"ВЫБРАТЬ
|	Номенклатура.Ссылка КАК Номенклатура
|ПОМЕСТИТЬ ВТ_Кандидаты
|ИЗ
|	Справочник.Номенклатура КАК Номенклатура
|ГДЕ
|	Номенклатура.ЭтоГруппа = ЛОЖЬ
|	И Номенклатура.Родитель В ИЕРАРХИИ(&Группа);
|
|////////////////////////////////////////////////////////////////////////////////
|ВЫБРАТЬ
|	ВТ_Кандидаты.Номенклатура КАК Номенклатура
|ИЗ
|	ВТ_Кандидаты КАК ВТ_Кандидаты
|		ЛЕВОЕ СОЕДИНЕНИЕ РегистрНакопления.ВыручкаИПродажи.Обороты(&НачПериода, &КонПериода, , ) КАК ПродажиОбороты
|		ПО ВТ_Кандидаты.Номенклатура = ПродажиОбороты.АналитикаУчетаНоменклатуры.Номенклатура
|ГДЕ
|	ПродажиОбороты.КоличествоОборот ЕСТЬ NULL";

Запрос.УстановитьПараметр("Группа", Объект.ПапкаДляОтбора);
Запрос.УстановитьПараметр("НачПериода", Объект.ДатаНачала);
Запрос.УстановитьПараметр("КонПериода", Объект.ДатаОкончания);

Результат = Запрос.Выполнить().Выгрузить();
// Далее применяем метод случайного перемешивания и отбора %

Обратите внимание, что использование ЛЕВОЕ СОЕДИНЕНИЕ с условием ЕСТЬ NULL работает быстрее и надежнее на больших объемах данных, чем использование конструкции НЕ В (ВЫБРАТЬ...), хотя современные оптимизаторы SQL часто приводят их к одинаковому плану выполнения.

Комбинированный алгоритм (Итоговое решение)

Давайте соберем все знания в единую последовательность действий, которую можно внедрить в обработку или расширение (например, для документа «Установка цен номенклатуры» в УТ 11):

  1. Получаем базовый список: Выполняем запрос с учетом всех фильтров пользователя (папка, склад, остатки).
  2. Исключаем продажи: В этом же запросе отсекаем товары, по которым были движения за период.
  3. Выгружаем в ТЗ: Получаем результат запроса в таблицу значений.
  4. Перемешиваем программно:
    
    ТаблицаРезультат.Колонки.Добавить("СлучайныйИндекс", Новый ОписаниеТипов("Число"));
    ГСЧ = Новый ГенераторСлучайныхЧисел;
    Для Каждого СтрокаТЗ Из ТаблицаРезультат Цикл
        СтрокаТЗ.СлучайныйИндекс = ГСЧ.СлучайноеЧисло(0, 100000);
    КонецЦикла;
    ТаблицаРезультат.Сортировать("СлучайныйИндекс Возр");
    
  5. Обрезаем таблицу: Рассчитываем КоличествоСтрок = ТаблицаРезультат.Количество() * Процент / 100. Если строк больше, чем нужно, удаляем лишние с конца.
  6. Заполняем документ: Переносим оставшиеся в таблице товары в табличную часть документа.

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

← На главную