Работа с базой данных 1С:Предприятие 7.7 в SQL-варианте таит в себе немало загадок, особенно когда дело касается внутренней структуры таблиц и поддержания ссылочной целостности. Одной из наиболее частых проблем, с которой сталкиваются администраторы и разработчики крупных баз данных, является рассинхронизация времени документов между основным журналом _1SJOURN и служебной таблицей связей _1SCRDOC. В дополнение к этому при анализе таблиц СУБД часто обнаруживаются странные записи с незаполненными или специфическими идентификаторами метаданных (MDID), у которых поле значения родителя (PARENTVAL) заполнено символом 'U'.
В этой статье мы подробно разберем, как устроена таблица перекрестных ссылок _1SCRDOC, выясним назначение всех типов записей в ней (включая загадочные строки с маркером 'U'), проанализируем причины расхождения дат документов и разработаем пошаговый алгоритм безопасного автоматического исправления этих данных напрямую через СУБД MS SQL Server.
Таблица _1SCRDOC является одной из важнейших системных таблиц в архитектуре 1С 7.7. Ее основное назначение — обеспечивать быструю выборку документов по связям подчиненности, графам отбора и дополнительным журналам. Без этой таблицы СУБД приходилось бы выполнять тяжелые сканирования таблиц документов при открытии любого отбираемого журнала или дерева подчиненности.
Проанализируем структуру основных колонок этой таблицы:
IDDOC). Именно по этому полю строятся индексы для сортировки документов в журналах.IDDOC).Рассмотрим подробнее три принципиально разные категории записей, которые могут находиться в этой таблице в зависимости от значения поля MDID:
Для таких записей поле MDID всегда принимает значение 0. В поле PARENTVAL записывается идентификатор родительского документа с системным префиксом (например, O1 + ID вида документа + ID самого документа). Благодаря этим записям платформа мгновенно строит дерево подчиненности для любого документа.
Если в конфигурации созданы графы отбора (например, «Контрагент», «Договор», «Склад»), то для каждого значения графы отбора, указанного в документе, в таблицу _1SCRDOC пишется строка. При этом MDID равен внутреннему идентификатору этой графы отбора из метаданных (в вашей конфигурации это могут быть числа вроде 1019, 4265 и т.д.). В поле PARENTVAL записывается конкретная ссылка на элемент справочника или документ, по которому выполняется фильтрация.
Это и есть те самые «странные» записи, которые вызывают массу вопросов у разработчиков. Когда в конфигураторе создается дополнительный журнал, в свойствах которого указывается состав входящих в него документов, платформа должна где-то фиксировать принадлежность конкретных документов к этому журналу. Для этого используется таблица _1SCRDOC. В этом случае:
MDID содержит идентификатор этого дополнительного журнала из метаданных.PARENTVAL всегда принимает фиксированное текстовое значение 'U ' (буква U и последующие пробелы до конца длины поля). Это системная константа-маркер платформы 1С 7.7, сигнализирующая о том, что запись описывает факт вхождения документа в дополнительный журнал.Таким образом, наличие записей с PARENTVAL = 'U' — это абсолютно штатное поведение платформы, отражающее структуру ваших дополнительных журналов документов. Удалять эти записи «вслепую» нельзя, так как из дополнительных журналов пропадут соответствующие документы!
Теперь разберем природу возникновения рассинхронизации. Почему же периодически расходятся значения DATE_TIME_IDDOC в общем журнале _1SJOURN и CHILD_DATE_TIME_IDDOC в таблице связей _1SCRDOC, а также в таблицах операций _1SOPER и проводок _1SENTRY?
Основной источник проблемы кроется в механизме позиционирования документов внутри одной секунды, реализованном в 1С 7.7. Когда пользователь записывает или проводит документ, платформа пытается автоматически распределить документы, созданные в одну и ту же секунду, сдвигая их время вперед на микросекундном уровне (в терминах 1С 7.7 это выражается в изменении текстового представления времени в тридцатишестиричной системе счисления, например, превращение 23:59:59 из 20231031EAEAY8 в новое смещенное значение 20231031EAGG40).
В процессе такого автоматического сдвига времени платформа инициирует транзакцию обновления данных во всех связанных таблицах. Однако в условиях высокой конкурентной работы, блокировок таблиц на уровне СУБД MS SQL или аварийного завершения сессий пользователей могут возникать ситуации, когда:
_1SJOURN._1SCRDOC, _1SOPER, _1SENTRY) блокируется или завершается с ошибкой выполнения на стороне СУБД.В результате мы получаем «битую» транзакцию на уровне логики 1С: в общем журнале у документа новое время, а в проводках и подчиненных связях осталось старое. Самое неприятное в этой ситуации заключается в том, что при попытке выполнить ТИИ (Тестирование и исправление), реструктуризацию таблиц или просто при создании новой графы отбора Конфигуратор считывает данные из _1SJOURN и пытается пересоздать записи в _1SCRDOC. Натыкаясь на несовпадение дат и уже существующие ключи, СУБД выбрасывает критическую ошибку:
SQL State: 23000, Native: 1505 (CREATE UNIQUE INDEX terminated because a duplicate key was found). Дальнейшая работа по обновлению метаданных или тестированию базы становится невозможной.
Для того чтобы исправить сложившуюся ситуацию без монопольного доступа и многочасового ожидания полного пересчета служебных данных в Конфигураторе, мы применим точечные SQL-скрипты. Рассмотрим этот процесс по шагам.
Прежде чем что-то менять, нам нужно локализовать проблему и оценить масштаб расхождений. Выполним проверочный запрос, который находит расхождения времени между общим журналом документов _1SJOURN, таблицей операций _1SOPER и таблицей бухгалтерских проводок _1SENTRY. За эталон мы всегда принимаем время из общего журнала _1SJOURN.
SELECT
o.docid AS [IDDOC],
j.docno AS [НомерДокумента],
j.date_time_iddoc AS [Время_в_Журнале],
o.date_time_docid AS [Время_в_Операциях],
s.date_time_docid AS [Время_в_Проводках]
FROM _1SOPER o (NOLOCK)
LEFT JOIN _1sentry s (NOLOCK) ON s.docid = o.docid
INNER JOIN _1SJOURN j (NOLOCK) ON j.iddoc = o.docid
WHERE (s.date_time_docid IS NOT NULL AND s.date_time_docid <> j.date_time_iddoc)
OR o.date_time_docid <> j.date_time_iddoc;
Если этот запрос вернул строки, значит, в вашей базе данных действительно есть документы с нарушенной целостностью времени проведения. Их необходимо вылечить.
Выполним принудительное обновление времени в таблицах _1SOPER и _1SENTRY. Мы подтянем актуальное время проведения из главного журнала _1SJOURN. Обратите внимание, что выполнять эти операции рекомендуется в периоды минимальной активности пользователей, предварительно сделав резервную копию базы данных.
-- Корректируем время в таблице бухгалтерских проводок
UPDATE _1sentry
SET _1sentry.DATE_TIME_DOCID = j.DATE_TIME_IDDOC
FROM _1sentry s
INNER JOIN _1SJOURN j ON s.DOCID = j.IDDOC
WHERE s.DATE_TIME_DOCID <> j.DATE_TIME_IDDOC;
-- Корректируем время в таблице операций (документов)
UPDATE _1SOPER
SET _1SOPER.DATE_TIME_DOCID = j.DATE_TIME_IDDOC
FROM _1SOPER o
INNER JOIN _1SJOURN j ON o.DOCID = j.IDDOC
WHERE o.DATE_TIME_DOCID <> j.DATE_TIME_IDDOC;
Теперь переходим к нашей целевой таблице _1SCRDOC. Нам необходимо обновить поле времени дочернего документа CHILD_DATE_TIME_IDDOC, чтобы оно строго соответствовало времени проведения этого документа из таблицы общего журнала _1SJOURN.
UPDATE _1scrdoc
SET _1scrdoc.CHILD_DATE_TIME_IDDOC = j.DATE_TIME_IDDOC
FROM _1scrdoc c
INNER JOIN _1SJOURN j ON c.CHILDID = j.IDDOC
WHERE c.CHILD_DATE_TIME_IDDOC <> j.DATE_TIME_IDDOC;
Этот апдейт вернет правильное время для всех граф отборов, структуры подчиненности и вхождений в дополнительные журналы.
После проведения массовой синхронизации времени в таблице _1SCRDOC может возникнуть побочный эффект. Из-за того, что ранее в таблице существовали записи с одинаковыми ключевыми полями (CHILDID, MDID, PARENTVAL), но отличающиеся только устаревшим временем в поле CHILD_DATE_TIME_IDDOC, после обновления эти записи станут абсолютно идентичными дубликатами.
Наличие полных дубликатов строк нарушает уникальность индексов и приведет к ошибке при первой же реструктуризации базы данных в Конфигураторе. Нам необходимо избавиться от дублей, оставив ровно по одной уникальной строке на каждую связку метаданных и документов. Для этого выполним следующий запрос удаления:
DELETE FROM _1SCRDOC
WHERE ROW_ID NOT IN (
SELECT MIN(ROW_ID)
FROM _1SCRDOC
GROUP BY CHILDID, MDID, PARENTVAL
);
В данном случае колонка ROW_ID (автоинкрементное поле, присутствующее в таблицах 1С на MS SQL) выступает гарантом того, что мы сохраним ровно по одной уникальной строке для каждой связи, физически удалив все лишние дубликаты, которые образовались в результате сбоев транзакций.
Для того чтобы забыть о ручном исправлении подобных ошибок и исключить возникновение критических ситуаций во время планового обновления конфигурации, рекомендуется настроить автоматическое «самолечение» базы данных. Для этого мы можем объединить все вышеперечисленные операции в единый транзакционный скрипт и добавить его в регламентное задание (Job) на SQL Server.
Создайте новое регламентное задание в MS SQL Server Agent со следующим шагом выполнения (класс расписания — ежедневно, например, в 03:00 ночи):
BEGIN TRANSACTION;
BEGIN TRY
-- 1. Исправление времени в проводках
UPDATE s
SET s.DATE_TIME_DOCID = j.DATE_TIME_IDDOC
FROM _1sentry s
INNER JOIN _1SJOURN j ON s.DOCID = j.IDDOC
WHERE s.DATE_TIME_DOCID <> j.DATE_TIME_IDDOC;
-- 2. Исправление времени в операциях
UPDATE o
SET o.DATE_TIME_DOCID = j.DATE_TIME_IDDOC
FROM _1SOPER o
INNER JOIN _1SJOURN j ON o.DOCID = j.IDDOC
WHERE o.DATE_TIME_DOCID <> j.DATE_TIME_IDDOC;
-- 3. Исправление времени в таблице подчиненности и отборов
UPDATE c
SET c.CHILD_DATE_TIME_IDDOC = j.DATE_TIME_IDDOC
FROM _1scrdoc c
INNER JOIN _1SJOURN j ON c.CHILDID = j.IDDOC
WHERE c.CHILD_DATE_TIME_IDDOC <> j.DATE_TIME_IDDOC;
-- 4. Очистка дубликатов в таблице связей
DELETE FROM _1SCRDOC
WHERE ROW_ID NOT IN (
SELECT MIN(ROW_ID)
FROM _1SCRDOC
GROUP BY CHILDID, MDID, PARENTVAL
);
COMMIT TRANSACTION;
PRINT 'Синхронизация таблиц документов выполнена успешно.';
END TRY
BEGIN CATCH
ROLLBACK TRANSACTION;
DECLARE @ErrorMessage NVARCHAR(4000) = ERROR_MESSAGE();
RAISERROR(@ErrorMessage, 16, 1);
END CATCH;
Использование конструкции BEGIN TRY ... BEGIN CATCH гарантирует, что если в процессе исправления данных возникнет непредвиденная блокировка или системная ошибка, транзакция будет полностью откатана к исходному состоянию, предотвращая повреждение структуры базы данных.
Внедрение данного регламентного скрипта позволяет полностью решить проблему расхождения дат в 1С 7.7, защитит СУБД от накопления неконсистентных данных и исключит появление ошибок уникальности индексов при работе с конфигурацией.