INSERT文のカラムと値を一覧化しました。

## 経緯

INSERT文を修正する際に、使用している値がどのカラムの値なのか
一目でわからなかったため作成しました。

## 画面

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/604560/d1bd7de4-956f-1de7-c6b9-0212237c2240.png)

画面半分にして使用するのを前提としているので全画面表示だと表示位置がずれます。

## コード

コピペしてHTMLファイル作成すれば動きます。

``` HTML
<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <title>Easy Insert</title>
    <script src="https://cdn.jsdelivr.net/npm/sortablejs@latest/Sortable.min.js"></script>

    <style name="insertDataStyle">
      .useData, .unUseData {
        width: 50%;
        margin-top: 10px;
        /* 下記二つでblockを中央寄せ */
        margin-left: auto;
        margin-right: auto;
      }

      #dataLength {
        width: 50px;
        margin-left: 7rem;
      }

      .scrollInner {
        border: solid 1px;
        width: 100%;
        margin-top: 5px;
        /* スクロールバー追加 */
        overflow-y: scroll;
      }

      .useScroll {
        height: 342px;
      }

      .unUseScroll {
        height: 185px;
      }

      tbody {
        cursor: pointer;
      }

      td {
        height: 10px;
        border: solid 1px;
      }

      th{
        /* テーブルヘッダ固定 */
        background-color: white;
        position: sticky;
        top: 0;
        left: 0;
        z-index: 1;
      }

      .tableColName {
        width: 8rem;
      }

      .tableData {
        width: 14rem;
      }

      .tableButton {
        width: 2.7rem;
      }

      td input {
        border: none;
        height: 1.4rem;
        width: 94%;
      }

      .tableNo {
        text-align: center;
      }

      .btn {
        margin-left: 9px;
        width: 5rem;
      }

      #convertBtn {
        letter-spacing: 10px;
        text-align: center;
        padding-left: 10px;
      }

      #clearBtn {
        letter-spacing: 4px;
      }
  </style>
  <style name="inputQueryStyle">
    .inputQuery {
      width: 50%;
      margin-left: auto;
      margin-right: auto;
      margin-top: 30px;
    }
  </style>
  <style name="createQueryStyle">
    .createQuery {
      width: 50%;
      margin-left: auto;
      margin-right: auto;
      margin-top: 10px;
    }

    #createBtn {
      position: relative;
      left: 20rem;
    }
  </style>
  <style name="common">
    body {
      width: 930px;
    }

    textarea {
      margin-top: 5px;
      width: 100%;
      resize: vertical;
    }
  </style>
  </head>
  <body>
    <div class="inputQuery">
      <label for="inputQuery">クエリ入力:</label>
      <button class="btn" onclick="sample(1)">サンプル1</button>
      <button class="btn" onclick="sample(2)">サンプル2</button>
      <button class="btn" id="clearBtn" onclick="clearInputQuery()">クリア</button>
      <button class="btn" id="convertBtn" onclick="convert()">変換</button>
      <textarea id="inputQuery"></textarea><br>
    </div>

    <div class="inputData">
      <div class="useData">
        <label class="blockLabel">使用データ</label>
        <input type="text" placeholder="tableName" id="tableName">
        <input type="number" readonly value="0" id="dataLength">
        <label for="dataLength">件</label>

        <div class="useScroll scrollInner">
          <table border="1">
            <thead>
              <tr>
                <th class="tableNo">No</th>
                <th class="tableColName">列名</th>
                <th class="tableData">値</th>
                <th class="tableButton"></th>
              </tr>
            </thead>
            <tbody id="useTbody"></tbody>
          </table>
        </div>
      </div>

      <div class="unUseData">
        <label for="tableName">未使用データ</label>
        <button id="createBtn" onclick="create()">生成</button>
        <div class="unUseScroll scrollInner">
          <table border="1">
            <thead>
              <tr>
                <th class="tableNo">No</th>
                <th class="tableColName">列名</th>
                <th class="tableData">値</th>
                <th class="tableButton"></th>
              </tr>
            </thead>
            <tbody id="unUseTbody"></tbody>
          </table>
        </div>
      </div>
    </div>

    <div class="createQuery">
      <textarea readonly id="createQuery"></textarea>
    </div>
    
    <script name="dispLoad">
      // SortableJS
      const useTbody = document.querySelector('#useTbody');
      Sortable.create(useTbody, {
        group: 'shared',
        draggable: '.item',
        onEnd: () => rowsChange(),
        onRemove: () => rowsChange(),
        onAdd: (evt) => {
          rowsChange();
          addBtnDisabledChange(evt.item);
        }
      });
      const unUseTbody = document.querySelector('#unUseTbody');
      Sortable.create(unUseTbody, {
        group: 'shared',
        draggable: '.item',
        onAdd: (evt) => addBtnDisabledChange(evt.item)
      });
    </script>        
    <script name="function">
      function convert() {
        const query = document.querySelector('#inputQuery').value;

        // insert文内の文字検索
        const searchWordDict = {
          into: 'into'
          , values: 'values'
          , beginbracket: '('
          , endbracket: ')'
        }
        
        // insert文のカラム名が省略されていないか確認
        const colExist = (query.match(new RegExp(/\(/, 'g' ) ) || [] ).length === 2 
          ? true : false ;

        // テーブル名を取得
        // insert into tb (col1) values (val1)
        const tableNameEndIdx = colExist
          ? query.indexOf(searchWordDict.beginbracket) 
          : query.toLowerCase().indexOf(searchWordDict.values);

        const tableName = query
          .substring(0 , tableNameEndIdx)
          .substr(query.toLowerCase()
            .indexOf(searchWordDict.into) + searchWordDict.into.length
          ).trim();

        document.querySelector('#tableName').value = tableName;

        // 列名
        const colNameList = (() => {
          if (colExist) {
            const colName = query
            .substring(0, query.indexOf(searchWordDict.endbracket))
            .substr(query.indexOf(searchWordDict.beginbracket) + 1)

            return colName.split(',');
          }
          return [];
        })();

        // 値
        const data = query
          .substr(query.lastIndexOf(searchWordDict.beginbracket) + 1)
          .replace(`${searchWordDict.endbracket};`, '');
        
        const dataList = query === '' ? [] : data.split(',');

        // 初期化
        document.querySelector('#useTbody').innerHTML = '';

        // テーブルにデータをセット
        createRows(colNameList, dataList, dataList.length, -1);
      }

      function createRows(inColNameList, inDataList, inRowsCount, inAddIdx) {
        const colNameList = inColNameList;
        const dataList = inDataList;
        const rowsCount = inRowsCount;
        const addIdx = inAddIdx;

        const table = document.querySelector('#useTbody');

        for(let i=0; i < rowsCount; i++) {
          const newRow = table.insertRow(addIdx);
          // SortableJSの対象を設定(tbodyのデータ)
          newRow.setAttribute('class','item');
          newRow.setAttribute('onDblclick','changeColor(this)');

          // No
          const newNo = document.createTextNode('');
          const newNoCell = newRow.insertCell();
          newNoCell.setAttribute('class','tableNo');
          newNoCell.appendChild(newNo);
          
          // ColName
          const newColName = document.createElement('input');
          newColName.setAttribute('type', 'text');
          newColName.setAttribute('placeholder', 'colName');
          newColName.setAttribute('class', 'inputTableColName');
          newColName.setAttribute('value', `${colNameList[0] ? colNameList[i].trim() : ''}`);
          const newColNameCell = newRow.insertCell();
          newColNameCell.setAttribute('class','tableColName');
          newColNameCell.appendChild(newColName);

          // Data
          const newData = document.createElement('input');
          newData.setAttribute('type', 'text');
          newData.setAttribute('placeholder', 'Data');
          newData.setAttribute('class', 'inputTableData');
          newData.setAttribute('value', `${dataList[0] ? dataList[i].trim() : ''}`);
          const newDataCell = newRow.insertCell();
          newDataCell.setAttribute('class','tableData');
          newDataCell.appendChild(newData);

          // Button
          const newBtn = document.createElement('button');
          newBtn.setAttribute('class', 'inputTableButton');
          newBtn.setAttribute('onClick', 'createRows([], [], 1, this.parentElement.parentElement.querySelector(\'td\').textContent)');
          newBtn.textContent = '追加';
          const newBtnCell = newRow.insertCell();
          newBtnCell.appendChild(newBtn);
        }
        rowsChange();
      }

      function create() {
        const tableName = document.querySelector('#tableName').value;
        const inputColNameList = document.querySelectorAll('#useTbody .inputTableColName');
        const inputDataList = document.querySelectorAll('#useTbody .inputTableData');

        const colExist = !!inputColNameList[0].value;
        const colName = (() => {
          let colName = '';

          if(colExist) {
            colName = ' (';
            inputColNameList.forEach(element => {
              colName += `${element.value}, `
            });
            colName = colName.slice(0, -2);
            return colName += ')';
          }
          return colName;
        })();

        const data = (() => {
          let data = '';
          inputDataList.forEach(element => {
            data += `${element.value}, `
          });
          return data.slice(0, -2);
        })();

        const query = `INSERT INTO ${tableName}${colName} VALUES (${data});`;
        document.querySelector('#createQuery').value = query;
      }

      function rowsChange() {
        const elm = document.querySelectorAll('#useTbody tr td.tableNo');
        for(let i=0; i < elm.length; i++) {
          elm[i].textContent = i + 1;
        }
        document.querySelector('#dataLength').value = elm.length;
      }
      
      function changeColor(elm) {
        const style = elm.style;
        style.backgroundColor = style.backgroundColor === '' ? 'skyblue' : '';
      }

      function addBtnDisabledChange(elm) {
        const addBtn = elm.querySelector('.inputTableButton');
        addBtn.disabled = addBtn.disabled ? false : true;
      }

      function sample(type) {
        const text = (() => {
          let textCol = '';
          let textData = 'VALUES (';
          for(let i=1; i<21; i++) {
            textCol += type === 1 ? `col${i}, ` : '';
            textData += `data${i}, `
          }
          textCol = textCol.slice(0, -2);
          return `INSERT INTO TABLE ${textCol === '' ? '' : `(${textCol}) `}${textData.slice(0, -2)});`;
        })();        
        document.querySelector('#inputQuery').value = text;
      }

      function clearInputQuery() {
        document.querySelector('#inputQuery').value = '';
      }
    </script>
  </body>
</html>

```

