Можно ли заменить GROUP при агрегировании?

Модераторы: kdv, CyberMax

Ответить
ZanZag
Сообщения: 12
Зарегистрирован: 15 авг 2006, 00:38

Можно ли заменить GROUP при агрегировании?

Сообщение ZanZag » 15 авг 2006, 15:50

(форум читаю давно, но вот нашел повод в нем и поучавствовать)
Суть вопроса.
Есть набор данных товары где каждая запись уникальна и цены с начальным значением и их приращением
для получения списка товаров с актуальной ценой на определенный момент надо проссумировать все значения их цен с ограничением по дате и вывести это сосписком товаров....
ну примерно так:

Код: Выделить всё

SELECT  t.tv_ndog, t.tv_ddog, t.tv_name, t.TV_STATE, ...,
  SUM(p.pr_pprod), SUM(p.pr_pkom)

FROM Tovar t left join Price p on (t.tv_ndog = p.pr_ndog)
WHERE  (p.pr_d_op < '9.8.2006')
GROUP BY t.tv_ndog, t.tv_ddog, t.tv_name, t.TV_STATE, ...
смущает меня в этом запросе то, что сколько полей нам потребуется с информацией о товаре, все надо пихать в этот самый GROUP...
некузявоть сего действия мне видиться в том, что группировать то по сути тут и не нужно, ибо запись о товаре существует только в одном экземпляре... по сути надо бы просумировать цену и только потом приклеивать к товару

например так:

Код: Выделить всё

SELECT  t.*, (select SUM(p.pr_pprod) from Price p where (t.tv_ndog = p.pr_ndog) )
FROM Tovar t
"беда" в том, что если цена неодна (например два поля, цена1, цена2) то во втором варианте подселектов будет тоже два...

Вопрос тут чисто философический ;)
не "как мне сделать, ой помогите"
а как бы это сделать красиво и правильно...
ибо включать в группировку все поля из таблицы, а это придеться делать всегда, когда нужен списот товаров с ценами меня как-то смущает... или это и есть "правильно"?

Подозреваю, что вся моя проблева вообще от того, что не правильно выбрана модель данных. Поэтому ниже поясню зачем так было придумано, ибо поменять сейчас еще не поздно.
Это вообще у меня проект для саморазвития...
Эта задача из комиссионной торговли.
есть таблица товаров (договоров комиссии) и таблица цен

Код: Выделить всё

TABLE TOVAR
    TV_NDOG   INTEGER NOT NULL, PK
    TV_DDOG   DATE,
    TV_NAME   VARCHAR(250) COLLATE PXW_CYRL,
    TV_STATE  CHAR(1)
    и т.д.

TABLE PRICE
    PR_NDOG   INTEGER NOT NULL, PK
    PR_D_OP    DATE NOT NULL, PK
    PR_PPROD  NUMERIC(15,2)
    PR_PKOM  NUMERIC(15,2)
Когда оформляется новый договор, то создается новфй договор то в товары пишеться запись типа
231 01.08.2006 Картина и т.д...
а в PRICE добавиться запись с начальными ценами
231 01.08.2006 200.00 100.00

потом может быть переоценка или уценка... т.е. дальше в прайс пишем только дельту изменения цены суказанием даты операции (в обин день может быть только одна переоценка, ну это понятно)
например 231 03.08.2006 -50.00 10.00 (первую цену уменьшили на 50 а вторую увеличили на 10)

что я хотел этим цырком сделать?
я просто не люблю системы, где, например цены, хранятся в конечном виде если есть операции их изменения. Тогда сложно получить состояние на прошлое число...
а если хранить приращение, то просто суммируем это поле с ограничением по дате... и в людой момент запрос даст тот же результат, стоклько бы не совершалось изменений после этого момента.
но вот группировка по всем полям меня смущает сильно...
может я что-то перемудрил и это все намного проще организовать можно?

если комуто интересно и есть предложения по другому организовать данные - готов обсуждать. если для этого надо заводить отдельный топик, сделаю, мне не сложно.
А рассписал так все подробно ибо очень часто на форуме видел обсуждения болгие, "как мне сделать чтоб тут было вот это" в конце которых выясняется что "это" делать ненадо и даже нельзя потому что изначаьно неправильный подход...

Merlin
Динозавр IB/FB
Сообщения: 1502
Зарегистрирован: 27 окт 2004, 11:44

