Как в 1С 7.7 выбрать документы по номеру телефона клиента из подчиненного справочника?

Программист 1С v7.7 Управленческий учет Торговля и дистрибуция
← На главную

В разработке на платформе 1С 7.7 часто возникает задача выбрать документы по условию, которое находится не в самом документе, а в связанных данных. Классический пример — найти все накладные по номеру телефона клиента, если телефоны хранятся в отдельном подчиненном справочнике. Попытка решить это "в лоб" одним запросом обычно проваливается из-за ограничений языка запросов 1С 7.7.

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

Почему не работает простой запрос со связыванием таблиц?

Предположим, у нас есть документ Продажа с реквизитом Клиент и подчиненный справочник КлиентыТелефоны, где Владелец — это ссылка на элемент справочника Клиенты.

Первая интуитивная попытка — написать запрос, который связывает документ и подчиненный справочник напрямую:


ТекстЗапроса="
|ОбрабатыватьДокументы Все;
|_Клиент=Документ.Продажа.Клиент, Справочник.КлиентыТелефоны.Владелец;
|_Телефон=Справочник.КлиентыТелефоны.Наименование;
|Условие(_Телефон=тТелефон);
|Группировка Документ;
|";

Такой запрос, скорее всего, либо ничего не вернет, либо вернет все документы, проигнорировав условие. Причина кроется в ограничениях встроенного языка запросов 1С 7.7. Он не умеет выполнять произвольные соединения таблиц, как это делается в SQL с помощью JOIN. Он может "подтягивать" данные по прямым ссылкам (например, получить наименование клиента из документа), но не может построить сложную связь "Документ -> его Клиент -> подчиненный справочник этого клиента". Система не понимает, что Документ.Продажа.Клиент и Справочник.КлиентыТелефоны.Владелец должны быть связаны между собой.

Решение 1: Использование функции в условии (не рекомендуется)

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

Шаг 1. Создаем функцию проверки.

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


Функция ЕстьТелефон(аКлиент, аТелефон)
    С = СоздатьОбъект("Справочник.КлиентыТелефоны");
    С.ИспользоватьВладельца(аКлиент);
    Возврат С.НайтиПоНаименованию(аТелефон, 1, 1); // 1-искать точно, 1-искать по наименованию
КонецФункции

Шаг 2. Вызываем функцию в условии запроса.


ТекстЗапроса="
|ОбрабатыватьДокументы Все;
|_Клиент=Документ.Продажа.Клиент;
|_Лицо=Документ.Продажа.Лицо;
|Условие(ЕстьТелефон(_Клиент, тТелефон) = 1);
|Группировка Документ;
|";

Почему это плохо?

Этот метод будет работать, но катастрофически медленно на больших базах данных. Проблема в том, что для каждого документа продажи, который попадает в выборку запроса, система будет вызывать функцию ЕстьТелефон(). А эта функция, в свою очередь, каждый раз создает новый объект и выполняет отдельный запрос к базе данных. Если у вас 10 000 накладных, будет выполнено 10 000 дополнительных маленьких запросов, что приведет к огромной нагрузке и долгому ожиданию.

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

Решение 2: Двухэтапный запрос (правильный и эффективный подход)

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

Шаг 1. Находим всех клиентов с нужным номером телефона.

Сначала мы делаем простой запрос к подчиненному справочнику и находим всех владельцев (клиентов), у которых есть искомый телефон.


Запрос = СоздатьОбъект("Запрос");
ТекстЗапроса = "
|Период с Дата1 по Дата2;
|_Клиент = Справочник.КлиентыТелефоны.Владелец;
|_ТелефонЭлемент = Справочник.КлиентыТелефоны.ТекущийЭлемент;
|Условие(_ТелефонЭлемент.Наименование = тТелефон);
|Группировка _Клиент Без Групп;
|";

Если Запрос.Выполнить(ТекстЗапроса) = 0 Тогда
    Возврат;
КонецЕсли;

Шаг 2. Выгружаем найденных клиентов в СписокЗначений.

Результат первого запроса — это список уникальных клиентов. Мы собираем их в объект СписокЗначений, чтобы использовать его в следующем запросе.


СписокКлиентов = СоздатьОбъект("СписокЗначений");
Пока Запрос.Группировка(1) = 1 Цикл
    СписокКлиентов.ДобавитьЗначение(Запрос._Клиент);
КонецЦикла;

Шаг 3. Выбираем документы по списку клиентов.

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


ТекстЗапроса = "
|ОбрабатыватьДокументы Все;
|_Клиент = Документ.Продажа.Клиент;
|_Лицо = Документ.Продажа.Лицо;
|Условие(_Клиент В СписокКлиентов);
|Группировка Документ;
|";

Если Запрос.Выполнить(ТекстЗапроса) = 0 Тогда
    Возврат;
КонецЕсли;

// Далее обрабатываем результат
Пока Запрос.Группировка(1) = 1 Цикл
    // ... ваш код для вывода данных
КонецЦикла;

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

Альтернатива: Прямые запросы к SQL

Для баз, работающих в режиме SQL-сервера, существует еще более быстрый способ — прямые запросы с помощью внешних компонент (например, 1CPP.dll). Этот метод позволяет писать запросы на языке T-SQL, используя всю его мощь, включая INNER JOIN.

Примерная логика такого запроса (псевдокод):


SELECT
    Документы.IDDOC, Документы.DATE_TIME_IDDOC
FROM
    _1SJOURN AS Журнал
INNER JOIN
    DHXXXX AS Документы ON Журнал.IDDOC = Документы.IDDOC
INNER JOIN
    SCYYYY AS Телефоны ON Документы.Клиент_ID = Телефоны.PARENTEXT
WHERE
    Телефоны.DESCR = 'Нужный_Телефон'
    AND Журнал.IDDOCDEF = ZZZZ -- ID вида документа "Продажа"

Плюсы: максимальная производительность, так как вся обработка происходит на стороне SQL-сервера.

Минусы:

  1. Требует знания структуры таблиц базы данных 1С.
  2. Код становится непереносимым между DBF и SQL версиями.
  3. Требует использования внешних компонент.

Этот метод рекомендуется для высоконагруженных систем, где производительность является критическим фактором.

Итог: Для большинства повседневных задач в 1С 7.7 двухэтапный запрос является золотым стандартом — он эффективен, надежен и использует только встроенные средства платформы.

← На главную