市区町村別の人口一覧画面

この画面は、都道府県別人口一覧画面で、都道府県のリンクを押下すると遷移する画面で、
該当都道府県の市区町村の性別、年代別の人口を表示し、合併、分離など市区町村マスタの
保守と各分類別の人口を更新します。画面及びその構成要素は以下の通りです。
市区町村一覧画面
登録ボタン   :入力フォームの内容でDBを更新します。
行追加ボタン  :新しい市区町村の行を追加します。
初期化ボタン  :入力内容をキャンセルし、最初に画面が表示された状態にします。
一覧へボタン  :都道府県別人口の検索・一覧画面に遷移します。
ログアウトボタン:ログイン画面に遷移します。画面放置でタイムアウトしたとき使用します。
削除チェック  :市区町村を削除する時チェックします。
コードテキスト :市区町村のコードを入力します。
名称テキスト  :市区町村の名称を入力します。
人口テキスト  :年代別、性別の人口を入力します。
1. Front部の HTML、JavaScript (prefecture.html)
画面遷移直後の初期処理で都道府県の地域別人口を検索して一覧画面に表示します。
データ更新後の登録ボタン押下でデータベースに登録、再検索して画面に表示します。
<html lang="ja">
    <head>
    <title>地域別人口の入力</title>
    <meta http-equiv="content-type" charset="utf-8">
    <meta http-equiv="Cache-Control" content="no-cache">
    <style>.top { vertical-align: top; }</style>
    <script src='./javascript/jquery-3.4.0.min.js'></script>
    <script src='./javascript/vue.js'></script>
    <script>
      var noCache = new Date().getTime();
      document.write('<script src="./javascript/cgjs.js?' + noCache + '"><\/script>');
    </script>
    <script>
      var app_table, name_obj;
      var data_state = { arr_len: 0, col_len: 0, row_pos: 0, rows: [ ] };
      var records, pref, codes, db_name = "";

      function get_time_seq() {
        let dt = new Date();
        let seq = dt.getHours() * 10000 + dt.getMinutes() * 100 + dt.getSeconds();
        return seq;
      }

      // 「一覧へ」ボタンのイベント
      function to_list() {
        location.href = "population.html";
      }
      // 「ログアウト」ボタンのイベント
      function to_login() {
        location.href = "login.html";
      }
      //  「初期化」ボタンのイベント
      function initialize() {
        location.href = "prefecture.html?pref=" + pref + "&db=" + db_name;
      }
      // 「行追加」ボタンのイベント
      function add_row() {
        let r_idx = data_state.row_pos++;
        let table = document.getElementById("data_table");
        let row = table.insertRow();
        let html = 
          `<td><input type="checkbox" id="check_${r_idx}" onChange="chkChange(${r_idx})" /></td>
           <td><input type="text" id="text_${r_idx}_0" size="1" maxlength="3" 
           onChange="txChange(${r_idx},0)" /></td>`;
        for (let k = 1 ; k < data_state.col_len ; k++) {
          html += `<td><input type="text" id="text_${r_idx}_${k}" size="4" maxlength="7" 
                   onChange="txChange(${r_idx},${k})" /></td>`;
        }
        row.innerHTML = html;
      }
      // TextBoxのChangeイベント
      function txChange(row, col) {
        $("#text_" + row + '_' + col).css("background-color", "#FFE0E0").backgroundColor;
        if (data_state.rows.indexOf(row) >= 0)
          return;
        data_state.rows.push(row);
      }
      // CheckBoxのChangeイベント
      function chkChange(row) {
        if (data_state.rows.indexOf(row) >= 0)
          return;
        data_state.rows.push(row);
      }
      //  「登録」ボタンのイベント
      function regist_table() {
        var k, val, city;
        let r_idx = data_state.arr_len;
        let pos = data_state.row_pos;
        let ins_city = [ ];
        let ins_arr = [ ];
        let upd_city = [ ];
        let upd_arr = [ ];
        let del_arr = [ ];
        // 変更のあった行で処理を行う
        for (let row of data_state.rows) {
          if (row < r_idx) {    // テーブルに既存のデータ
            city = records[row*1][0];
            if ($("#check_" + row).prop('checked')) {// 既存データの削除準備
              // レコードの削除
              del_arr.push([ pref, city ]);
            }
            else {    // 既存データの更新準備
              // 地域名
              val = $("#text_" + row + '_1').val();
              if (val == "") {
                $("#text_" + row + '_1').focus();
                alert("地域名の入力テキストが空白です。");
                return;
              }
              if (val != records[row*1][1]) { // 地域名の更新
                upd_city.push([ pref, city, val ]);
              }
              // 地域の人口
              for (k = 2 ; k < data_state.col_len ; k++) {
                val = $("#text_" + row + '_' + k).val();
                if (val == "") {
                  $("#text_" + row + '_' + k).focus();
                  alert("地域人口の入力テキストが空白です。");
                  return;
                }
                if (val != records[row*1][k]) { // 地域人口の更新
                  upd_arr.push([ pref, city, codes[k][0], codes[k][1], val ]);
                }
              }
            }
          }    // 新規挿入データの準備
          else {
            if ($("#check_" + row).prop('checked'))   // 行追加の取り消し
              continue;
            city = $("#text_" + row + '_0').val();    // 地域コード
            if (city == "") {
              $("#text_" + row + '_0').focus();
              alert("地域コードの入力テキストが空白です。");
              return;
            }
            val = $("#text_" + row + '_1').val();   // 地域名
            if (val == "") {
              $("#text_" + row + '_1').focus();
              alert("地域名の入力テキストが空白です。");
              return;
            }
            ins_city.push([ pref, city, val ]); // 地域マスタの挿入
            // 地域の人口
            for (k = 2 ; k < data_state.col_len ; k++) {
              val = $("#text_" + row + '_' + k).val();
              if (val != "") { // 地域人口の挿入
                ins_arr.push([ pref, city, codes[k][0], codes[k][1], val ]);
              }
            }
          }
        }
        // DB更新のレコード数
        let reg_cnt = ins_city.length + upd_city.length + ins_arr.length + upd_arr.length + del_arr.length;
        if (reg_cnt == 0) {
          alert("新規・変更・削除を登録するデータがありません。");
          return;
        }
        // ajaxで地域マスタ、地域人口を登録
        var send_obj = {
          arg: { "Prefecture": pref, "insert": ins_arr, "update": upd_arr, "delete": del_arr, 
                   "upd_city": upd_city, "ins_city": ins_city, "db": db_name },
          include:"demo_module/reg_pref_data",
          execute:"reg_pref_data(arg);",
        }
        let obj = { };
        var ret = CGJS.cgjs_if(obj, send_obj);   // 同期でAjax通信
        if (ret != "") {
          alert(ret);
          return;
        }
        if (obj.CGI_CODE*1 != 0 && obj.CGI_CODE != "") {
          alert("Error code:" + obj.CGI_CODE + " ,message:" + obj.CGI_MESSAGE);
          return;
        }
        else if (obj.CGI_MESSAGE != "") {
          alert(obj.CGI_MESSAGE);
        }
        // 画面を登録結果で更新する
        location.href = "prefecture.html?pref=" + pref + "&db=" + db_name;

      }
    </script>
  </head>
  <body>
    <div id="name_header" style="font-size:15px;">
      <table>
        <tr>
          <td>会社:{{ company }}</td>
          <td>    </td>
          <td>本部:{{ division }}</td>
          <td>    </td>
          <td>部室:{{ department }}</td>
          <td>    </td>
          <td>名前:{{ user_mame }}</td>
          <td>    </td>
          <td>DB:{{ database }}</td>
        </tr>
      </table>
    </div style="margin: 10px 0px 10px 0px;">
    <div>地域別人口の入力</div>
    <form method="POST" action="">
      <div id="app">
        <div id="btn_top">
          <table>
            <tr>
              <td>       </td>
              <td>
                <input type="button" value="登 録" onClick="regist_table();">
              </td>
              <td>       </td>
              <td>
                <input type="button" value="行追加" onClick="add_row();">
              </td>
              <td>       </td>
              <td>
                <input type="button" value="初期化" onClick="initialize();">
              </td>
              <td>       </td>
              <td>
                <input type="button" value="一覧へ" onClick="to_list();">
              </td>
              <td>       </td>
              <td>
                <input type="button" value="ログアウト" onClick="to_login();">
              </td>
            </tr>
          </table>
        </div>
        <hr>
        <div id="app_table">
          <table id = "data_table" border="1">
            <tr>
              <th rowspan="2">削除</th>
              <th colspan="2">市区町村</th>
              <th colspan="2">15歳未満</th>
              <th colspan="2">15~64歳</th>
              <th colspan="2">65~74歳</th>
              <th colspan="2">75歳以上</th>
            </tr>
            <tr>
              <th>コード</th><th>名称</th>
              <th>男性</th><th>女性</th>
              <th>男性</th><th>女性</th>
              <th>男性</th><th>女性</th>
              <th>男性</th><th>女性</th>
            </tr>
            <tr v-for="(idx, row) in records">
              <template v-for="(index, col) in row" track-by="$index">
                <template v-if="index == 0">
                  <td><input type="checkbox" :id="'check_'+idx" :onChange="'chkChange('+idx+')'" /></td>
                  <td><input type="text" :id="'text_'+idx+'_'+index" size="1" maxlength="3" 
                        :value="col" :onChange="'txChange('+idx+','+index+')'" readonly="true" /></td>
                </template>
                <template v-else>
                  <td><input type="text" :id="'text_'+idx+'_'+index" size="4" maxlength="7" 
                        :value="col" :onChange="'txChange('+idx+','+index+')'" /></td>
                </template>
              </template>
            </tr>
          </table>
        </div>
        <div id="btn_bottom">
          <table>
            <tr>
              <td>       </td>
              <td>
                <input type="button" value="登 録" onClick="regist_table();">
              </td>
              <td>       </td>
              <td>
                <input type="button" value="行追加" onClick="add_row();">
              </td>
              <td>       </td>
              <td>
                <input type="button" value="初期化" onClick="initialize();">
              </td>
              <td>       </td>
              <td>
                <input type="button" value="一覧へ" onClick="to_list();">
              </td>
              <td>       </td>
              <td>
                <input type="button" value="ログアウト" onClick="to_login();">
              </td>
            </tr>
          </table>
        </div>
      </div>
    </form>
    <script>
      var tmp_str = location.search.substr(1);
      let idx = tmp_str.indexOf("pref=");
      if (idx >= 0)  {
        pref = tmp_str.substr(idx + 5);
        if ((idx = pref.indexOf("&")) >= 0) {
          pref = pref.substring(0, idx);
        }
        idx = tmp_str.indexOf("db=");
        if (idx >= 0)  {
          db_name = tmp_str.substr(idx + 3);
          if ((idx = db_name.indexOf("&")) >= 0) {
            db_name = db_name.substring(0, idx);
          }
          sessionStorage.setItem('db_name', db_name);

          // ajaxで都道府県の人口を取得
          var send_obj = {
            arg: { "Prefecture": pref, db: db_name, },
            include:"demo_module/get_pref_data",
            execute:"get_pref_data(arg);",
          }
          let obj = { };
          var ret = CGJS.cgjs_if(obj, send_obj);
          if (ret != "") {
            alert(ret);
          }

          data_state = { arr_len: 0, col_len: 0, row_pos: 0, rows: [ ], };
          records = [ ];
          codes = [ ];
          if (obj.CGI_CODE*1 != 0 && obj.CGI_CODE != "") {
            alert("Error code:" + obj.CGI_CODE + " ,message:" + obj.CGI_MESSAGE);
          }
          else if (obj.CGI_MESSAGE != "") {
            alert(obj.CGI_MESSAGE);
          }
          else {
            try {
              if (obj.CGI_OBJ) {
                records = obj.CGI_OBJ.records;
                codes = obj.CGI_OBJ.codes;
                if (!records || !(records instanceof Array)) {
                  records = [ ];
                }
                data_state.arr_len = data_state.row_pos = records.length;
                data_state.col_len = records[0].length;
              }
            } catch (ex) {
              alert("Exception:" + ex);
              records = [ ];
            }
          }
          if (records.length > 0) {
            app_table = new Vue({
              el: '#app_table',
              data: { records: records, seq: get_time_seq(), },
            });
          }
        }
        else {
          alert("使用するデータベースが指定されていません。");
        }
      }
      else {
        alert("都道府県コードが指定されていません。");
      }
      name_obj = new Vue({
        el: '#name_header',
        data: {
          seq: 0,
          company: "",
          division: "",
          department: "",
          user_mame: "",
          database: ""
        }
      });
      name_obj.company = sessionStorage.getItem('company');
      name_obj.division = sessionStorage.getItem('division');
      name_obj.department = sessionStorage.getItem('department');
      name_obj.user_mame = sessionStorage.getItem('user_mame');
      name_obj.database = db_name;
      name_obj.seq = name_obj.seq + 1;
    </script>
  </body>