Сообщение Merlin » 15 авг 2006, 17:29

Модель нормальная, один из двух распространённых способов хранения периодических реквизитов. Он быстрее второго на модификациях, особенно задним числом, и медленнее на селектах. Второй - дополнение таблицы цен уникальным полем _ID поколения записи_ и ссылка из документов непосредственно по нему, а ID служит для связи этих поколений в единую сущность. Но именно для цен (как, впрочем, и для любых одноатрибутных цифровых, не символьных, сущностей) я бы пошёл дальше - таблица таблицей, а значение хранить непосредственно в документе тоже. Это затрудняет модификации "справочных" данных задним числом так, чтобы модификация отразилась и в связанных таблицах, но тут уже надо подходить с точки зрения конкретики - а что нам нужно, чтоб модификация справочников отражалась на ссылающихся документах (например, исправление ошибки обслуживания справочника) или наоборот (с вот этого момента должно быть так и всё)? Если модификация задним числом - редкий аварийный режим, то нужно не полениться оформить процедуру его обслуживания и неважно насколько она медленная и навороченная. Если он основной или равноправный - отставить денормализацию и упомянутый второй способ организации периодических реквизитов и усложнять основной режим и чтение.

Насчёт количества полей в группировке. Всё определяется практикой. Да, чем "шире" запись резалтсета, тем медленнее сортировка (в общем случае - раньше кочается оперативка и сортировка уходит в tmp-файл). Вопрос в конкретике - с твоими данными так получается или нет. Нет - ну и фиг с ним. Да - группировать только по ID и сджойнивать так или иначе с запросом, достающим остальные атрибуты. Например, группировать в процедуре и писать неявный джойн по ID возвращаемого ей резалтсета и простого вытягивания атрибутов из той же таблицы.

ZanZag
Сообщения: 12
Зарегистрирован: 15 авг 2006, 00:38

Сообщение ZanZag » 16 авг 2006, 05:16

весьма благодарен за ответ.
на радостях позволю себе подискутировать далее, если сия тема будет интересна местному люду. а то и не с кем вовсе более...

Итак. По поводу модели...
я обдумывал уже мого вариантов, и меня не смутить ни частичным уходом от нормализации, ни потребностью в создании некиз процедур и тригеров для внесения изменений задним числом...
кстати говоря, в моеи случае уже, простое добавление нового документа надо делать процедурой, что бы сразу разносить по таблицам и документ и цены. не из приложения же это делать, тут то как раз хочу по максимуму на сервер бизнеслогику перекинуть.

С дополнительным ID в "ценах" или не уловил смысла или это для возможности получить селект с однозначным соответствием товары - цены (один к одному)...
Ну туда же направлен совет поместить цены (сдублировать) в сами товары, то бишь в сам документ...
(это я все проговариваю, что бы было видно как я понял сказанное).
безусловно идея правильная, но!

встает вопрос, а какая цена должна быть тогда помещена в документ?
некая конечная, учитывающая все приращения (изменения) цен этого документа (товара).
Позвольте, господа, но такой конечной цены не существует... откуда она может взятся? это же набор данных а не отпечатанный документ на такое-то число. (Вот это и служит благодатной почвой для роста бездны непонимания у меня и бухгалтеров.. вот подобные понятия как конечные цены и закрытые периоды... сначала они убеждают в правильности таких понятий, потом бегают с просьбами подправить в закрытом периоде цифирьку.... :) сори, отвлекся)

Итак, включить "конечную" цену можно, но это не облегчит жизнь, и вот почему. То что усложниться правка задним числом не беда, на то я и программист что бы написать нудную процедуру и навешать тригеров. Печально то, что перестают работать отчеты... Эти то печатаем в любой момент с указанием периода (или даты) формирования содержимого... и тут уж все поплывет. либо надо смотреть, не сегодня ли мы берем данные для отчета, или на прошедший период, и коли так, то "текущие" цены не катят и надо доставать актуальные на указанную дату, т.е. снова вернулись к нашим баранам. Исходя из этого я и решил считать текущую дату лишь частным случаем отчета за прошедший (за любой) период...

Метод работы, когда отчет в конце дня кладется в папку и считается незыблемым - идеальны.. и не из жизни. Я не встречал еще фирм, где это была бы основа деятельности... Все копаются в старом, правят, перепечатывают...

