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

Программист 1С v8.3 (Управляемые формы) IT и автоматизация бизнеса
← На главную

Одной из классических задач в разработке на платформе 1С является выборка случайных данных из базы. Проблема заключается в том, что язык запросов 1С не содержит встроенной функции RAND() или NEWID(), которые доступны в «чистом» SQL. В этой статье мы подробно разберем, как обойти это ограничение, проанализируем различные методики и выберем наиболее эффективные решения для разных ситуаций.

Метод 1: Сортировка по случайному числу во временной таблице

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

Рассмотрим пошагово этот алгоритм:

  1. Выгружаем необходимые ссылки из справочника в ТаблицаЗначений.
  2. Добавляем в таблицу колонку «СлучайноеЧисло» с типом Число.
  3. Заполняем эту колонку с помощью объекта ГенераторСлучайныхЧисел.
  4. Передаем таблицу обратно в запрос в качестве параметра.
  5. В запросе выбираем ПЕРВЫЕ N элементов, упорядочивая их по колонке со случайным числом.

Пример реализации кода:


// Шаг 1: Получаем все ссылки (например, контрагентов)
Запрос = Новый Запрос("ВЫБРАТЬ Ссылка ИЗ Справочник.Контрагенты ГДЕ НЕ ЭтоГруппа");
ТЗ = Запрос.Выполнить().Выгрузить();

// Шаг 2-3: Добавляем случайности
ТЗ.Колонки.Добавить("Сортировка", Новый ОписаниеТипов("Число"));
ГСЧ = Новый ГенераторСлучайныхЧисел();
Для Каждого Строка Из ТЗ Цикл
    Строка.Сортировка = ГСЧ.СлучайноеЧисло(0, 1000000);
КонецЦикла;

// Шаг 4-5: Финальный отбор в запросе
ЗапросСлуч = Новый Запрос;
ЗапросСлуч.Текст = 
    "ВЫБРАТЬ ПЕРВЫЕ 5
    |   ВТ.Ссылка
    |ИЗ
    |   &ТЗ КАК ВТ
    |УПОРЯДОЧИТЬ ПО
    |   ВТ.Сортировка";
ЗапросСлуч.УстановитьПараметр("ТЗ", ТЗ);
Результат = ЗапросСлуч.Выполнить().Выгрузить();

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

Метод 2: Поиск «ближайшего соседа» по случайному GUID

Этот способ более изящен и позволяет избежать выгрузки всего справочника в память. Идея заключается в том, чтобы сгенерировать случайный УникальныйИдентификатор, создать на его основе «пустую» ссылку и в запросе найти элемент справочника, чья ссылка больше или равна нашей сгенерированной.

Рассмотрим, как это работает программно. Ссылки в базе данных 1С (GUID) имеют определенный порядок. Если мы «ткнем пальцем» в случайное место этого порядка, мы с высокой вероятностью попадем между двумя существующими элементами.

Алгоритм действий следующий:


// Создаем таблицу со случайными ссылками
ПервоначальнаяТаблица = Новый ТаблицаЗначений;
ПервоначальнаяТаблица.Колонки.Добавить("Товар", Новый ОписаниеТипов("СправочникСсылка.Номенклатура"));

НадоСлЭлементов = 5;
Для СчЭлементов = 1 По НадоСлЭлементов Цикл
    // Генерируем абсолютно случайный идентификатор
    НовыйГУИД = Новый УникальныйИдентификатор;
    НоменклатураСсылка = Справочники.Номенклатура.ПолучитьСсылку(НовыйГУИД);
    ПервоначальнаяТаблица.Добавить().Товар = НоменклатураСсылка;
КонецЦикла;

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

Запрос.УстановитьПараметр("НедействительныеСсылки", ПервоначальнаяТаблица);
РезЗапроса = Запрос.Выполнить().Выгрузить();

Проанализируем этот подход. Мы используем условие ВТ_Ключи.СлучайныйКлюч >= Номенклатура.Ссылка. Это позволяет запросу быстро найти подходящую запись, используя индекс по ссылке. Однако, как отмечалось в обсуждении, этот метод имеет математический дефект: вероятность выбора элемента пропорциональна «расстоянию» между его GUID и GUID предыдущего элемента. В практических задачах это редко является критичным, но для серьезных статистических исследований стоит иметь это в виду.

Метод 3: Математический расчет псевдослучайного числа в запросе

Если нам запрещено использовать внешние таблицы значений и мы хотим решить задачу «чистым» запросом, мы можем использовать Линейный конгруэнтный метод генерации псевдослучайных чисел. Для этого нам понадобится какой-то числовой параметр элемента (например, код, если он числовой, или преобразованная часть наименования) и переданное в запрос «зерно» (Seed).

Формула выглядит примерно так: (A * ВходноеЧисло + B) % M. В запросе 1С остаток от деления можно имитировать через использование функции ВЫРАЗИТЬ.

Рассмотрим пример, где мы используем числовой код контрагента и переданный параметр времени в качестве источника энтропии:


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

Выясним причину использования функции ВЫРАЗИТЬ. Она позволяет отсечь дробную часть или изменить разрядность числа, что при умножении на большое простое число дает эффект «хаотичного» распределения значений, по которым затем выполняется сортировка.

Метод 4: Использование хэш-функции (MD5 или аналоги)

Существует продвинутая методика, основанная на расчете хэш-функции прямо в запросе. Мы можем проанализировать строковые реквизиты объекта (Код, Наименование, ИНН) и с помощью цепочки функций ПОДСТРОКА и математических операций над кодами символов получить уникальный хэш. Если к этой строке добавить текущее время, хэш будет меняться при каждом запуске.

Хотя полная реализация MD5 в запросе крайне громоздка, упрощенный алгоритм выглядит так:

  1. Складываем строки основных реквизитов.
  2. Для каждого символа сопоставляем его числовое значение (через заранее подготовленную временную таблицу кодировки).
  3. Суммируем эти значения с весовыми коэффициентами.
  4. Сортируем результат.

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

Сравнение и итоги

Проанализировав все способы, можно сделать следующие выводы:

При выборе метода всегда учитывайте объем данных. Помните, что любая нумерация элементов в запросе (через соединение таблицы самой с собой) — это тяжелая операция, которая растет квадратично от количества записей. Старайтесь избегать её в задачах по получению случайных данных.

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

← На главную