風待ち

プログラミングの勉強や日々の出来事のログなど

emacsの*scratch*バッファでテンプレートエンジンのmustache.elを使って雑にSQLを作ってみる。

mustache.elを見つけたので使ってみる。

Emacs Lisp自体ほとんどわかっていないので勉強もかねてやってみた。

*scratch*バッファだから、順番に”C-j”して実行して行けば、テンプレートの{{key}}で書かれたところを埋めたSQLが手に入る。

利用したライブラリは下記の3つ

*scratch*バッファ

;;テンプレートを作る
(setq template "
update users set user_id = '{{new-id}}' where user_id = '{{old-id}}';
delete from item_info where user_id = '{{old-id}}';
")

;;ハッシュテーブルを作る
(require 'ht)
(setq context (list
           (ht ("new-id" "new-foo") ("old-id" "old-foo"))
           (ht ("new-id" "new-bar") ("old-id" "old-bar"))
           (ht ("new-id" "new-baz") ("old-id" "old-baz"))))

;;SQLをmustache.elで作る
(require 'dash)
(require 'mustache)

(-map (lambda (m) (princ (mustache-render template m))) context)

;;再帰でもやってみる
(defun my-render (tmpl lst)
  (cond
   ((null lst) (princ "commit;\n"))
   (t (progn
    (princ (mustache-render tmpl (car lst)))
    (my-render tmpl (cdr lst))))))

(my-render template context)

業務で単純なSQLをたくさん書かないと行けない時は、Excelを使って作っている。

ただ、Excelだと一対一のSQLは作りやすいけど、一つのキーで複数のテーブルを更新しに行く一対多のSQLは作りづらいから、こういうやり方も覚えておくと便利かもしれない。

追記

READMEを読んだら、リストをループさせるテンプレートの書き方も載ってた。

当たり前だけど、公開されているドキュメントは一通り見ておいて損はないね。

(require 'mustache)
mustache

(require 'ht)
ht

(mustache-render "
{{#hoge-list}}
* {{hoge}}
{{/hoge-list}}
"
         (ht ("hoge-list"
              (list
               (ht ("hoge" "hogehoge1"))
               (ht ("hoge" "hogehoge2"))))))
"
* hogehoge1
* hogehoge2
"

ちょっとだけ違う似たようなバッチをたくさん作らないといけなくなったので、Node.jsでHandlebars.jsを使えばいいやと思ったけど、どうやらPythonでもpybars3ってのでHandlebarsが使えるみたいなので、そっちでやってみる。

大量に似たようなバッチを作らないといけなくなり、Node.jsでやるかっと思ったら、目の前のPCには入ってない。

WinPythonは入っていて、WinPythonにはpybars3もインストール済みなようなので、日本語の情報がないことに不安を感じつつも使ってみることにした。

GitHub pybars3

入ってなければ

pip install pybars3

WindowsのCMDバッチだから、ファイルのエンコードは「cp932」

settings.json

[
  {
    "file_name": "A.bat",
    "message": "AAAA",
    "users": [{"name": "山田"}, {"name": "鈴木"}]
  },
  {
    "file_name": "B.bat",
    "message": "BBBB",
    "users": [{"name": "田中"}, {"name": "佐藤"}, {"name": "山本"}]
  }
]

template.hbs

@echo off

echo.
echo message: {{message}}

echo.
echo users:
{{#for users}}
echo * {{name}}
{{/for}}

exit /b

json_to_cmd.py

# -*- coding: utf-8 -*-

import json
from pybars import Compiler

def main():
    with open("settings.json", "r", encoding="cp932") as f:
        settings = json.load(f)

    with open("template.hbs", "r", encoding="cp932") as f:
        source = f.read()

    compiler = Compiler()
    template = compiler.compile(source)
    helpers = {"for": _for}

    for setting in settings:
        output = template(setting, helpers=helpers)
        with open(setting["file_name"], "w", encoding="cp932") as f:
            f.write(output)

def _for(this, options, items):
    result = []
    for thing in items:
        result.extend(options["fn"](thing))

    return result

if __name__ == "__main__":
    main()

Handlebarsそのものとは違うような気がしないでもないが、一応できた。

この感じでJSONとテンプレートを用意すれば、ちょっと違うけど別物っていうバッチを簡単に生み出せるから、楽ができそうだ。

やっぱり、退屈なことはPythonにやらせなきゃいけないからね。

Windows7の32bitにWinPythonを入れてJupyter Notebookを使ってみようと思うも「ImportError: DLL load failed: 指定されたモジュールが見つかりません。」と出て起動してくれない時に解決した方法

Windowsでお手軽にPythonを始めるためにWinPythonをインストールした。

インストーラーはGithubからWinPython-32bit-3.5.4.1Qt5.exをダウンロードしてくる。

インストール先はわかりやすいように「C:\WinPython」にした。

その後、インストールしたフォルダの中に入っている「WinPython Control Panel.exe」をダブルクリックで起動し、

Advanced -> Register distribution...

をクリックして、なんかメッセージがでるので、「Yes」をクリック。

次に

コントロールパネル -> システム -> システムの詳細設定 -> 詳細設定(タブ) -> 環境変数

のシステム環境変数の「PATH」にすでに入っているものを消さないように以下を追加

;C:\WinPython;C:\WinPython\python-3.5.4\Scripts

;(セミコロン)区切りで2つのパスを追加する。

準備が整ったのでおもむろに「Jupyter Notebook.exe」をダブルクリックする。

黒い画面が表示され、おっきたかっと思ってしばらく待ってみると

Traceback (most recent call last):
  File "C:\WinPython\python-3.5.4\lib\runpy.py", line 193, in _run_module_as_main
    "__main__", mod_spec)
  File "C:\WinPython\python-3.5.4\lib\runpy.py", line 85, in _run_code
    exec(code, run_globals)
  File "C:\WinPython\python-3.5.4\scripts\jupyter-notebook.exe\__main__.py", line 5, in <module>
  File "C:\WinPython\python-3.5.4\lib\site-packages\notebook\notebookapp.py", line 42, in <module>
    from zmq.eventloop import ioloop
  File "C:\WinPython\python-3.5.4\lib\site-packages\zmq\__init__.py", line 34, in <module>
    from zmq import backend
  File "C:\WinPython\python-3.5.4\lib\site-packages\zmq\backend\__init__.py", line 40, in <module>
    reraise(*exc_info)
  File "C:\WinPython\python-3.5.4\lib\site-packages\zmq\utils\sixcerpt.py", line 34, in reraise
    raise value
  File "C:\WinPython\python-3.5.4\lib\site-packages\zmq\backend\__init__.py", line 27, in <module>
    _ns = select_backend(first)
  File "C:\WinPython\python-3.5.4\lib\site-packages\zmq\backend\select.py", line 26, in select_backe
nd
    mod = __import__(name, fromlist=public_api)
  File "C:\WinPython\python-3.5.4\lib\site-packages\zmq\backend\cython\__init__.py", line 6, in <mod
ule>
    from . import (constants, error, message, context,
ImportError: DLL load failed: 指定されたモジュールが見つかりません。

C:\WinPython\notebooks>

なるほど。なんかDLLが足らないらしい、エラーメッセージをもとに「WinPython モジュールが見つかりません」とかググって見ても一致するものがでてこない。

そこで、一旦、WinPythonは忘れてモジュールが見つからんとエラー吐いている「zmq」を調べると、エラーメッセージそのものズバリではないもののバージョンアップの方法がわかった。

とりあえず、ものは試しなのでそこに書かれているコマンドを打ってバージョンアップしてみる。(コマンドを打つのはさっきのエラーメッセージが出たコマンドプロンプトの黒い画面のところでいい)

C:\WinPython\notebooks>pip install --upgrade pyzmq
Collecting pyzmq
  Downloading pyzmq-17.0.0-cp35-cp35m-win32.whl (812kB)
    100% |################################| 819kB 109kB/s
Installing collected packages: pyzmq
  Found existing installation: pyzmq 16.0.3
    Uninstalling pyzmq-16.0.3:
      Successfully uninstalled pyzmq-16.0.3
Successfully installed pyzmq-17.0.0

C:\WinPython\notebooks>

今、入ってたのが16.0.3で17.0.0に上がったわけか。

そのコマンドプロンプトは閉じて、もう一度、「Jupyter Notebook.exe」をダブルクリックする。

また黒い画面が起動して来て、

[I 10:50:30.048 NotebookApp] Writing notebook server cookie secret to C:\WinPython\settings\runtime\
notebook_cookie_secret
[I 10:50:43.351 NotebookApp] JupyterLab alpha preview extension loaded from C:\WinPython\python-3.5.
4\lib\site-packages\jupyterlab
[I 10:50:46.073 NotebookApp] Serving notebooks from local directory: C:\WinPython\notebooks
[I 10:50:46.074 NotebookApp] 0 active kernels
[I 10:50:46.075 NotebookApp] The Jupyter Notebook is running at:
[I 10:50:46.075 NotebookApp] http://localhost:8888/?token=ac22eb5a9a5027ee205ec50bad0cf28f438a76898c
c2883d
[I 10:50:46.076 NotebookApp] Use Control-C to stop this server and shut down all kernels (twice to s
kip confirmation).
[C 10:50:46.085 NotebookApp]

    Copy/paste this URL into your browser when you connect for the first time,
    to login with a token:
        http://localhost:8888/?token=ac22eb5a9a5027ee205ec50bad0cf28f438a76898cc2883d
     

っと表示され、暫く待つと、IEも起動して使える状態になった。

まぁ使えるようにはなったけど、「Jupyter Notebook」ってのがなんなのかわかってないので、これから使い方を覚えないと行けないんだけどね。

Oracleの簡単なSQLを実行するBashのシェルスクリプトを今年になってからいろんなところで書くので自分用のテンプレート

ちょっとした確認用のSQLを実行するのにSQL Developerを立ち上げるのが重いというのもあり、よく実行するものはシェルスクリプトにしている。

今年に入ってから、いろいろな場所で同じようなSQLばかり書いているので、面倒だと感じ始めたからシェルスプリクトにする。

ただ、それぞれ別の場所で作るから毎回微妙に違うものを書いていたので、次に自分が作るとき用のメモ。

自分がよく作るのはGit for WindowsBashOracleに接続してSQLを投げるシェルスプリクト。

そのまま実行するとSELECTするSQLが実行され、-aをつけるとINSERTするSQLを実行する。

作るもののほとんどがマスターに登録があるかないかを確認する系のSQLで、EXISTSで確認して無かったら追加する流れなので、値は指定しない事が多い。

#!/bin/bash
#
# 説明

readonly ORACLE_CONNECTION=USER/PASSWORD@SERVER:PORT/SID

function usage() {

cat <<_EOF_

Usage:
  $0 [-a]

Description:
  説明

Option:
  -a ADD DATA

_EOF_

exit 1
}

err() {
  echo "[$(date +'%Y-%m-%dT%H:%M:%S%z')]: $@" >&2
}

function select_data() {

sqlplus -s $ORACLE_CONNECTION << EOF

SELECT *
  FROM USERS A
 WHERE NOT EXISTS (
           SELECT * FROM USERS2 B WHERE A.ID = B.ID
       )
;

EXIT;

EOF

  if [ $? -ne 0 ]; then
    err "ERROR SELECT"
    return 1
  fi

  return 0
}

function insert_data() {

sqlplus -s $ORACLE_CONNECTION << EOF

INSERT INTO USERS(
    ID
  , NAME
)
SELECT A.ID
     , A.NAME
  FROM USERS2 A
 WHERE NOT EXISTS (
           SELECT * FROM USERS B WHERE A.ID = B.ID
       )
;

COMMIT;

EXIT;

EOF

  if [ $? -ne 0 ]; then
      err "ERROR INSERT"
      return 1
  fi

  return 0
}

FLAG_A=""
if [ "$OPTIND" = 1 ]; then
  while getopts abf:h OPT
  do
    case $OPT in
      a)
        FLAG_A="on"
        ;;
      h)
        usage
        ;;
      \?)
        echo "Try to enter the h option." 1>&2
        ;;
    esac
  done
else
  echo "No installed getoptions-command." 1>&2
  exit 1
fi

function main() {

  if [ -n "$FLAG_A" ]; then
    insert_data
  else
    select_data
  fi

  return 0
}

main

emacsでVB6のコードを見る。そのままでも見れるけど、やっぱり白黒ではあじけないのでvisual-basic-mode.elを入れてみる。

EmacsWikiからvisual-basic-mode.elをダウンロードする。

ダウンロードしたらどこでもいいけど「~/.emacs.d/elisp/visual-basic-mode.el」なんかに置く。

おもむろにinit.elに追記する。

(add-to-list 'load-path "~/.emacs.d/elisp")  ;ファイルを置いたところのパスを追加

;;; VB mode
(require 'visual-basic-mode)
(add-to-list 'auto-mode-alist
         '("\\.\\(frm\\|bas\\|cls\\|vbs\\)$" . visual-basic-mode))

これで、VB6のコードも少しは見る気になれるだろうか。

十数年前に、今、私の目の前にあるコードを書いた人は、まさか2018年になってもまだ自分の作ったプログラムが使われ続け、今もなおメンテナンスされているとは思わないだろう。

システムはいつの時も作者の予想よりも長く運用されるもの。

PL/SQLでSELECT INTOをした時にORA-01422が出る場合には、一度、引数とか変数の名前をカラム名と同じにしていないか確認しよう。

ORA-01422がでるのは、PL/SQLで変数にどこかのテーブルの値を検索してセットする時なんかに、戻ってくる値が一意にならないからエラーになる。

例えば下記のようにするとエラーになる。

CREATE OR REPLACE PROCEDURE SAMPLE (
 USER_ID IN NUMBER
) AS

  V_USER_NAME VARCHAR2(32 CHAR)

BEGIN

 SELECT USER_NAME INTO V_USER_NAME
    FROM USERS
  WHERE USER_ID = USER_ID;

  DBMS_OUTPUT.PUT_LINE('USER_NAME: ' || V_USER_NAME);

EXCEPTION
  WHEN OTHERS THEN
    DBMS_OUTPUT.PUT_LINE('ERROR CODE: ' || SQLCODE || ', MESSAGE: ' || SQLERRM);
  END;
END;

この場合、引数のUSER_IDとUSERSテーブルのカラム名のUSER_IDがまったく同じなので、エラーになる。

例えば、引数のUSER_IDを「SAMPLE」にするとWHERE句が「'SAMPLE' = 'SAMPLE'」となり、まったく同じ値で比較してしまう。

これだとUSERSテーブルのすべてが返ってきてしまうので、V_USER_NAMEに何をセットしたらいいかわからなくて、ORA-01422が出てしまう。

 SELECT USER_NAME INTO USER_NAME
    FROM USERS
  WHERE 'SAMPLE' = 'SAMPLE';

それを回避するには、単に引数名や変数名をカラム名と同じにしなければいい。

引数は「P_(アンダーバー)」で始め、変数は「V_(アンダーバー)」で始めれば区別もついてわかりやすいと思う。

上のコードの引数の部分を書き換えて、P_USER_IDにすれば問題なく動いてくれる。

CREATE OR REPLACE PROCEDURE SAMPLE (
 P_USER_ID IN NUMBER
) AS

  V_USER_NAME VARCHAR2(32 CHAR)

BEGIN

 SELECT USER_NAME INTO V_USER_NAME
    FROM USERS
  WHERE USER_ID = P_USER_ID;

  DBMS_OUTPUT.PUT_LINE('USER_NAME: ' || V_USER_NAME);

EXCEPTION
  WHEN OTHERS THEN
    DBMS_OUTPUT.PUT_LINE('ERROR CODE: ' || SQLCODE || ', MESSAGE: ' || SQLERRM);
  END;
END;

当初はエラーを出力していなかったので、なんでこけるのかわからず、無駄な時間を過ごした。

2018年、正月三が日の深夜の思い出。