Работа с регистрами через SQL (1С 7.7)

В 1С можно увидеть не очень удобное для работы в SQL понятие “регистры”. Цель у регистров довольно простая - обеспечить скорость подсчета чего-либо (суммы товара и прочего). С регистрами я начал работать при исследовании динамических свойств товара. Отмечу, извлекать из регистров историческую информацию очень накладно (приходится перебирать все документы, изменяющие динамические свойства). Вот я решил с вами поделиться очередной находкой, как при помощи средств SQL преобразовать регистр в другой вид таблицы.

Итак начнем

Связь таблиц для определения динамического свойства товара выглядит следующим образом в 1C:

Динамические свойства
Приведу краткое описание механизма.

Установка динамического свойства начинается с документа, который изменяет или устанавливает требуемое динамическое свойство. Документ оставляет изменение в регистре (в моем случае таблица ra9642), “1” делает свойство активным, а “-1” отменяет свойство. Остальные связи нужны для того, чтобы добраться до самого свойства. Только проведенный документ (значение _1sjourn.closed = 1) может двигать динамическое свойство, так что таблицу _1sjourn можно исключить из рассмотрения.

Итак, я хочу получить следующего рода таблицу: product_id, din_svo_id, date_start, date_finish. Такая таблица относится к классу темпоральных.

Что для этого надо сделать? Надо заменить регистр ra9642 и таблицу sc556 на темпоральную таблицу.
В результате мы можем ответить на следующий вопрос: “Какое динамическое свойство установлено у товара в произвольный момент времени?”. Для этого достаточно один раз перебрать регистр средствами SQL в обход стандартных процедур и получить готовую таблицу по всем динамическим свойствам. Для статистической базы - самое то, тогда для базы 1С не могу гарантировать оптимальную производительность. Выполнение запроса на реальной БД занимает 15125 ms (примерно 15 сек.) с возвращением данных (более 463 тыс. строк). Далее нужно перегрузить эти данные в статистическую базу и у вас все готово. Дальнейшая работа с данными зависит от оптимизации индексов и оптимизации ETL процессов. (Либо можно при следующей загрузки данных только догружать данные в таблицу за определенную дату. Но это уже техника настройки обновления таблиц).

Приступаем к практике

Для этого напишем “боевой” скрипт на SQL:

create view test as
select temp.*, sc556.descr as type_name_price
from
(
  select t_from.sp9643 as product_id,
         t_from.sp9647 as din_svo_id,
         t_from.date_from,
         min(t_to.date_to) as date_to
  from (
    select CAST(CAST(ra9642.DATE_TIME_IDDOC AS CHAR(8)) AS date) as date_from,
           sp9643,
           sp9647,
           sp9645  
    from RA9642 where ra9642.sp9645 = 1
    ) as t_from  inner join
         (
           select CAST(CAST(ra9642.DATE_TIME_IDDOC AS CHAR(8)) AS date) as date_to,
                  sp9643,
                  sp9647,
                  sp9645  
           from RA9642 where ra9642.sp9645 = -1
           union
           select cast('2999.12.31' AS date) as date_to,
                  sp9643,
                  sp9647,
                  -1
           from RA9642 where ra9642.sp9645 = 1
         )     
       as t_to ON t_from.sp9643 = t_to.sp9643 AND t_from.sp9647 = t_to.sp9647
  where   t_from.date_from <= t_to.date_to
  group by  t_from.sp9643,
            t_from.sp9647,
            t_from.date_from
) as temp inner join sc556 on sc556.ID = temp.din_svo_id;

 

create view test as  нужен для создания представления и проведения тестирования правильности работы select запроса.

Идея работы

“1” и “-1” в регистре разбивают множество ra9642 на два различных подмножества, причем множество с “1” будет больше, чем множество с” -1” (логично, сначала устанавливаем свойство “1”, потом отменяем “-1”). Обзовем два множества t_from (“1”) и t_to (“-1”)

Сначала создадим множество t_from:

select  CAST(CAST(ra9642.DATE_TIME_IDDOC AS CHAR(8)) AS date) as date_from,
          sp9643, /*id_product*/
          sp9647, /*id_свойства*/
          sp9645  /*значение свойства*/
from RA9642 
where ra9642.sp9645 = 1

Нужно выровнять мощность множеств или добиться того, чтобы t_to стало больше или равным t_from. Для этого используем следующий запрос, который формирует множество t_to:

select   CAST(CAST(ra9642.DATE_TIME_IDDOC AS CHAR(8)) AS date) as date_to,
            sp9643, /*id_product*/
            sp9647, /*id_свойства*/
            sp9645  /*значение свойства*/
from RA9642
where ra9642.sp9645 = -1
union
select   CAST('2999.12.31' AS date) as date_to,
            sp9643, /*id_product*/
            sp9647, /*id_свойства*/
            -1
from RA9642 
where ra9642.sp9645 = 1

обратим внимание на условия отбора у двух операторов select, после чего станет ясно, какое множество мы получили (после union стоит запрос t_from, только значение “-1” отменяет  динамическое свойство, и дата его отмены - '2999.12.31' ). Важный момент: нужно задать большую дату по умолчанию, к примеру '2999.12.31', так как эта дата будет у всех активных динамических свойств. 

