В нашей повседневной работе с 1С:Предприятием мы часто сталкиваемся с необходимостью интеграции с внешними системами и базами данных. Для этого в платформе предусмотрен механизм Внешних Источников Данных (ВИД). Мы можем подключать таблицы из внешних баз данных и работать с ними как с обычными объектами 1С. Однако, как показывает практика, при работе с объектными таблицами ВИД могут возникнуть неочевидные нюансы, особенно когда речь идет о ключевых полях. Сегодня мы подробно разберем одну из таких проблем: почему платформа 1С трактует значение 0 (ноль) в ключевом поле внешней таблицы как "пустую ссылку" и какие есть способы решения этой ситуации.
Представим ситуацию: мы подключили внешнюю базу данных (например, MSSQL) к нашей базе 1С как внешний источник данных. Во внешней БД существует таблица, которая содержит первичный ключ с автоинкрементом, и этот ключ по каким-то причинам начинает нумерацию с 0. Мы настроили эту таблицу в метаданных 1С как объектные данные, указав поле ключа (например, id) и установив для него свойство "Только чтение" в истину.
Допустим, в нашей внешней таблице есть следующие записи:
id = 0, price = 100id = 1, price = 200Теперь, если мы попытаемся найти эти записи программно, используя метод НайтиПоПолю(), мы обнаружим неожиданное поведение. Рассмотрим пример кода:
ИмяВИДа = "DWH"; // Например, имя вашего Внешнего Источника Данных
ИмяТаблицыВИДа = "Table1"; // Имя таблицы во Внешнем Источнике Данных
ИмяПоляСДанными = "price"; // Имя поля, по которому ищем
ТаблицаВИД = ВнешниеИсточникиДанных[ИмяВИДа].Таблицы[ИмяТаблицыВИДа];
// Поиск записи с price = 100 (которая имеет id = 0)
ЗначениеДляПоиска1 = 100;
ЗаписьВИДСсылка1 = ТаблицаВИД.НайтиПоПолю(ИмяПоляСДанными, ЗначениеДляПоиска1);
Сообщить("Пустая ссылка (id=0): " + ЗаписьВИДСсылка1.Пустая());
// Поиск записи с price = 200 (которая имеет id = 1)
ЗначениеДляПоиска2 = 200;
ЗаписьВИДСсылка2 = ТаблицаВИД.НайтиПоПолю(ИмяПоляСДанными, ЗначениеДляПоиска2);
Сообщить("Пустая ссылка (id=1): " + ЗаписьВИДСсылка2.Пустая());
// Поиск несуществующей записи с price = 777
ЗначениеДляПоиска3 = 777;
ЗаписьВИДСсылка3 = ТаблицаВИД.НайтиПоПолю(ИмяПоляСДанными, ЗначениеДляПоиска3);
Сообщить("Пустая ссылка (id=777): " + ЗаписьВИДСсылка3.Пустая());
В результате выполнения этого кода мы получим следующие сообщения:
Пустая ссылка (id=0): ДаПустая ссылка (id=1): НетПустая ссылка (id=777): ДаМы ожидаем, что первая запись (с id = 0) будет найдена и не будет трактоваться как пустая ссылка. Однако платформа 1С возвращает "Да" для поля Пустая(), как если бы запись не была найдена вовсе, что вводит в заблуждение и не позволяет корректно работать с данными. Это происходит из-за внутренней логики 1С.
Чтобы понять причину такого поведения, давайте разберем, как 1С работает с пустыми ссылками для объектных данных.
В 1С для большинства примитивных типов данных существуют значения по умолчанию, которые трактуются как "пустые". Например:
0."".01.01.0001 00:00:00.Когда мы объявляем таблицу внешнего источника данных как "объектные данные", платформа 1С ожидает, что ее ключевое поле будет вести себя как "ссылочный тип" 1С. Пустая ссылка является неотъемлемой частью концепции ссылочных типов в 1С, и платформа реализует ее именно через пустое значение типа ключевого поля. Таким образом, если поле ключа имеет числовой тип и содержит значение 0, 1С интерпретирует его как пустую ссылку.
Важно не путать "пустую ссылку" в 1С с NULL в базах данных. NULL в СУБД обозначает отсутствие значения, тогда как пустая ссылка в 1С — это конкретное, хоть и "пустое" по значению, представление ссылки. Платформа 1С иногда может некорректно обрабатывать NULL значения из внешних источников, заменяя их на 0 для числовых полей или 01.01.1753 для даты в некоторых случаях.
Контракт метода НайтиПоПолю(), который мы использовали, фактически возвращает пустую ссылку в двух случаях:
0 для числа), которое задано для этого ключа в метаданных конфигурации.Разберем несколько подходов, которые помогут нам корректно работать с записями, имеющими id = 0, во внешних источниках данных.
Это один из наиболее эффективных способов, если мы хотим сохранить возможность работать с таблицей ВИД как с объектными данными и использовать стандартные механизмы 1С, такие как НайтиПоПолю(), ссылки на эти объекты в других объектах конфигурации, а также интерактивное редактирование через формы.
GUID, ExternalID или просто другой числовой столбец, гарантированно не содержащий 0). Заполните ее уникальными значениями для всех записей.id укажите в качестве поля ключа новую созданную колонку.Преимущества:
НайтиПоПолю(), начнет работать корректно, так как новое поле ключа не будет содержать значения, трактуемые как "пустая ссылка".Недостатки: Требует модификации внешней структуры данных и, возможно, перенастройки существующих интеграций, если они опирались на поле id как на первичный ключ.
Мы должны помнить, что самовозрастающий первичный числовой ключ в MSSQL по умолчанию нумеруется с 1 при создании новой таблицы. Ситуация с id = 0 чаще всего возникает, когда этот параметр специально изменен (identity seed = 0).
Если модификация внешней таблицы невозможна или нежелательна, мы можем использовать явные запросы 1С для чтения данных из внешнего источника. Этот подход позволяет обойти проблемы объектной модели при поиске, но имеет ограничения при манипулировании данными.
Запрос для выборки данных.id = 0.Рассмотрим пример, как мы можем получить данные с помощью запроса:
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| ТаблицаВИД.id КАК id,
| ТаблицаВИД.price КАК price
|ИЗ
| ВнешнийИсточникДанных.DWH.Таблица1 КАК ТаблицаВИД
|ГДЕ
| ТаблицаВИД.price = &ЗначениеДляПоиска";
Запрос.УстановитьПараметр("ЗначениеДляПоиска", 100); // Поиск записи с price = 100
РезультатЗапроса = Запрос.Выполнить().Выгрузить();
Если РезультатЗапроса.Количество() > 0 Тогда
Сообщить("Запись найдена через запрос. id = " + РезультатЗапроса[0].id + ", price = " + РезультатЗапроса[0].price);
Иначе
Сообщить("Запись не найдена через запрос.");
КонецЕсли;
// Поиск записи с price = 200
Запрос.УстановитьПараметр("ЗначениеДляПоиска", 200);
РезультатЗапроса = Запрос.Выполнить().Выгрузить();
Если РезультатЗапроса.Количество() > 0 Тогда
Сообщить("Запись найдена через запрос. id = " + РезультатЗапроса[0].id + ", price = " + РезультатЗапроса[0].price);
Иначе
Сообщить("Запись не найдена через запрос.");
КонецЕсли;
// Поиск несуществующей записи с price = 777
Запрос.УстановитьПараметр("ЗначениеДляПоиска", 777);
РезультатЗапроса = Запрос.Выполнить().Выгрузить();
Если РезультатЗапроса.Количество() > 0 Тогда
Сообщить("Запись найдена через запрос. id = " + РезультатЗапроса[0].id + ", price = " + РезультатЗапроса[0].price);
Иначе
Сообщить("Запись не найдена через запрос.");
КонецЕсли;
Преимущества:
id = 0.Недостатки:
id = 0, чтобы, например, изменить ее поле price и записать обратно.Таким образом, если нам требуется лишь получить данные, включая записи с id = 0, и не нужно их модифицировать через объектную модель 1С, то явные запросы — это хороший выбор. Если же нам нужна возможность манипулировать данными и использовать ссылочную модель, то следует рассмотреть первый вариант.
Если функционал "объектных данных" нам не критичен, и мы готовы работать с таблицей ВИД как с набором данных, не имеющим в 1С прямой "ссылочной" сущности, мы можем изменить тип таблицы в метаданных.
Преимущества:
id = 0 как пустой ссылки, поскольку понятие "пустой ссылки" перестает быть применимым к такой таблице.id = 0, через запросы или другие методы чтения.Недостатки:
НайтиПоПолю() и создание объекта таблицы больше не будут доступны или будут работать иначе.Этот подход может быть приемлем, если таблица ВИД используется исключительно для импорта данных или для отображения информации, без необходимости создания ссылок на ее записи или сложной объектной манипуляции в рамках 1С.
При работе с внешними источниками данных мы также должны учитывать следующие моменты:
ТолькоЧтение, РазрешитьNull и ЗначениеЗаполнения.0 или "объект не найден", даже если запись в БД фактически создана. Это еще один аспект, подчеркивающий сложности работы с ключевыми полями.Проблема трактовки id = 0 как пустой ссылки в объектных таблицах внешних источников данных в 1С является важным нюансом, который необходимо учитывать при проектировании интеграций. Мы рассмотрели несколько решений:
Выбор конкретного подхода зависит от требований к работе с данными и возможностью внесения изменений во внешнюю структуру. Всегда тщательно анализируйте свои потребности и возможные последствия каждого из решений, чтобы обеспечить надежную и эффективную работу системы.