ラブびあ

ビール。ときどきラブ

東京メトロ オープンデータ活用コンテスト

東京メトロ オープンデータ活用コンテスト

「エンドユーザーのアプリから直接APIを呼んではいけない」

まじか。開発者登録した後に読める規約に書いてあるらしい。
これってつまり、ドメイン取って自前WEBサーバー立ててデータストア設計してWEBアプリ作って、
そこまで揃えてやっとスマホアプリ作りに入れるってことか。

スマホアプリ作るの初めてなんですぅ〜
という軟弱者の私が軽々しく参加できるコンテストではなさそう・・・

東京メトロAPIで取得できるデータを応答するWEBアプリ、で応募するかw


※2014/9/27追記

利用規約が改定され、上の条項は削除されました。
これで、どんなアプリでも自由に作ってくれたまえ、ということになったみたいです。

一緒に「トークンの扱いは自己責任で」的な規約が追加されましたが、
トークンなんて、最初からいらなかったんじゃ・・・

まぁ何か作ってみましょうかね。

ShiftJISの固定長ファイルを読み込む

VBScriptでShiftJISの固定長ファイルを読み込むテンプレ。
例によって cmd.vbs と wow64.vbs を同じフォルダへ入れておく。
サンプルでは text.txt を fixedlength.vbs にドロップすると、1レコードずつmsgboxします。

test.txt

123ほげ1  ふが1  
456ほげ2  ふが2  
789ほげ3  ふが3  

fixedlength.vbs

option explicit
dim wsh,fso
const ForReading   = 1
const ForWriting   = 2
const ForAppending = 8
set wsh = CreateObject("WScript.Shell")
set fso = CreateObject("Scripting.FileSystemObject")

include "cmd.vbs"
include "wow64.vbs"

call init
call main
call fini

set wsh = nothing
set fso = nothing

WScript.Quit '-----------------------------------------------------------------

sub init()
end sub

sub main()
	dim i
	for i = 0 to WScript.Arguments.Count - 1
		call func(WScript.Arguments(i))
	next
end sub

sub func(arg)
	dim ifile, defs, record
	defs = array(3,10,10,2) 'レコード定義

	set ifile = fso.OpenTextFile(arg, ForReading, False)
	do until ifile.AtEndOfStream
		record = readRecord(ifile, defs)
		if UBound(record) > 0 then
			msgbox join(record, vbCrLf)
		end if
	loop
	ifile.Close
	set ifile = nothing
end sub

sub fini()
end sub

sub include(filename)
	filename = fso.BuildPath(fso.GetParentFolderName(WScript.ScriptFullName), filename)
	ExecuteGlobal fso.OpenTextFile(filename, ForReading, False).ReadAll()
end sub

' 固定長ファイルから1レコード分読み込み、配列に分割して返す
' ifile   固定長ファイル
' defs    カラム長配列
' @return 1レコード分の配列
function readRecord(ifile, defs)
	dim arr,i,s,l,c
	redim arr(UBound(defs))
	for i = 0 to UBound(defs)
		s = ""
		l = 0
		do while l < defs(i)
			if ifile.AtEndOfStream then
				exit function
			end if
			c = ifile.Read(1)
			l = l + lenj(c)
			s = s & c
		loop
		arr(i) = s
	next

	readRecord = arr
end function

' ShiftJIS相当関数群 ---
' 文字列の切り出し
function midj(s, position, length)
	dim i,c,pos
	pos = 1
	for i = 1 to len(s)
		c = mid(s, i, 1)
		if pos >= position then
			midj = midj + c
		end if

		pos = pos + lenj(c)
		if pos >= position + length then
			exit function
		end if
	next
end function

' 1文字の長さ
function lenj(c)
	if(asc(c) >= 1 and asc(c) <= 255) then
		lenj = 1
	else
		lenj = 2
	end if
end function

' 右桁埋め(文字列の空白埋めなど)
function rpadj(s, length, padding)
	rpadj = midj(s & string(length, padding), 1, length)
end function

' 左桁埋め(数値のゼロ埋めなど)
function lpadj(s, length, padding)
	lpadj = strreverse(rpadj(strreverse(s), length, padding))
