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