В данном конкретном случаеэто норма вещей, правка задним числом. И сложность не в самой правке... а в отчетах, т.е. как раз в обустройстве данных так, что бы иметь простоту и быстроту селектов с актуальными ценами на произвольную дату. Ничего более удобного мне в голову не пришло кроме указанной ранее модели.
(да и то, там в ценах по мимо приращения еще надо будет высчитывать итог в соседнем поле, но это для других селектов, и пока не буду об этом)

Что касается самих селектов, то с группировкой понял, не зря она меня смущала. Там же сортировка....

попробовал соорудить по совету процедурку (данных совсем немного набил, 5 записей в товарах и 10 в ценах)...

Код: Выделить всё

CREATE PROCEDURE PRICE_S(
  Dat_OP DATE)
RETURNS (
  PR_NDOG INTEGER,
  PR_DOP DATE,
  PR_PPROD NUMERIC(15,2),
  PR_PKOM NUMERIC(15,2))

AS
BEGIN
  FOR SELECT PR_NDOG,
             PR_DOP,
             sum(PR_PPROD),
             sum(PR_PKOM)
      FROM PRICE
      WHERE PR_DOP < :Dat_OP
      GROUP BY PR_NDOG,
               PR_DOP
      INTO :PR_NDOG,
           :PR_DOP,
           :PR_PPROD,
           :PR_PKOM
  DO
  BEGIN
    SUSPEND;
  END
END

и селектиком ее
SELECT t.*, p.*
FROM Tovar t right join PRICE_S('3.8.2006') p on (t.tv_ndog = p.pr_ndog)
План
PLAN JOIN (PRICE ORDER PK_PRICE,T INDEX (PK_TOVAR))

похоже это лучший вариант... пока.
сейчас не нравиться только, что не смотря на ограничение по дате таблица прайс читаеться вся (хоть и по индексу), даже если в результате нет ничего...

А может еще у кого мысль какая будет? эксперементировать так эксперементировать...
(сори за длинные посты... знаю как быват утомительно их читать ;) )

Merlin
Динозавр IB/FB
Сообщения: 1502
Зарегистрирован: 27 окт 2004, 11:44

Сообщение Merlin » 16 авг 2006, 15:11

В товары (номенклатор) цены пихать бессмыссленно, понятие "действующая цена" - фикция, документы всегда создаются на какую-то дату и "сегодня" - частный случай, не такой уж и распространённый.

Насчёт перебора - а зачем ты хранишь именно приращения, а не значения начиная с такой-то даты? Потому и приходится суммировать от царя Гороха. Эдак за пару лет ты сервак-то положишь. Если хранить значения, то доставать цену будешь например так (я в твои структуры не вникаю, на концептуальном уровне)

Код: Выделить всё


От товара (единичного)

Select First 1 Price From Prices 
  Where Tovar_ID=:Param1 And Date_Ot<=:Param2
Order By Date_Ot Desc

От документа с товарным составом (или номенклатора аналогично)

Select SD.* 
(Select First 1 P.Price From Prices P
  Where P.Tovar_ID=SD.Tovar_ID And P.Date_Ot<=HD.DateDoc
Order By P.Date_Ot Desc)
From Head_Doc HD Join Sost_Doc SD On SD.Doc_ID=HD.ID
Where HD.ID=:Param
Вот только подзапросы - не самый быстрый способ извлечения данных на больших выбрках. Отсюда и идея ID поколения записи. Если PK в Prices - свой искусственный ID, а Tovar_ID соединяет поколения записей по одному товару с разными датами действия, то текущие на дату цены достаются указанным выше способом только при формировании документов, ID укладывается в состав как ссылка на поколение цены, и в отчётах

Код: Выделить всё

Select SD.*, P.Price
From Head_Doc HD
        Join Sost_Doc SD On SD.Doc_ID=HD.ID
        Join Prices P On P.ID=SD.Price_ID
Where HD.ID=:Param
Фактически это мало отличается от хранения цены в документе, целесообразно применять для многоатрибутных справочников или содержащих большие чары.[/code]

ZanZag
Сообщения: 12
Зарегистрирован: 15 авг 2006, 00:38

Сообщение ZanZag » 17 авг 2006, 21:10