Далее начинаем объединять два множества t_from и t_to. Происходит объединение командой inner join

select  t_from.sp9643 as product_id,
          t_from.sp9647 as din_svo_id,
          t_from.date_from,
          min(t_to.date_to) as date_to
from
      (...) as t_from  inner join
      (...) as t_to ON t_from.sp9643 = t_to.sp9643 AND t_from.sp9647 = t_to.sp9647
where    t_from.date_from <= t_to.date_to
group by   t_from.sp9643,
               t_from.sp9647,
               t_from.date_from

Разбираемся. При объединении множества нужно использовать inner join, так как множество t_to уже больше, чем t_from. Надо объединять по двум столбцам (так как один и тот же товар может иметь несколько динамических свойств), поэтому используем условие объединения on t_from.sp9643 = t_to.sp9643 and t_from.sp9647 = t_to.sp9647
Условие отбора where t_from.date_from <= t_to.date_to задает принцип объединения таблиц по дате. То есть за документом, устанавливающим свойство “1”, должен следовать документ с “-1” и с датой больше или равной “1”. Из всех дат, следующих за документом с “1” нужно выбрать минимальную дату с “-1” или min(t_to.date_to).

Отметим, что t_to.date_to приведена к формату Data. Если в один день устанавливались и отменялись свойства, и снова устанавливались и отменялись свойства, то тут могут быть “подводные камни”. Будьте внимательнее! Как выход - можно использовать тип Datetime.

Мне осталось только привести скрипты для создания БД с данными и опубликовать работу проверочного запроса.