</html>
※画面の入力フォームにデータを入力して、「登録」ボタンが押下されると、
 75行~131行で入力フォームをチェックして、削除行、更新行、新規登録行のデータを準備します。
※139行~146行でDB更新のモジュール demo_module/reg_pref_dataの関数
 reg_pref_data(arg);を実行して、準備したデータをDBに登録・更新します。
※159行で登録後のデータで画面を更新しています。
※287行~293行で都道府県コード、DBの指定を取得し、I/F関数 CGJS.cgjs_if(obj, send_obj); で
 モジュール demo_module/get_pref_dataの関数 get_pref_data(arg); を実行して、都道府県の
 地域別人口を取得します。
※298行~328行で取得したデータで、Vueコンポーネントを作成し、画面に表形式で表示します。
2. Back End部のデータの取得 JabaScript (get_pref_data.js)
画面遷移での初期表示、初期化ボタン押下や更新後の再表示で、地域別人口テーブルを検索して、地域の人口を表示します。
具体的な取得データは、以下のソースでコメント、SQLを参照してください。
// テーブル表示に対応する年齢別、男女別の人口を取得
function get_pref_data(arg) {
  let conn = null;
  let records = [ ];
  let codes = [ [ ], [ ] ];
  try {
    // データベースに接続
    conn = openDb(DB_NAME, DB_LIB, DB_PARAM);

    // テーブルのヘッダーに対応する年齢別、男女別のコード配列を生成
    let sql = `select age.Code as a_cd, sex.Code as s_cd 
               from (select Code from Column_M where Type = '01') age, 
                    (select Code from Column_M where Type = '02') sex 
               order by age.Code, sex.Code `;
    // 画面の表のヘッダー部データを作成
    let tmp_arr = select(conn, sql);
    for (let nm of tmp_arr) {
      codes.push([ nm[0], nm[1] ]);
    }

    // 市区町村別人口の配列を生成
    let pref = arg.Prefecture;
    if (DB_NAME == 'sqlserver')
      sql = `select x.CityCd, x.Name, y.Value
             from (select city.CityCd, city.Name, age.Code as a_cd, age.Name as a_nm, sex.Code as s_cd, sex.Name as s_nm
                   from (select CityCd, Name from City_M where PrefCd = '${pref}') city,
                       (select Code, Name from Column_M where Type = '01') age,
                       (select Code, Name from Column_M where Type = '02') sex) x left outer join
                 (select CityCd, SexCd, AgeCd, Value from Population_T where PrefCd = '${pref}') y
                 on (y.CityCd = x.CityCd and y.SexCd = x.s_cd and y.AgeCd = x.a_cd)
             order by x.CityCd, x.a_cd, x.s_cd `;
    else
      sql = `select x.CityCd, x.Name, y.Value
             from (select city.CityCd, city.Name, age.Code as a_cd, age.Name as a_nm, sex.Code as s_cd, sex.Name as s_nm
                   from (select CityCd, Name from City_M where PrefCd = :pref) city,
                       (select Code, Name from Column_M where Type = '01') age,
                       (select Code, Name from Column_M where Type = '02') sex) x left outer join
                 (select CityCd, SexCd, AgeCd, Value from Population_T where PrefCd = :pref) y
                 on (y.CityCd = x.CityCd and y.SexCd = x.s_cd and y.AgeCd = x.a_cd)
             order by x.CityCd, x.a_cd, x.s_cd `;
    tmp_arr = select(conn, sql);
    let c_cd = "";
    let arr = [ ];
    // 市区町村ごとに年齢別、男女別の人口を横に並べる
    for (let rec of tmp_arr) {
      if (rec[0] != c_cd) {
        if (arr.length > 0)
          records.push(arr);
        c_cd = rec[0];
        arr = [ rec[0], rec[1],  rec[2] ];
      }
      else {
        arr.push(rec[2]);
      }
    }
    records.push(arr);
  }
  catch (ex) {
    console.log(ex);
  }
  finally {
    // データベースのセッションを終了
    if (conn) {
      closeDb(conn);
    }
  }
  // フロントのJavaScriptに転送する
  sendToFront(records);
  sendToFront(codes);
}
3. Back End部のデータ更新 JabaScript (reg_pref_data.js)
Front部のJavaScriptで入力フォームを調べて作成した以下のデータで、地域マスタを更新したり、地域別人口を更新します。
  arg.delete:マスタの削除データ(配列[ 県コード, 市コード ]の配列)
  arg.upd_city:マスタの更新データ(配列[ 県コード, 市コード, 名称 ]の配列)
  arg.ins_city:マスタの挿入データ(配列[ 県コード, 市コード, 名称 ]の配列)
  arg.update:人口の更新データ(配列[ 県コード, 市コード, 年代, 性別, 人口 ]の配列)
  arg.insert:人口の更新データ(配列[ 県コード, 市コード, 年代, 性別, 人口 ]の配列)