Насчёт перебора - а зачем ты хранишь именно приращения, а не значения начиная с такой-то даты? Потому и приходится суммировать от царя Гороха. Эдак за пару лет ты сервак-то положишь.
На самом деле храниться будут и приращения и конечные цены.
Просто в данном случае (получение списка товара с актуальными ценами на произвольную дату) мне показалось легче суммировать приращение с отсечкой по дате, нежели выискивать актуальную цену на дату меньше требуемой, которая для каждого товара разная. (в бухгалтерии понятие "на дату" определяетна утро такого-то числа, т.е. не включая операции за само это число. т.е. именно < даты а не <=. но это я так... на всякий)
Т.е. цены не меняют всем товарам а только выборочно.. Уценки вообще может не быть.

Посему конструкцию select sum() я посчитал наиболее простой. но заволновался насчет длинного списка полей в group вот и начал вопросы спрашивать.

Что касаеться суммирования от начала времен, то тут тоже не вижу пока опасности. За время жизни одного товара цена может поменяться 4 раза (по правилам комиссионной торговли) или не меняться совсем...
И коли даже изменятся правила, то .. ну 10 уценок... ну 20... тут уже смысл теряется самой сути продажи.
Я видать не четко выразился, когда сетовал что процедура в своем запросе тянет всю таблицу цен...
В процедуре (запросе) указывается дата на которую выдаються цены... а по плану я видел что идет чтение всей таблицы... Это и не нравилось, хотелось бы чтоб сначала записи отсекались по дате, потом группировались по ID а потом суммировались...
Для этого пришлось поменять PK(ID, DATA) -> PK(DATA, ID) и все заработало так как хотелось, теперь читаються по индексу только нужные записи. Осталось решить, менять именно PK или добавить по дате еще один индекс.

Попробовал оба варианта. Селект с процедурой, в прошлом посте.

1.если PK (PR_D_OP, PR_NDOG) (из цен читает записи отсекая по дате)
Адаптированный план PLAN JOIN (SORT (PRICE INDEX (PK_PRICE)),T INDEX (PK_TOVAR))
2.если PK (PR_NDOG, PR_D_OP) (из цен читает по индексу все записи)
Адаптированный план PLAN JOIN (PRICE ORDER PK_PRICE,T INDEX (PK_TOVAR))
3.если PK тот же но добавлен индекс по PR_D_OP (из цен читаються только записи удовлетворяющие дате)
Адаптированный план PLAN JOIN (PRICE ORDER PK_PRICE,T INDEX (PK_TOVAR))
вот тут я и не понял. из плана совсем не видно что в 3 используеться новый индекс по дате, хотя судя по количеству чтений индекс явно используется... :roll: чешу репу... весь в задумчивости...

О подзапросах.
Согласен, вариант далеко не лучший, и медленный...
Тем более, что как sum, так и first в подзапросе смогут вернуть только одно поле, а если цены вде (а их именно две) то это два подзапроса и уж совсем не красиво получается...
(за first спасибо, первый раз увидел как его можно применять в FB, везч полезная иногда)
так что и тут большого выигрыша перед суммированием не увидел...

С уникальным ID в ценах... (может быть я не понял глубины мысли в следствии зашоренности своей конкретной задачей... )
но опять таки совсем не понял чем помоч то это может...

Как я понял сию конструкцию:
Есть таблица товаров со своим ID
таблица цен со своим ID
и связочная таблица с полями ID_Tovar, ID_Price...

Но в этом варианте остается задача из поколений записей с ценами по одному товару достать актуальную цену. Из фразы "указанным выше способом" предполагаю что select first... по сути, те же яйца, вид сбоку... или туплю не по детски, или еще более сложный способ. вертел и так и сяк...

Ivan_Pisarevsky
Заслуженный разработчик
Сообщения: 644
Зарегистрирован: 15 фев 2005, 11:34

Сообщение Ivan_Pisarevsky » 21 авг 2006, 12:09

А я всегда храню цену товара в первичном документе, потому что именно эта цена скреплена подписями контрагентов, и именно она и есть правда в последней инстанции, ее надо хранить, а не высчитывать. А вот историю откуда именно и на основании какого распоряжения она возникла, пожалуйста храните в еще одной таблице и в случае необходимости туда можно заглянуть. Все эти периодические реквизиты нужны на время выписки первичного документа, как только накладная распечатана и подписана, цена в ней фиксируется намертво, как на бумаге, так и в ее электронной копии. И в отчетах должна проходить именно цена из документов, а номенклатор - это просто справочник. На мой скромный взгляд цена в номенклаторе и цена продажи есть разные сущности, взаимосвязь которых может быть очень разная, и почти наверняка не взаимооднозначная. Собственно поэтому включение цены в документ я даже денормализацией не считаю.