CREATE TABLE sc556
(
  row_id BIGINT
, id VARCHAR(15)
, descr VARCHAR(12)
, parentext BIGINT
, ismark BIGINT
, verstamp BIGINT
)
;
INSERT INTO sc556(row_id, id, descr, parentext, ismark, verstamp) VALUES (5423,'18S000','Распродажа',1,0,0);
INSERT INTO sc556(row_id, id, descr, parentext, ismark, verstamp) VALUES (5423,'18S001','Красная цена',1,0,0);
CREATE TABLE sc84
(
  row_id BIGINT
, id VARCHAR(9)
, parentid VARCHAR(9)
, code BIGINT
, descr VARCHAR(40)
, isfolder BIGINT
, ismark BIGINT
, verstamp BIGINT
, sp85 BIGINT
, sp86 VARCHAR(9)
, sp208 BIGINT
, sp2417 BIGINT
, sp97 BIGINT
, sp5066 BIGINT
, sp5013 BIGINT
, sp94 VARCHAR(9)
, sp4427 VARCHAR(9)
, sp103 BIGINT
, sp104 BIGINT
, sp5012 BIGINT
, sp9466 BIGINT
, sp9502 BIGINT
, sp10358 BIGINT
, sp101 VARCHAR(13)
, sp95 TIMESTAMP
);
INSERT INTO sc84(row_id, id, parentid, code, descr, isfolder, ismark, verstamp, sp85, sp86, sp208, sp2417, sp97, sp5066, sp5013, sp94, sp4427, sp103, sp104, sp5012, sp9466, sp9502, sp10358, sp101, sp95) VALUES (34606,'   EHG   ','   EHC   ',25975,'Terra VG Kids',2,0,3,701022,'   BCX   ',0,2,0,0,0,'   BCX   ','   X8U   ',0,0,25,0,2,0,'Terra VG Kids',null);
INSERT INTO sc84(row_id, id, parentid, code, descr, isfolder, ismark, verstamp, sp85, sp86, sp208, sp2417, sp97, sp5066, sp5013, sp94, sp4427, sp103, sp104, sp5012, sp9466, sp9502, sp10358, sp101, sp95) VALUES (29647,'   EHL   ','   EHC   ',25996,'Boston',2,0,6,601034,'   BD2   ',0,2,0,0,0,'   BD2   ','   XBN   ',0,0,2,0,2,0,'Boston',null);
INSERT INTO sc84(row_id, id, parentid, code, descr, isfolder, ismark, verstamp, sp85, sp86, sp208, sp2417, sp97, sp5066, sp5013, sp94, sp4427, sp103, sp104, sp5012, sp9466, sp9502, sp10358, sp101, sp95) VALUES (19929,'   EHH   ','   EHC   ',25991,'Hobart 25',2,0,2,310433,'   BCY   ',0,2,0,0,0,'   BCY   ','   X8S   ',0,0,0,0,2,8000,'Hobart 25',null);
INSERT INTO sc84(row_id, id, parentid, code, descr, isfolder, ismark, verstamp, sp85, sp86, sp208, sp2417, sp97, sp5066, sp5013, sp94, sp4427, sp103, sp104, sp5012, sp9466, sp9502, sp10358, sp101, sp95) VALUES (4786,'   EI0   ','   EHC   ',26030,'Xpedition II',2,0,4,810033,'   BDH   ',0,2,0,0,0,'   BDH   ','   XAA   ',0,0,5,0,2,8000,'Xpedition II',null);
INSERT INTO sc84(row_id, id, parentid, code, descr, isfolder, ismark, verstamp, sp85, sp86, sp208, sp2417, sp97, sp5066, sp5013, sp94, sp4427, sp103, sp104, sp5012, sp9466, sp9502, sp10358, sp101, sp95) VALUES (20143,'   EI3   ','   EHC   ',26231,'Hill 243053',2,0,1,243053,'   BDK   ',0,2,0,0,0,'   BDK   ','   X8Y   ',0,0,53,0,2,0,'Hill 243053',null);
CREATE TABLE RA9642
(
  iddoc VARCHAR(9)
, lineno_ VARCHAR(15)
, actno VARCHAR(15)
, debkred VARCHAR(15)
, iddocdef VARCHAR(15)
, date_time_iddoc VARCHAR(40)
, sp9643 VARCHAR(9)
, sp9647 VARCHAR(15)
, sp9645 BIGINT
);
INSERT INTO RA9642(iddoc, lineno_, actno, debkred, iddocdef, date_time_iddoc, sp9643, sp9647, sp9645) VALUES ('  4Y1V000','0','62','0','9648','2010121575LELC  4Y1V000','   EHL   ','18S000',1);
INSERT INTO RA9642(iddoc, lineno_, actno, debkred, iddocdef, date_time_iddoc, sp9643, sp9647, sp9645) VALUES ('  4Y1V000','0','83','0','9648','2010121575LELC  4Y1V000','   EI0   ','18S000',1);
INSERT INTO RA9642(iddoc, lineno_, actno, debkred, iddocdef, date_time_iddoc, sp9643, sp9647, sp9645) VALUES ('  4YQG000','0','2','0','9648','20101216A8GBFK  4YQG000','   EHG   ','18S000',1);
INSERT INTO RA9642(iddoc, lineno_, actno, debkred, iddocdef, date_time_iddoc, sp9643, sp9647, sp9645) VALUES ('  7V9K000','0','2','0','9648','20111021A8UOEO  7V9K000','   EHL   ','18S000',-1);
INSERT INTO RA9642(iddoc, lineno_, actno, debkred, iddocdef, date_time_iddoc, sp9643, sp9647, sp9645) VALUES ('  8OT4000','0','3','0','9648','20111222CXHRBK  8OT4000','   EHL   ','18S000',1);
INSERT INTO RA9642(iddoc, lineno_, actno, debkred, iddocdef, date_time_iddoc, sp9643, sp9647, sp9645) VALUES ('  8OT4000','0','17','0','9648','20111222CXHRBK  8OT4000','   EI3   ','18S000',1);
INSERT INTO RA9642(iddoc, lineno_, actno, debkred, iddocdef, date_time_iddoc, sp9643, sp9647, sp9645) VALUES ('  8OT4000','0','18','0','9648','20111222CXHRBK  8OT4000','   EHH   ','18S001',1);
INSERT INTO RA9642(iddoc, lineno_, actno, debkred, iddocdef, date_time_iddoc, sp9643, sp9647, sp9645) VALUES ('  9T3Y000','0','86','0','9648','20120217A4DPNK  9T3Y000','   EHH   ','18S001',-1);
INSERT INTO RA9642(iddoc, lineno_, actno, debkred, iddocdef, date_time_iddoc, sp9643, sp9647, sp9645) VALUES ('  GAPJ000','0','32','0','9648','201209107AKSXC  GAPJ000','   EHL   ','18S000',-1);
INSERT INTO RA9642(iddoc, lineno_, actno, debkred, iddocdef, date_time_iddoc, sp9643, sp9647, sp9645) VALUES ('  GAPJ000','0','38','0','9648','201209107AKSXC  GAPJ000','   EI3   ','18S000',-1);
INSERT INTO RA9642(iddoc, lineno_, actno, debkred, iddocdef, date_time_iddoc, sp9643, sp9647, sp9645) VALUES ('  JJRJ000','0','62','0','9648','201212119NUPWG  JJRJ000','   EHH   ','18S000',1);
INSERT INTO RA9642(iddoc, lineno_, actno, debkred, iddocdef, date_time_iddoc, sp9643, sp9647, sp9645) VALUES ('  KA4E000','0','22','0','9648','2012122570V81S  KA4E000','   EHL   ','18S000',1);
INSERT INTO RA9642(iddoc, lineno_, actno, debkred, iddocdef, date_time_iddoc, sp9643, sp9647, sp9645) VALUES ('  KA4E000','0','38','0','9648','2012122570V81S  KA4E000','   EI3   ','18S000',1);

Запускаем “боевой” скрипт. Далее проверочный скрипт:

SELECT
  sc84.descr,
  test.din_svo_id,
  test.date_from,
  test.date_to,
  test.type_name_price
FROM
  public.test,
  public.sc84
WHERE
  sc84.id = test.product_id;

 Вывод проверочного запроса:

Результат

Ссылки на материалы:

  1. Описание структуры таблиц на 1 С7: http://www.script-coding.com/v77tables.html