При прямой работе с базой данных 1С на уровне СУБД MS SQL Server разработчики нередко сталкиваются с ситуацией, когда простейший запрос с условием отбора по дате завершается ошибкой: «Сообщение 242, Преобразование типа данных varchar в тип данных datetime привело к выходу значения за пределы диапазона». Проанализируем, почему возникает эта ошибка при передаче строкового литерала даты, как правильно формировать условия по периоду и почему прямое удаление строк из таблиц движений регистров накопления через SQL приводит к повреждению целостности базы.
Рассмотрим механизм интерпретации строковых литералов в T-SQL. Когда мы пишем условие вида dbo._AccumRg10048._Period > '2016-02-21 09:00:02.000', СУБД пытается неявно привести строку к типу datetime.
Однако строковый формат с дефисами (YYYY-MM-DD) в MS SQL Server не является универсальным для типа datetime: его парсинг зависит от региональных настроек сессии, определяемых параметрами SET LANGUAGE и SET DATEFORMAT. Если в сеансе действует порядок даты mdy (месяц-день-год) или ydm (год-день-месяц), число 21 начинает восприниматься сервером как номер месяца, что и приводит к выходу за допустимый диапазон значений.
Кроме того, в базах 1С на уровне параметров создания информационной базы может быть задано смещение дат на 2000 лет. В таком случае даты 2016 года физически записываются в таблицы как 4016 год, что также необходимо учитывать при анализе сырых данных.
Разберем по шагам, как правильно составить запрос к таблице 1С в среде SQL Server Management Studio, чтобы избежать ошибок региональных стандартов.
SELECT *
FROM dbo._AccumRg10048
WHERE _Period > '2016-02-21T09:00:02.000';
SELECT *
FROM dbo._AccumRg10048
WHERE _Period > '20160221 09:00:02';
120 жестко задает стандарт ODBC canonical (yyyy-mm-dd hh:mi:ss):
SELECT *
FROM dbo._AccumRg10048
WHERE _Period > CONVERT(datetime, '2016-02-21 09:00:02', 120);
Выясним причину, по которой прямое удаление строк оператором DELETE из таблицы _AccumRg... категорически не рекомендуется. В платформе 1С регистр накопления физически состоит не из одной таблицы, а как минимум из двух:
_AccumRg10048), где хранятся сами записи регистратора._AccumRgT..., _AccumRgOpt...), где платформа хранит рассчитанные остатки и обороты по периодам.Если удалить строки напрямую из таблицы движений через SQL, в таблицах итогов останутся прежние накопленные значения. Это вызовет расхождение между детальными записями и отчетами по остаткам, нарушит контроль отрицательных остатков и приведет к логическому сбою конфигурации.
Когда документ невозможно отменить штатно из-за ошибок в алгоритмах проведения или строгих проверок при записи, безопаснее всего очистить движения программно в режиме загрузки данных. В этом режиме отключаются платформенные и прикладные проверки, но целостность таблиц итогов сохраняется платформой автоматически.
Посмотрим на пример кода очистки набора записей регистра:
НаборЗаписей = РегистрыНакопления.СебестоимостьТоваров.СоздатьНаборЗаписей();
НаборЗаписей.Отбор.Регистратор.Установить(СсылкаНаДокумент);
// Включаем режим обмена данными для обхода обработчиков и проверок
НаборЗаписей.ОбменДанными.Загрузка = Истина;
// Записываем пустой набор: движения удалятся, а итоги пересчитаются платформой
НаборЗаписей.Записать();
Если требуется снять документ с проведения целиком в обход блокировок и проверок, воспользуемся аналогичным приемом через объект документа:
ДокументОбъект = СсылкаНаДокумент.ПолучитьОбъект();
ДокументОбъект.ОбменДанными.Загрузка = Истина;
ДокументОбъект.Проведен = Ложь;
ДокументОбъект.Записать();
Также перед внесением изменений убедимся, что документ не заблокирован регламентными процедурами (например, расчетом себестоимости или закрытием месяца). При необходимости завершим конфликтующие сеансы через консоль администрирования кластера серверов 1С.
Если ручное удаление записей в СУБД уже произошло, необходимо восстановить синхронизацию таблиц итогов с фактическими движениями. Для этого выполним пересчет итогов:
РегистрыНакопления.СебестоимостьТоваров.ПересчитатьИтоги();