end function

bashでメニュー

bashでこんな感じのメニューを簡単に作る方法です。

■■■メインメニュー■■■
 0: submenu001.sh メニュー1
 1: submenu002.sh メニュー2
 q: 終了
コマンドを選択してEnterキーで決定します>

■作り方

  • メインメニューとサブメニューは同じフォルダに作成します
  • メインメニューはサブメニューを呼び出します
  • サブメニューに記述したコマンドを適宜実行する構成です
  • メインメニューにはサブメニュー内の「#@menu:サブメニュー」が表示されます
  • メインメニューはとりあえずコピペで動きます
  • サブメニューは適宜作成してください
  • 小さいシステムならサブメニューだけで、サブメニューを直接起動でも良いかもしれません

■メインメニュー
mainmenu.sh

#!/bin/bash

setMenu()
{
	IFS=$'\n'
	menu=(`grep "^#@menu:" *.sh`)
}

getMenu()
{
	IFS=$':'
	arr=($1)

	if [[ ${#arr[@]} -ge $2 ]]; then
		echo ${arr[$2]}
	else
		echo
	fi
}

printMenu()
{
	for (( i = 0; i < ${#menu[@]}; i++ ))
	do
		shell=`getMenu "${menu[$i]}" 0`
		title=`getMenu "${menu[$i]}" 2`
		printf "%2d: %s %s \n" $i ${shell} ${title}
	done
}

executeMenu()
{
	IFS=$':'
	item=(${menu[$1]})
	./${item[0]}
}

showMenu()
{
	while true; do
		clear
		echo ■■■メインメニュー■■■
		printMenu
		echo " q: 終了"
		read -p "コマンドを選択してEnterキーで決定します> " input

		if [[ ${input} =~ "[eEqQ]" ]]; then
			exit
		fi

		if [ ${input} -ge 0 ] && [ ${input} -lt ${#menu[@]} ]; then
			executeMenu ${input}
		fi
	done
}

setMenu
showMenu
exit 0

■サブメニュー
submenu001.sh

#!/bin/bash
#@menu:メニュー1
while true; do
	clear
	cat <<EOF
==================================================
メニュー1
==================================================
 1: サブメニュー1
 2: サブメニュー2
 3: サブメニュー3
 q: 終了
==================================================
EOF
	case "$input" in
	1)
		sudo ssh -t -t {remotehost} {command1}
		;;
	2)
		sudo ssh -t -t {remotehost} {command2}
		;;
	3)
		sudo ssh -t -t {remotehost} {command3}
		;;
	q|Q|e|E)
		exit 0
		;;
	*)
		;;
	esac
	read -p "メニューを選択してください(1-3:q)>" input
done

exit 0

重いSQLを抽出する

SQLDeveloperの(たしか)CPU上位SQLを少し改造したもの。
実行1回あたり5秒以上かかっているSQLを洗い出します。

SELECT s.INST_ID,
    (s.cpu_time/1000000) "CPU_Seconds",
    s.disk_reads "Disk_Reads",
    s.buffer_gets "Buffer_Gets",
    s.executions "Executions",
    CASE
        WHEN s.rows_processed = 0
        THEN NULL
        ELSE ROUND((s.buffer_gets/NVL(REPLACE(s.rows_processed,0,1),1)))
    END "Buffer_gets/rows_proc",
    ROUND((s.buffer_gets /NVL(REPLACE(s.executions,0,1),1))) "Buffer_gets/executions",
    (s.elapsed_time      /1000000) "Elapsed_Seconds",
    ROUND((s.elapsed_time/1000000)/NVL(REPLACE(s.executions,0,1),1)) "Elapsed/Execution",
    ga.sql_fulltext "SQL",
    s.module "Module",
    s.SQL_ID
FROM gv$sql s
LEFT JOIN gv$sqlarea ga ON s.inst_id = ga.inst_id and s.sql_id = ga.sql_id
WHERE s.sql_id IN
    ( SELECT DISTINCT sql_id
    FROM
        ( WITH sql_class AS
        (SELECT sql_id,
            state,
            COUNT(*) occur
        FROM
            (SELECT sql_id ,
                CASE
                    WHEN session_state = 'ON CPU'
                    THEN 'CPU'
                    WHEN session_state = 'WAITING'
                    AND wait_class    IN ('User I/O')
                    THEN 'IO'
                    ELSE 'WAIT'
                END state
            FROM gv$active_session_history
            WHERE session_type                         IN ( 'FOREGROUND')
            AND sample_time BETWEEN TRUNC(sysdate,'MI') - :minutes/24/60 AND TRUNC(sysdate,'MI')
            )
        GROUP BY sql_id,
            state
        ),
        ranked_sqls AS
        (SELECT sql_id,
            SUM(occur) sql_occur ,
            rank () over (order by SUM(occur)DESC) xrank
        FROM sql_class
        GROUP BY sql_id
        )
    SELECT sc.sql_id,
        state,
        occur
    FROM sql_class sc,
        ranked_sqls rs
    WHERE rs.sql_id = sc.sql_id
        --and rs.xrank <= :top_n
    ORDER BY xrank,
        sql_id,
        state
        )
    )
AND ROUND((s.elapsed_time/1000000)/NVL(REPLACE(s.executions,0,1),1)) > 5 -- SQL1回あたり5秒を超えるもの
ORDER BY "Elapsed/Execution" DESC NULLS LAST

SQL整形マクロ

SQL整形マクロ V1.04
http://hide.maruo.co.jp/lib/macro/sqlclean104.html

希望通りの動作のものを見つけられなかったので、
自分でも改造できそうだった↑に、ちょっとだけ好みを追加してみました。

■「シングルクォートで括られた」を文字列定数とする機能
スキップ用の変数をループの前に追加して、

	##Skip = -1;

シングルクォートが出てくる度にフラグを反転させます。

		// 文字列定数の時の処理キャンセル
		if(code == 39)
		{
			##Skip = ##Skip * -1;
			right;
			continue;
		}
		if(##Skip == 1)
		{
			right;
			continue;
		}

■改行を追加する条件のjoinをleftとinnerに変更。
この配列を変更

$EntElm[14]	="join"

やや実行速度が遅い気もしますが、今のところまずまず使わせて頂いています。

delete_flagに0/1

データベースでマスタを削除するとき、割り当て済みのプライマリキーを残すために削除フラグを立てて論理削除する設計があります。このときフラグだから0/1で、というシステムを山ほど見ましたが、100%暗黙の型変換なしの正しいSQLを書けているところはありませんでした。つまりdelete_flag=0とdelete_flag='0'は混在するのです。これはフラグという名前と0/1という値から、項目のデータ型を推測できないことが原因だと思うわけです。そこで、

delete_flagは文字列型で

値は文字列でfalse/trueとか、alive/deadとか、何ならO/Xとか、とにかく数字でなければ何でも良いです。これなら必ずdelete_flag='false'と記述されます。まぁ大文字小文字の間違いとか、頭文字だけにしたらどっちが有効だよ、とかいうのはありますが少なくとも暗黙の型変換が紛れ込む余地をなくせます。ぜひ誰かやってみてくださいw

小数点以下の桁数を指定して丸め

C#で小数点以下の桁数(digit)を指定して丸める関数です。標準関数は整数丸めなので、digitで10進数で桁シフトして、丸めて、元に戻す、としています。なお、マイナス値について、Floorはゼロに近い方への丸め、Ceilingはゼロから遠い方への丸め、に変更しています。

public static decimal Floor(decimal value, int digit)
{
    int shift = (int)Math.Pow(10, digit);
    return Math.Floor(Math.Abs(value) * shift) * Math.Sign(value) / shift;
}

public static decimal Round(decimal value, int digit)
{
    int shift = (int)Math.Pow(10, digit);
    return Math.Round(value * shift, MidpointRounding.AwayFromZero) / shift;
}

public static decimal Ceiling(decimal value, int digit)
{
    int shift = (int)Math.Pow(10, digit);
    return Math.Ceiling(Math.Abs(value) * shift) * Math.Sign(value) / shift;
}