Многотабличный отчёт в строку

отредактировано 19:16 Раздел: FastReport 3.0
Заранее прошу прощения, если моя тема является повтором, однако поиск по форуму ответа на мой вопрос не дал. В противном случае, убедительно прошу указать аналогичную и/или смежную тему.

Описание задачи:
Необходимо выдавать на печать акт испытаний. Каждый акт имеет минимум 1 позицию.
В БД имеется 5 таблиц: 1 главная (общая информация), 4 дочерних (1 таблица = 1 тип испытаний: A,B,C,D), с общим набором ключевых полей "Номер акта"+"Номер позиции акта".
Испытания проводятся в зависимости от условий заказчика (заполнение информацией произвольное: один заказчик хочет испытания только по 1 из 4 типов, другой - 2 из 4, а третий может захотеть "по полной программе" :) ). Помимо этого, нормативными документами предписывается количество опытов для каждого типа испытаний, в зависимости от испытуемой детали (н-р, для болта должно проводиться 2 опыта типа С и/или 2 опыта типа D; для гайки - 1 типа А и/или 3 типа В и/или 2 типа С; для шурупа - 2 типа А).

Данные:
Чтоб было наглядней, "заполню" все 5 таблиц на основании изложенного выше.
1. Главная (AKTS: номер акта (NAkt), номер позиции (NPos), название детали (NameDet)):
111   1   Болт      
111   2   Гайка     
112   1   Шуруп
2. Тип испытаний А (TypeA: номер акта (NAkt), номер позиции (NPos), номер опыта (NOpyt), результат A1 (ResA1)):
111   2   1   0.55
112   1   1   1.17
112   1   2   1.14
3. Тип испытаний B (TypeB: номер акта (NAkt), номер позиции (NPos), номер опыта (NOpyt), результат B1 (ResB1), результат B2 (ResB2)):
111   2   1   177   3.6568
111   2   2   181   3.6480
111   2   3   179   3.6592
4. Тип испытаний C (TypeC: номер акта (NAkt), номер позиции (NPos), номер опыта (NOpyt), показатель C1 (PC1), результат C1 (ResC1), показатель C2 (PC2), результат C2 (ResC2)):
111   1   1   56   3.0   116   3.2
111   1   2   55   3.6   117   3.3
111   2   1   17   3.9   320   17.5
111   2   2   18   3.9   321   17.4
5. Тип испытаний D (TypeD: номер акта (NAkt), номер позиции (NPos), номер опыта (NOpyt), результат D1 (ResD1), замечания (Zam)):
111   1   1   1251   нет
111   1   2   1509   срыв

Попытаемся сделать "отчёт" для акта №111...

Выборка:
Каждой таблице соответствуют свои Dataset'ы, данные в которые выбираю нехитрым способом:
select <список полей>
from <имя таблицы>
where <имя таблицы>.NAkt=111
Не знаю, может что-то не так делаю? :)

Структура отчёта:
Полазив по форуму, перечитав мануалы и просмотрев ФРДемос, пришёл к выводу, что в сабрепортах моё спасение:
- на отчёт бросил Header с шапкой;
- под ним MasterData, привязанный к dataset'y главной таблицы, на котором лежит мемка с "Akts.NAkt" и 4 сабрепорта;
- на каждой странице конкретного сабрепорта лежит MasterData, привязанный на соответствующий dataset, с соответствующими мемками нужных полей.

Теперь "распечатаю" акт №111...
Что надо получить и что получается:
Пример создан в Excel'e. :)
Как видим, полученное отличается от желаемого. Замечу, что структуру (допустим, не в строку, а в столбик) итогового документа менять нельзя (собственно, в противном случае я б её уже давным давно поменял бы :) ). Только так - и никак иначе! :)
Нутром чую, что отсутствует связь "номер позиции"-"номер опыта", но как это сделать - ломаю голову который день. Перепробовал все возможные варианты, которые пришли в голову: и по части дизайна (=структуры) отчёта, и по части запросов. Понимаю, что ответ где-то близко, но... не совсем я, видать, понимаю принцип работы ФР'а. :)

Помогите, пожалуйста, и укажите мне на мои ошибки.

Всем, поучаствовавшим в решении вопроса, заранее выражаю огромнейшую благодарность!!!

