При изучении программирования в системе 1С, особенно на примере учебных пособий (например, знаменитого учебника М.Г. Радченко), начинающие разработчики часто сталкиваются с ошибкой «Таблица не найдена» в тех случаях, когда обращаются ко второй части пакетного запроса или используют несколько объектов запроса с временными таблицами. Рассмотрим ситуацию, когда в модуле объекта документа ОказаниеУслуги при выполнении обработчика ОбработкаПроведения система выдает ошибку на методе Запрос2.Выполнить().
Проанализируем исходный код, приведенный в вопросе. Ошибка {(9, 2)}: Таблица не найдена "НоменклатураДокумента" прямо указывает на то, что во втором запросе система не «видит» таблицу, которую мы создали и поместили в оперативную память в первом запросе. Чтобы два разных объекта типа Запрос могли использовать общее хранилище временных таблиц, им должен быть назначен один и тот же МенеджерВременныхТаблиц.
Посмотрим на строку кода автора:
Запрос2МенеджерВременныхТаблиц = МенеджерВТ;
В этом месте кроется критическая ошибка. Программист просто создал новую переменную с длинным именем Запрос2МенеджерВременныхТаблиц и присвоил ей ссылку на менеджер. Однако свойство самого объекта Запрос2 осталось незаполненным. Для исправления ситуации необходимо обязательно ставить точку, обращаясь к свойству объекта:
// Правильное присвоение менеджера временных таблиц
Запрос2.МенеджерВременныхТаблиц = МенеджерВТ;
После этого исправления Запрос2 получит доступ к таблице НоменклатураДокумента, и ошибка выполнения метода Выполнить() исчезнет.
Даже если мы исправим синтаксическую ошибку, алгоритм проведения может работать некорректно или не выдавать нужных данных. Проанализируем, как автор пытается получить данные для цикла:
РезультатЗапроса = Запрос.Выполнить(); // Это результат первого запроса (помещение во ВТ)
...
Результат = Запрос2.Выполнить(); // Это результат второго запроса (выборка с остатками)
ВыборкаДетальныеЗаписи = РезультатЗапроса.Выбрать(); // ОШИБКА ЗДЕСЬ
Выясним причину проблемы: РезультатЗапроса — это результат выполнения ПЕРВОГО запроса. Поскольку первый запрос заканчивается инструкцией ПОМЕСТИТЬ, его результат по сути пуст (он не содержит строк для обхода, он просто сообщает системе, что данные успешно помещены в менеджер). Чтобы получить данные, соединенные с остатками, нам нужно делать выборку из Результат (результата второго запроса).
Правильный вариант кода должен выглядеть так:
ВыборкаДетальныеЗаписи = Результат.Выбрать();
В обсуждении на форуме возник спор о том, как правильно обращаться к полям таблиц. Рассмотрим этот момент подробнее. В языке запросов 1С существует понятие псевдонима (ALIAS). Когда мы пишем:
ИЗ Документ.ОказаниеУслуги.ПереченьНоменклатуры КАК ОказаниеУслугиПереченьНоменклатуры
Мы говорим системе, что внутри этого конкретного запроса мы будем обращаться к таблице ТЧ документа по имени ОказаниеУслугиПереченьНоменклатуры. Это удобно, когда нужно соединять несколько таблиц одного типа.
Однако важно помнить: когда мы используем ПОМЕСТИТЬ НоменклатураДокумента, мы создаем совершенно новую временную таблицу. Во втором запросе она уже является самостоятельным источником данных. Поэтому обращение НоменклатураДокумента.Номенклатура во втором запросе абсолютно корректно, независимо от того, какие псевдонимы использовались в первом запросе.
Современная методика разработки в 1С рекомендует не создавать несколько объектов Запрос и вручную связывать их через менеджер, а использовать **пакетные запросы**. Это повышает производительность и делает код более читаемым. Рассмотрим, как объединить два запроса в один пакет:
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| ТЧ.Номенклатура,
| ТЧ.Номенклатура.ВидНоменклатуры КАК ВидНоменклатуры,
| СУММА(ТЧ.Количество) КАК КоличествоВДокументе,
| СУММА(ТЧ.Сумма) КАК СуммаВДокументе
|ПОМЕСТИТЬ НоменклатураДокумента
|ИЗ
| Документ.ОказаниеУслуги.ПереченьНоменклатуры КАК ТЧ
|ГДЕ
| ТЧ.Ссылка = &Ссылка
|СГРУППИРОВАТЬ ПО
| ТЧ.Номенклатура, ТЧ.Номенклатура.ВидНоменклатуры
|;
|////////////////////////////////////////////////////////////////////////////////
|ВЫБРАТЬ
| НоменклатураДокумента.Номенклатура,
| НоменклатураДокумента.ВидНоменклатуры,
| НоменклатураДокумента.КоличествоВДокументе,
| НоменклатураДокумента.СуммаВДокументе,
| ЕСТЬNULL(СтоимостьОстатки.СтоимостьОстаток, 0) КАК Стоимость,
| ЕСТЬNULL(ОстаткиОстатки.КоличествоОстаток, 0) КАК Количество
|ИЗ
| НоменклатураДокумента КАК НоменклатураДокумента
|ЛЕВОЕ СОЕДИНЕНИЕ РегистрНакопления.СтоимостьМатериалов.Остатки(&МоментВремени, ) КАК СтоимостьОстатки
| ПО НоменклатураДокумента.Номенклатура = СтоимостьОстатки.Материал
|ЛЕВОЕ СОЕДИНЕНИЕ РегистрНакопления.ОстаткиМатериалов.Остатки(&МоментВремени, ) КАК ОстаткиОстатки
| ПО НоменклатураДокумента.Номенклатура = ОстаткиОстатки.Материал";
Запрос.УстановитьПараметр("Ссылка", Ссылка);
Запрос.УстановитьПараметр("МоментВремени", МоментВремени());
// Выполняем весь пакет сразу
РезультатПакета = Запрос.ВыполнитьПакет();
// Нас интересует результат последнего запроса в пакете (индекс 1)
Выборка = РезультатПакета[1].Выбрать();
МоментВремени(). Если оставить параметры пустыми, 1С получит остатки на текущую секунду, а не на позицию документа в ленте времени, что приведет к ошибкам при перепроведении «задним числом».Движения.ОстаткиМатериалов.Записать();.Выборка.Количество <> 0 перед делением стоимости на количество для расчета себестоимости единицы. Это предотвратит аварийное завершение программы при отсутствии остатков на складе.Применяя данные рекомендации, мы не только исправим ошибку «Таблица не найдена», но и сделаем код более надежным, быстрым и соответствующим стандартам разработки «1С:Предприятие».