以前にも表のピボットについては記事を書きました。似たような相談を受けたのですが、結果が少し違った実装になったので作業を記録します。
それぞれの従業員についてコンピュータ言語の習得レベルを確認したい、というのが目的です。それで、確認しやすいように、以下の形式にできないか、というのが要件です。
それぞれの言語が列となり、横並びになっています。表示上は見やすいです。しかし、このまま表にする前提で、クイックSQLのモデルを定義すると以下のようになります。
# prefix: pvt
# semantics: default
language_skills
name vc80 /nn
COBOL vc16 /check Excellent, Moderate, Acceptable
FORTRAN vc16 /check Excellent, Moderate, Acceptable
C vc16 /check Excellent, Moderate, Acceptable
新しい言語、例えばPythonやJavaScriptを追加しようとすると、表に列を追加する必要があります。このような表定義は望ましいものではないので、一般的には以下のように定義します。
# prefix: pvt
# semantics: default
languages
language_name vc16 /nn
employee_skills
employee_name vc80 /nn
language /reference languages /nn
skill_level vc16 /check Excellent, Moderate, Acceptable /nn
まずはこのモデルを元にアプリケーションを作成します。
SQLワークショップからユーティリティのクイックSQLを開きます。クイックSQLが開いたら、上記のモデルを入力します。続けて、SQLの生成、SQLスクリプトを保存、レビューおよび実行を行います。
生成されたDDLのレビュー画面で実行をクリックします。(この時点ではアプリケーションの作成は行いません。先に表を作成します)
続く画面で即時実行を行い、DDLの実行が成功したのを確認したのち、アプリケーションの作成をクリックして、アプリケーション作成ウィザードを開始します。
アプリケーションの作成をクリックします。
アプリケーションの作成画面が開きます。名前は任意ですが、ここでは言語スキルと設定しています。表PVT_LANGUAGESとPVT_EMPLOYEE_SKILLSのレポートとフォームが作成されることを確認し、アプリケーションの作成を実行します。
アプリケーションを実行し、確認のために使用するデータを投入します。
表PVT_LANGUAGES(メニューからはLanguages)に比較的伝統的な言語、COBOL、FORTRAN、Cの3つの言語を登録します。
従業員の言語スキルのレベルも登録します。以下の画面ではオラクル太郎さんはCOBOLが得意(EXCELLENT)、オラクル花子さんはFORTRANがそこそこできる(MODERATE)、オラクル二郎さんはCが少しできる(ACCEPTABLE)、という情報を登録しています。
テストに使うデータを登録しました。これから、上記のように縦並びで表示されているデータを言語ごとに横並びにしていきます。
最初にクラシック・レポートで、横並びの表示を行います。
ページの作成をクリックし、ページ作成ウィザードを開始します。
コンポーネントからレポートを選択します。
続いてクラシック・レポートを選びます。
ページ名をピボット・レポートとし、ブレッドクラムをBreadcrumbとして、次に進みます。
ナビゲーション・プリファレンスとして新規ナビゲーション・メニュー・エントリの作成を選択し、次に進みます。
declare l_sql1 varchar2(4000) := 'select k.employee_name row_key, k.employee_name'; l_sql2 varchar2(4000) := ' from (select employee_name from pvt_employee_skills group by employee_name) k '; l_idx integer := 0; l_tbl varchar2(8); begin for l in ( select id, language_name from pvt_languages order by 1 ) loop l_idx := l_idx + 1; -- construct SQL l_tbl := 't' || l_idx; l_sql1 := l_sql1 || ',' || l_tbl || '.' || l.language_name; l_sql2 := l_sql2 || ' left outer join (select employee_name, skill_level as ' || l.language_name || ' from pvt_employee_skills where language = ' || l.id || ') ' || l_tbl || ' on k.employee_name = ' || l_tbl || '.employee_name '; end loop; return (l_sql1 || l_sql2); end;
select k.employee_name row_key, k.employee_name,
t1.COBOL,
t2.FORTRAN,
t3.C
from (select employee_name from pvt_employee_skills group by employee_name) k
left outer join (select employee_name, skill_level as COBOL from pvt_employee_skills where language = 1) t1
on k.employee_name = t1.employee_name
left outer join (select employee_name, skill_level as FORTRAN from pvt_employee_skills where language = 21) t2
on k.employee_name = t2.employee_name
left outer join (select employee_name, skill_level as C from pvt_employee_skills where language = 22) t3
on k.employee_name = t3.employee_name
declare l_sql1 varchar2(4000) := 'select k.employee_name row_key, k.employee_name'; l_sql2 varchar2(4000) := ' from (select employee_name from pvt_employee_skills group by employee_name) k '; l_idx integer := 0; l_tbl varchar2(8); begin for l in ( select id, language_name from pvt_languages order by 1 ) loop l_idx := l_idx + 1; -- set column header apex_util.set_session_state(p_name => 'P6_COL' || to_char(l_idx + 2, 'FM00'), p_value => l.language_name, p_commit => FALSE); -- construct SQL l_tbl := 't' || l_idx; l_sql1 := l_sql1 || ',' || l_tbl || '.' || l.language_name; l_sql2 := l_sql2 || ' left outer join (select employee_name, skill_level as ' || l.language_name || ' from pvt_employee_skills where language = ' || l.id || ') ' || l_tbl || ' on k.employee_name = ' || l_tbl || '.employee_name '; end loop; return (l_sql1 || l_sql2); end;
declare l_skill_level pvt_employee_skills.skill_level%type; begin case when :APEX$ROW_STATUS in ('C','U') then for c in (select id, language_name from pvt_languages) loop l_skill_level := v('P8_' || upper(c.language_name)); if l_skill_level is not null then merge into pvt_employee_skills k using ( select :P8_EMPLOYEE_NAME as employee_name, c.id as language, l_skill_level as skill_level from dual ) e on (k.employee_name = e.employee_name and k.language = e.language) when matched then update set skill_level = e.skill_level when not matched then insert (employee_name, language, skill_level) values (e.employee_name, e.language, e.skill_level); else delete from pvt_employee_skills where employee_name = :P8_EMPLOYEE_NAME and language = c.id; end if; end loop; if :P8_ROW_KEY is null then :P8_ROW_KEY := :P8_EMPLOYEE_NAME; end if; when :APEX$ROW_STATUS = 'D' then delete from pvt_employee_skills where employee_name = :P8_EMPLOYEE_NAME; end case; end;
declare type pvt_employee_skills_t is table of pvt_employee_skills%rowtype; rs pvt_employee_skills_t; begin select * bulk collect into rs from pvt_employee_skills where employee_name = :P8_EMPLOYEE_NAME for update nowait; end;
declare l_skill_level pvt_employee_skills.skill_level%type; begin case when :APEX$ROW_STATUS in ('C','U') then for c in (select id, language_name from pvt_languages) loop l_skill_level := v(upper(c.language_name)); if l_skill_level is not null then merge into pvt_employee_skills k using ( select :EMPLOYEE_NAME as employee_name, c.id as language, l_skill_level as skill_level from dual ) e on (k.employee_name = e.employee_name and k.language = e.language) when matched then update set skill_level = e.skill_level when not matched then insert (employee_name, language, skill_level) values (e.employee_name, e.language, e.skill_level); else delete from pvt_employee_skills where employee_name = :EMPLOYEE_NAME and language = c.id; end if; end loop; if :ROW_KEY is null then :ROW_KEY := :EMPLOYEE_NAME; end if; when :APEX$ROW_STATUS = 'D' then delete from pvt_employee_skills where employee_name = :EMPLOYEE_NAME; end case; end;
declare type pvt_employee_skills_t is table of pvt_employee_skills%rowtype; rs pvt_employee_skills_t; begin select * bulk collect into rs from pvt_employee_skills where employee_name = :EMPLOYEE_NAME for update nowait; end;