Комментарии

  • gpigpi
    отредактировано 19:16
    1-й и самый простой вариант - сделать выборку всехнеобходимых данных одним запросом
    2-й вариант - при выводе каждой строки основного набора данных переоткрывать детаил запросы с номером акта и номером позиции в качестве параметров
  • отредактировано February 2011
    gpi, спасибо, что откликнулся.

    1й вариант, как видно из примера, не подходит. Если бы была выборка 1-к-1 - нет проблем, но тут 1-ко-многим (+4 таблицы к тому же). Разумеется, я попробую, но... буду надеяться, что ФР покажет мне совсем не то, что я вижу в IBExpert'e. :)

    А по 2му варианту я "где-то так и думал". Но мне не понятно:
    - в каком месте делать передёргивание запросов? (неужели в MasterData.OnBeforePrint?!)
    - как из отчёта делать "перебор" всех IBQuery, находящихся на делфийской форме? (получается как бы "самообновление" отчёта, это разве возможно?)

    По структуре отчёта замечаний нет? Всё так?

    Кстати, оговорюсь, что приведённое в первом посте описание задачи, не является реальным, оно изменено и упрощено. Я оставил лишь суть. На самом деле, всё немножко сложней. :)
  • отредактировано 19:16
    gpi, что-то по 1-му варианту не получается...
    Делаю выборку вида:
    select <поля главной таблицы>, <поля таблицы A>, <поля таблицы B>, <поля таблицы C>, <поля таблицы D>
    from <главная таблица>
    left join <поля таблицы A> on <список ключевых полей>
    ... 
    left join <поля таблицы D> on <список ключевых полей>
    where <главная таблица>.NAkt=111
    order by <главная таблица>.NPos
    
    Структуру не менял, т.е.:
    написал:
    MasterData, привязанный к dataset'y главной таблицы, на котором лежит мемка с "Akts.NPos" и 4 сабрепорта;
    В свою очередь, все сабрепорты перелинковал на "единый" набор данных.
    В итоге получил "Access violation..." и самого отчёта так и не увидел.
    Потом я грохнул все сабрепорты и кинул их содержимое в MasterData отчёта. Аналогично результату в IBExpert'e, выдало "чёрт знает что".
    Вернул всё как было в начале. Содержимое всех сабрепортов перебросил в один. И опять "Access violation..."...
    Что я не так делаю? Где подводный камень, о который я ломаю ноги?

    И попутно: где можно почитать как сделать 2-й вариант:
    написал:
    при выводе каждой строки основного набора данных переоткрывать детаил запросы с номером акта и номером позиции в качестве параметров
    ???
    Хотя бы маленький исходничек, чтоб мысль уловить, так сказать. Точнее, как сделать в Делфях я знаю, а вот как это "применить" к ФР?
  • gpigpi
    отредактировано 19:16
    Выборка данных из подчинённых таблиц должна быть такого вида:
    select номер акта (NAkt), номер позиции (NPos), SUM(<поля таблицы A>), SUM(<поля таблицы B>)
    from 
      (
       select номер акта (NAkt), номер позиции (NPos), <поля таблицы A>, 0 as <поля таблицы B>
       from <таблицa A>
       union all
       select номер акта (NAkt), номер позиции (NPos),0 as <поля таблицы A>, 0 <поля таблицы B>
       from <таблицa B>
      )
    group by номер акта (NAkt), номер позиции (NPos)
    
    + объединение с главной таблицей. Результат выводить на одном MasterData
    написал:
    Хотя бы маленький исходничек, чтоб мысль уловить, так сказать. Точнее, как сделать в Делфях я знаю, а вот как это "применить" к ФР?
    Так же, как и в Delphi
    DatasetName.Close;
    DatasetName.ParamByName...
    DatasetName.Open;
  • отредактировано March 2011
    По 1-му варианту опять не то... :)
    Приведу полностью запрос:
    select 
    NAkt,NPos,ResA1,ResB1,ResB2,PC1,PC2,ResC1,ResC2,ResD1,Zam
    from
    (select
     NAkt,NPos,ResA1,0 as ResB1,0 as ResB2,0 as PC1,0 as PC2,0 as ResC1,0 as ResC2,0 as ResD1,0 as Zam
     from TypeA
     union all
     select
     NAkt,NPos,0 as ResA1,ResB1,ResB2,0 as PC1,0 as PC2,0 as ResC1,0 as ResC2,0 as ResD1,0 as Zam
     from TypeB
     union all
     select
     NAkt,NPos,0 as ResA1,0 as ResB1,0 as ResB2,PC1,PC2,ResC1,ResC2,0 as ResD1,0 as Zam
     from TypeC
     union all
     select
     NAkt,NPos,0 as ResA1,0 as ResB1,0 as ResB2,0 as PC1,0 as PC2,0 as ResC1,0 as ResC2,ResD1,Zam
     from TypeD)
    where NAkt=111
    order by NAkt,NPos
    
    Кстати, в предыдущем запросе я не понял зачем использовались SUM'ы, следовательно, на group by ругалось и не фунциклировало.

    Все мемки сабрепортов кинул на МД отчёта, перелинковал, запустил.
    Если смотреть на пример из первого поста, то, вместо 5 строк в отчёте (2 строки для 1-й позиции, 3 - для 2-й), я получаю 4 строки для 1-й позиции и 6 - для 2-й. Это и понятно. Получилась эдакая "лесенка": каждый первый опыт нового типа испытаний начинается не с "первой" строки, а со следующей, т.е. (для № акта 111 и позиции 1):
    111   1   8   0   0   0  
    111   1   0   4   0   0
    111   1   0   5   0   0
    111   1   0   0   6   6
    
    вместо
    111   1   8   4   6   6  
    111   1   0   5   0   0
    
    Может я не совсем ясно выразился, что мне необходимо? :) Что не так опять я сделал?! :)

    По 2-му варианту сейчас буду пробовать...
    Кстати, для второго варианта подразумевается использование сабрепортов или, аналогично 1-му варианту, всё в один МД отчёта?
  • отредактировано 19:16
    По 1-му варианту...
    Сделал запрос, аналогичный предложенному gpi - результат такой же, т.е. не то. :) Да и сведущие люди сказали (точнее, подтвердили мои предположения), что то, что я хочу, одним запросом не получить. Поэтому 1й вариант отбрасываем. Хотя, в принципе, остаются варианты вывода типа "одним запросом", н-р, через ClientDataSet, массив, даже тупо через многострочные мемки выкрутиться можно. Но... всё надо делать красиво и по уму. :)

    По 2-му варианту...
    1. Понятно, что под него остаётся первоначальная структура: на отчёте лежит MasterData, привязанный к главной таблице, в котором находится мемка с номером позиции и 4 сабрепорта.
    2. В теории понятно, что при выводе номера позиции (т.е. при каждом изменении значения поля NPos) необходимо:
    - закрыть все 4 датасета сабрепортов;
    - передать в них новое значение номера позиции;
    - открыть их.
    Я попробовал в событии BeforePrint каждого сабрепорта хотя бы закрыть/открыть датасеты, но ФР говорит, что это неопределённый идентификатор...

    gpi, объясни мне, пожалуйста, где, в каком событии я должен передёргивать свои параметризованные запросы? Ну не въеду я никак! (((
  • отредактировано March 2011
    Достали уже эти аксес виолейшены!!! :)
    При первом вызове предпросмотра ругается:
    написал:
    First chance exception at $7C812AFB. Exception class EAccessViolation with message 'Access violation at address 005FE8C2 in module 'XXX.exe'. Read of address 00000000'. Process XXX.exe (960)
    Если брикануть, то выпадаю в frxClass'e сюда:
    ... 
    procedure TfrxReport.SetLocalValue(const Value: Variant);
    begin
      FLocalValue.Value := Value;
    end;
    ...
    
    Если продолжить, то все последующие сообщения такие:
    написал:
    First chance exception at $7C812AFB. Exception class EAccessViolation with message 'Access violation at address 005FAAC0 in module 'XXX.exe'. Read of address 0000012C'. Process XXX.exe (960)
    и валятся уже сюда:
    ...
    function TfrxReportPage.IsSubReport: Boolean;
    begin
      Result := SubReport <> nil;
    end;
    ...
    
    Был уверен, что все эти ошибки из -за "неправильности" отчёта...

    Эта темка подтолкнула на очередные ваяния. Кинул в отчёт frxIBXDatabase и 4 frxIBQuery. Всё настроил. В качестве Master'a для всех frxIBQuery указывал и frxDBDataset, завязанный на главную таблицу и расположенный на форме проекта, и внутренний frxIBQuery, завязанный аналогичным образом.
    Запросы всех дочерних frxIBQuery имеют вид:
    SELECT <поля таблицы А> 
    FROM
        <таблица А> 
    WHERE
        <таблица А>.NPOS =:NPOS
    ORDER BY <таблица А>.NPOS , <таблица А>.NOPYT
    
    В общем, всё сделал, как тут предлагалось.
    В теории всё должно работать "на ура!". Однако эти, просто фашистские, виолейшены не дают жизни! Имею Delphi 2005 Pro и FastReport 3.22, но пока абсолютно не имею с ними счастья. :) Кто-нибудь может подскажет чего-нибудь?
  • gpigpi
    отредактировано 19:16
    написал:
    gpi, объясни мне, пожалуйста, где, в каком событии я должен передёргивать свои параметризованные запросы? Ну не въеду я никак! (((
    В событии MasterData1.OnBeforePrint

    Желательно сделать простой тестовый проект, демонстрирующий проблему, и выложить его здесь. После 8 Марта посмотрю. Сейчас не могу - у меня сейчас "ёлки" :)
  • отредактировано March 2011
    gpi, извини за молчание... То болел, то другой задачей занимался.

    С ошибками я разобрался. Проблема крылась в "переизбытке чувств" к ФРу: удалил все прошлые версии, почистил и установил 4ку. Сейчас всё в порядке. Тьфу-тьфу-тьфу, ни одной егоги!
    По теме тоже всё заработало. Кинул в самом ФРе 5 IBXQuery (1 - головной, 4 - дочерних), дочерним в качестве Master'a указал головной. В результате имею то, что и хотел.
    Сейчас бьюсь уже над другой "проблемой": из-за разного количества дочерних записей не так, как надо, отображаются границы мемок. Везде поставил стретчи и максхейты, а в итоге получаю нечто типа такого:
    |   |   |   |   |
            |   |
            |   |
    
    вместо
    |   |   |   |   |
    |   |   |   |   |
    |   |   |   |   |
    
    По-моему, из-за того, что мемки лежат в разных сабрепортах, они просто "не знают", что где-то там есть мемки бОльшей высоты. По крайней мере, это моё предположение...
    Попытался "замазать" простыми линиями, но нужного не добился:
    - в MasterData.OnAfterCalcHeight каждого сабрепорта вычислял высоту линии как сумму высот мемки (что-то типа счётчика; для каждого сабрепорта свой) по типу:
    NeedH:=NeedH+Memo.CalcHeight
    
    (всё это мракобесие обнуляется в Page1OnBeforePrint)
    - в MasterData.OnBeforePrint делал проверку какая высота является большей и, найдя её, присваивал эту высоту каждой линии.
    В итоге получал линии большей длины (в нашем случае - высоты). Во всех остальных моих изысканиях все линии получались с высотой, равной начальной высоте мемки (т.е. высотой в 1 строку данных).

    Как грамотно такое "изобразить"?

    P.S: Понимаю, что мои трудности связаны с недостаточным пониманием последовательности событий, несмотря на то, что я их знаю (UserManual стр. 95). Н-р, сложилось впечатление (проверял через ShowMessage), что MasterData.OnBeforePrint на главной странице происходит и перед и после OnAfterCalcHeiht мастеров каждого сабрепорта...
  • gpigpi
    отредактировано 19:16
    Попробуй установить у вложенных отчётов свойство PrintOnParent в True, а под вложенными отчётами расположить мемки, растягиваемые на высоту бэнда. Они и будут отображать границы
  • отредактировано 19:16
    И как это я про "подложки" сразу не придумал-то? gpi, браво!!! Всё получилось.
    Единственное, хочу добавить и уточнить для будущих "поколений" :) :
    - мемо-"подложки" должны иметь свойства ShiftMode=smWhenOverlapped, StretchMode=smMaxHeight;
    - сабрепорт должен лежать поверх мемо-"подложек";
    - MasterData, на котором всё это дело лежит, должен иметь свойство Stretched=True.
    Именно из-за последнего с первого раза у меня не получилось - забыл, что в ходе предыдущих экспериментов я это свойство отключил.

    В любом случае, gpi, очередное тебе громадное спасибо!!!

Оставить комментарий

Многофункциональный текстовый редактор. Чтобы отредактировать стиль параграфа, нажмите TAB, чтобы перейти к меню абзаца. Там вы можете выбрать стиль. По умолчанию не выбран ни один стиль. Когда вы выберете текст, появится встроенное меню форматирования. Нажмите TAB, чтобы войти в него. Некоторые элементы, такие как многофункциональные вставки ссылок, картинок, индикаторов загрузки и сообщений об ошибок могут быть вставлены в редактор. Вы можете перемещаться по ним, используя стрелки внутри редактора и удалять с помощью клавиш delete или backspace.