## 動作

### 初期表示

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/604560/c74aa35b-4a67-e719-4873-01445bb71364.png)

### サンプルボタン押下

サンプル用のINSERT文が表示されます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/604560/d50e63c0-e71f-c4b0-7c88-d13ad819ebbb.png)

### 変換ボタン押下

入力されているINSERT文の内容からテーブルに値をセットします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/604560/25d0483e-05e8-4eac-1634-9a906c8666bc.png)

### 生成ボタン押下

テーブルの内容からINSERT文を生成します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/604560/9f46704c-a860-5fe9-3d83-636e2abec9b1.png)

### テーブルNoドラッグ

1 同じテーブル内でNoをドラッグした場合、列名と値が移動します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/604560/54633e24-dabc-a349-4dc6-14072298a510.png)

2 テーブルをまたいで、ドラッグも可能です。
未使用データテーブルの追加ボタンは非活性です。
また、生成ボタン押下時のINSERT文の対象外です。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/604560/0aa3ee85-ba92-71ef-9135-68b918160158.png)

### 行をダブルクリック

ダブルクリックをした行に色が付きます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/604560/2bad74e3-a430-3084-4e1d-a065dca3a572.png)

### 追加ボタン押下

押した追加ボタンの1行下に行を作成します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/604560/a3364aa8-15f3-1e50-9c6d-a8f93b0ec405.png)

## 参考記事

行の並び替えに使用
https://sortablejs.github.io/Sortable/