Да и насчет группировки, группировать можно по ключу, а остальные неключевые атрибуты набирать например min(...), если конструктивно запись отберется одна, то и минимум от единственной записи будет сама запись.

ZanZag
Сообщения: 12
Зарегистрирован: 15 авг 2006, 00:38

Сообщение ZanZag » 21 авг 2006, 18:02

Позволю себе заметить, что вопрос весьма спорный, ибо случаи бывают разные.

Впрочем предположим, в документе храним цену. Какую? некую конечную. понятие конечной цены уже обсуждалось, но всеж. ну допустим цену включающую все уценки/переоценки... ок.
тогда имеем простоту селектов по отбору документов с актуальными ценами...
И все отчеты/селекты документов, оперирующих всегда конечной ценой (такие как списки проданных товаров, возврат товаров...) очень просто получаем.
Но есть еще как минимум две группы отчетов: где используется цена начальная (список принятых на комиссию и т.п.) и цена действительная на некую дату (журналы движения, списки наличия, накладные...).
с первыми (начальная дата) можно так же... в документ ее, начальную цену... (ага, и маму в дом!..(ц) ;) )
Но вот последняя группа (с ценой на дату) никак не зависит от способа хранения начальной или конечной цены, в документе или нет...
Понятно, что существует только СЕЙЧАС... это жизнь.
Но реально, организация работы не позволяет сделав изменения, отпечатать документы и не возвращаться к этому моменту...
Кто-то вносит сегодняшние уценки, а кто-то печатает вчерашние отчеты... Не говоря уже о "переделках".

Вот собственно я и подумал, а почему бы не организовать хранение данных сразу так, что бы небыло разницы в способе доставания актуальной цены... что бы конечная цена была всего лишь частным случаем, а механизм получения данных однообразен, быстр и удобен...

И даже если пойти на хранение цен в документе, третья группа отчетов все равно потребует обработки истории цен, а следовательно возвращаемся к тепе топика :)

По поводу заключения неключевых атрибутов в "min()/max()" (сори за каламбурчик) то же способ, согласен. Но несколько "не изящьный".
Серваку всеж придется как-то на эти min() обращать внимание... и даже если накладные расходы такого запроса получатся невидны и в микроскоп, то "осадок останеться"...

Мне казалось более правильный путь ргруппировать только суть.. т.е. ключ и цены, а потом приклеивать остальные атрибуты товара. Сделать это получилось посредством процедуры...

Тем не менее, огромное спасибо за интерес к моему вопросу...
Моя задумка идеальной мне не представляется, посему любые предложения - велкам. :)

Ivan_Pisarevsky
Заслуженный разработчик
Сообщения: 644
Зарегистрирован: 15 фев 2005, 11:34

Сообщение Ivan_Pisarevsky » 22 авг 2006, 08:50

ZanZag писал(а):Но есть еще как минимум две группы отчетов: где используется цена начальная (список принятых на комиссию и т.п.) и цена действительная на некую дату (журналы движения, списки наличия, накладные...).
с первыми (начальная дата) можно так же... в документ ее, начальную цену... (ага, и маму в дом!..(ц) ;) )
Но вот последняя группа (с ценой на дату) никак не зависит от способа хранения начальной или конечной цены, в документе или нет...
То какую именно цену пропечатывать в конкретный отчет решается нормальным диалогом с бухгалтером, потребителем сего отчета. У меня присутствуют отчеты (да та же самя оборотка) в ней начальное, конечное сальдо и дебет в учетных ценах, а кредит аж три колонки: учетная, отпускная цена и расхождение. Как поставили передо мной задачу, так я и сделал.

ZanZag
Сообщения: 12
Зарегистрирован: 15 авг 2006, 00:38

Сообщение ZanZag » 22 авг 2006, 13:48

[s]еле тему нашел, савсем быстро бегает, да... %) [/s]
Ivan_Pisarevsky писал(а): То какую именно цену пропечатывать в конкретный отчет решается нормальным диалогом с бухгалтером, потребителем сего отчета.
Простите, сударь, позволю себе не согласиться...