具体的な更新内容は、以下のソースでコメント、SQLを参照してください。
 // 画面で新規入力、変更、削除されたデータを登録する
function reg_pref_data(arg) {
  let conn = null;
  let sql;
  // データベースに接続
  conn = openDb(DB_NAME, DB_LIB, DB_PARAM);
  try {
    // レコードの削除
    if (arg.delete.length > 0) {
      // Transaction テーブルの該当市町村の人口を削除
      execute(conn, "delete from Population_T where PrefCd = :1 and CityCd = :2", arg.delete);
      // Master テーブルの該当市町村のレコードを削除
      execute(conn, "delete from City_M where PrefCd = :1 and CityCd = :2", arg.delete);
    }
    // レコードの更新
    // 市区町村マスタのレコード更新
    if (arg.upd_city.length > 0) {
      sql = "update City_M set Name = :3 where PrefCd = :1 and CityCd = :2";
      execute(conn, sql, arg.upd_city);
    }
    // 市区町村の人口の更新
    if (arg.update.length > 0) {
      if (DB_NAME == 'postgresql')
        sql = "update Population_T set Value = :5::int where PrefCd = :1 and CityCd = :2 and AgeCd = :3 and SexCd = :4";
      else if (DB_NAME == 'db2')
        sql = `merge into Population_T as pt using (values 
                  (cast(:1 as varchar(2)), cast(:2 as varchar(3)), cast(:3 as varchar(6)), 
                   cast(:4 as varchar(3)), cast(:5 as integer))) 
                  as val (PrefCd, CityCd, AgeCd, SexCd, Value)
                  on (pt.PrefCd = val.PrefCd and pt.CityCd = val.CityCd and 
                      pt.AgeCd = val.AgeCd and pt.SexCd = val.SexCd)
                  when matched then update set Value = val.Value
                  when not matched then insert (PrefCd, CityCd, AgeCd, SexCd, Value)
                    values (val.PrefCd, val.CityCd, val.AgeCd, val.SexCd, val.Value); `;
      else if (DB_NAME == 'sqlserver')
        sql = `merge into Population_T pt using 
                  (select cast(:1 as varchar(2)) as PrefCd, cast(:2 as varchar(3)) as CityCd, 
                          cast(:3 as varchar(6)) as AgeCd, cast(:4 as varchar(3)) as SexCd, 
                          cast(:5 as integer) as Value) val
                  on (pt.PrefCd = val.PrefCd and pt.CityCd = val.CityCd and 
                      pt.AgeCd = val.AgeCd and pt.SexCd = val.SexCd)
                  when matched then update set Value = val.Value
                  when not matched then insert (PrefCd, CityCd, AgeCd, SexCd, Value)
                    values (val.PrefCd, val.CityCd, val.AgeCd, val.SexCd, val.Value); `;
      else
        sql = "update Population_T set Value = :5 where PrefCd = :1 and CityCd = :2 and AgeCd = :3 and SexCd = :4";
      execute(conn, sql, arg.update);
    }
    // レコードの挿入
    // 市区町村マスタのレコード挿入
    if (arg.ins_city.length > 0) {
      sql = "insert into City_M (PrefCd, CityCd, Name) values (:1, :2, :3)";
      execute(conn, sql, arg.ins_city);
    }
    // 市区町村の人口の挿入
    if (arg.insert.length > 0) {
      if (DB_NAME == 'postgresql')
        sql = "insert into Population_T (PrefCd, CityCd, AgeCd, SexCd, Value) values (:1, :2, :3, :4, :5::int)";
      else
        sql = "insert into Population_T (PrefCd, CityCd, AgeCd, SexCd, Value) values (:1, :2, :3, :4, :5)";
      execute(conn, sql, arg.insert);
    }
    // データベースのセッションを終了
    execute(conn, "commit");            // 全ての更新処理が終わると、commit処理をおこないます
  } catch (ex) {
    console.log(ex);
  }
  finally {
    // データベースのセッションを終了
    if (conn) {
      closeDb(conn, "rollback");        // 更新途中でエラーがあると、rollback処理をおこないます
    }
  }
}