Какой такой диалог с бухгалтером, когда там двух мнений быть не модет, ибо... если документ например наличия печатается НА ххх ДАТУ, то и войти в него должны товары принятые до этой даты не проданные и не возвращенные и с ценой на эту дату... а что происходило с ценой после этой даты и с самим товаром не имеет значения для данного документа. И давать один и тот же результат сей документ на эту определенную дату должен как сегодня, так и спустя месяц, год, век...

Не хотелось бы вас нагружать подробностями своей задачи, в них не так интересно вникать я пологаю, когда и своих хватает...

Просто хочу подчеркнуть еще раз. Я искренне считаю большим приимуществом системы, если документы, создаваемые задним числом, получаются такие же как и в момент текущего создания... без всяких дополнительных ухищрений... это добовляет сложностей на этапе проектирования, но зато потом все работает правильно.

А вот когда печатаешь "наличие" октября прошлого года и видишь что там сумма документа почему-то другая, нежели в таком же наличии но созданном сразу по проществии сего месяца... то что с этим делать? это по меньшей мере бесит...
а еще бывает что надо достать архив, вспомнить все изменения которые вносили в первичку за прошедшее время из них внести в архив только те, что могли войти в этот документ.. и т.д. и получить желаемое, но все равно не сойтись в сумме и долго вспоминать что еще забыл...

Ну это так, лирика... накипело просто, не обращайте внимания, к FB не относится :)

[s]...а какие неподьемные отчеты, чужие, приходилось править на ЛИВИЗе... :roll: [/s]

Ivan_Pisarevsky
Заслуженный разработчик
Сообщения: 644
Зарегистрирован: 15 фев 2005, 11:34

Сообщение Ivan_Pisarevsky » 22 авг 2006, 14:24

У нас немного другая учетная политика...Например у нас уценка лежалого товара это расход товара по учетной цене и приход его но по новой цене и с новыми переклееми бирками с новой ценой и новым артикулом, и по складу программно начинает фигурировать другой товар, хотя фактически это теже самые штаны или пинжаки с карманАми. А просто отгрузить на сторону мы можем по севершенно отфонарной цене, напрмер запихнуть в цену транспортные расходы, потом это просто отразится в оборотке и прочих отчетах.

>Какой такой диалог с бухгалтером, когда там двух мнений быть не модет
Бывает разная учетная политика ;)

ZanZag
Сообщения: 12
Зарегистрирован: 15 авг 2006, 00:38

Сообщение ZanZag » 22 авг 2006, 14:36

Ivan_Pisarevsky писал(а): Бывает разная учетная политика ;)
угу... я то про своих баранов.
тама (комиссионка) изменение цены - штатная вещь...

эх, тяжкое дело я себе затеял, тем более что никто не просит...

Кстати, не только в цене дело. Часто сталкивался с тем, что разработчики просто не задумываются что данные в документе могут поплыть со временем... Классический случай нерадивости, это наличие товара без учета даты продажи... типа продано - не берем в расчет...

Господа, а кто-нибудь еще пытается реализовывать системы дающие неизменный результат в документах задним числом?
(что-то топик занесло в совсем другую гавань...)

WildSery
Заслуженный разработчик
Сообщения: 1738
Зарегистрирован: 05 июн 2006, 16:19

Сообщение WildSery » 22 авг 2006, 15:03

ZanZag писал(а):Господа, а кто-нибудь еще пытается реализовывать системы дающие неизменный результат в документах задним числом?
(что-то топик занесло в совсем другую гавань...)
... а заодно "те самые бланки отчётов", которые совсем недавно в очередной раз поправили... Знакомо.
Мы в каждом конкретном случае заново принимаем решение о критичности и глубине "заднечислового" (исторического) хранения, "стандарта" ИМХО тут быть не может.

ZanZag
Сообщения: 12
Зарегистрирован: 15 авг 2006, 00:38

Сообщение ZanZag » 22 авг 2006, 15:11

WildSery писал(а):... а заодно "те самые бланки отчётов", которые совсем недавно в очередной раз поправили... Знакомо.
Ну это даже необсуждается... а то как же? Первый раз в 97 году поменялись бланки квитанций и спустя пару месяцев понадобилась старая квитанция... Создав ее заново, с листа, был научен сохранять все формы отчетов в истории... на всякий. Так потом себе дешевле...

Ответить