Humanity

Edit the world by your favorite way

SQLのデータをtableタグから作成する

※この記事はVim Advent Calendar 2012の283日目の記事です。


つい最近レビュー記事を書くプレッシャーからの現実逃避にSQLの練習をやっていました。

テーブルのデータを生成する時にVimを少しだけ使ったので、今日はその時のことを書きます。

tableタグやExcelファイルとはTSV形式でやり取りする

まずテーブルのデータをVim Advent Calendar 2012のページの表からVimのバッファにコピペします。
Vimのバッファのようなテキストの世界と、HTMLページのtableタグやExcelファイルの世界でやり取りするにはTSV(Tab Separated Values)形式が便利です。
その名の通りタブ文字で各項目/各セルが区切られたテキストです。


Firefoxだと表をCtrl+マウスドラッグでコピペするとTSV形式のテキストがコピペできます。
ChromeだとCtrlを押さなくてもTSV形式になりました。
IEだとスペース区切りになってしまいました。
スペースは普通の文字列に頻出するのでこれではあまりうれしくありませんね。
FirefoxChromeを使ってください。
ちなみに今回tableタグからVimのバッファにコピペするのにTSV形式を使いましたが、逆もいけます。
つまりなんらかの手段でTSV形式のテキストを生成してコピーすればExcelファイルに貼り付ける事ができます。
Excelを使わなければいけないお仕事で地味に役立ちます。*1


ちなみにコピーしたテキストをVimのバッファにペーストする時は、中クリックでもいけます。
この場合

  1. tableタグからTSV形式のテキストをコピー (この時手はマウスに置かれているはず)
  2. Vimを立ち上げる (この時Vimのバッファは空の状態)
  3. Vimのバッファにペーストする

という手順を踏むため、マウスから手を離してペーストするより中クリックでペーストしてしまった方が速いです。

編集してINSERT文を生成しよう

tableタグからコピペしたら、Vimのバッファは以下のような状態になっているはずです。

001(タブ文字)12/01(土)(タブ文字)@manga_osyo(タブ文字)Vim プラグインgithub で公開するまで
002(タブ文字)12/02(日)(タブ文字)@kaoriya(タブ文字)minimap-vimができるまで
003(タブ文字)12/03(月)(タブ文字)@thinca(タブ文字)Sublime Text 2 のあの機能を使う
004(タブ文字)12/04(火)(タブ文字)@mattn_jp(タブ文字)モテる Vim 使いに読み書き出来ないファイルなどなかったんだよ!

(省略)

275(タブ文字)09/01(日)(タブ文字)@manga_osyo(タブ文字)Vim でプロ生ちゃんを連続再生する
276(タブ文字)09/02(月)(タブ文字)@tyru(タブ文字)「実践Vim 思考のスピードで編集しよう!」レビュー (1日目)
277(タブ文字)09/03(火)(タブ文字)@manga_osyo(タブ文字)Vim で非同期で Clang を使用したコード補完を行う
278(タブ文字)09/04(水)(タブ文字)@KSuzukiii(タブ文字)ファイルに保存する。(:sav[eas])

この状態からINSERT文を生成していきましょう。
そのためにはまずどのようなテーブルにどのような項目をINSERTするのか決めるために、DDLを決めておきます。
DDLは以下の通りです。(ddl/VimAdventCalendar.sql)

USE [VimAdventCalendar]
GO

if exists (select * from sysobjects where id =
object_id(N'[dbo].[VimAdventCalendar]') and
  OBJECTPROPERTY(id, N'IsUserTable') = 1)
DROP TABLE [dbo].[VimAdventCalendar]
GO
CREATE TABLE [dbo].[VimAdventCalendar] (
    [nth] [int] not null,
    [date] [datetime] not null,
    [username] [nchar] (32) not null,
    [title] [nchar] (256) not null,
    PRIMARY KEY (nth)
) ON [PRIMARY]
GO

SQLを見て分かる通りDBはMicrosoft SQL Server 2012 Expressです。
さっきのTSV形式のテキストと同じく4項目ですし、項目を追加する必要はなさそうですね。
ということは各項目をINSERTできるSQLの値に編集するだけでよさそうです。

各項目を文字列化

まずは文字列として扱うため、各項目を「'(シングルクォート)」で囲みましょう。

:%s/[^\t]\+/'&'/g

「&(アンパサンド)」はマッチした文字列を意味します。
括弧*2でくくるよりこちらの方がタイプ数も少なくて楽です。

nth(n日目、番号)を編集

今度は左から順番に編集していきましょう。
nthはint型なので先程つけたシングルクォートは不要ですので取ります。
また「001」のように先頭が0埋めされているので、SQL Serverでは先頭0の数値を8進数として解釈する事はないようですが*3、気持ち悪いので取ってしまいましょう。


まずは確認のため検索します。

/^'0*\(\d\+\)'

これでハイライトされている部分を目視で確認し、一括置換しても構わないか確認します。
括弧をつけたのは置換のためです。
確認して問題ないようだったら、対象行をヴィジュアルモードで選択したのち以下を実行して置換します。

:'<,'>s//\1/

このようにパターンを省略すると「/」レジスタの内容(=前回検索したパターン)が使われます。
ちなみに1行につき1個だけなので、gフラグは付けなくても結構です。

date(日付)を編集

今度は日付です。
「12/01(土)」→「2012/12/01」のように編集する必要があります。
しかし1月以降は2013年なので「09/01(日)」→「2013/09/01」のように編集します。
先に数の少ない12月から手を付けましょう。


前回と同じく確認してから一括置換します。
まずは12月の各日付に2012年を付加し、曜日を削除します。

/\(12\/\d\+\)([土日月火水木金])
:'<,'>s//2012\/\1/

次に1月以降の各日付を編集します。

" /でコマンドラインに入って<C-p>で前回のパターンを挿入
/<C-p>

/\(12\/\d\+\)([土日月火水木金])

" 12を「\d\+」に変更
/\(\d\+\/\d\+\)([土日月火水木金])

" 対象行をヴィジュアルモードで選択したのち「:(コロン)」でコマンドラインモードに入る
:'<,'>

" コマンドラインで<Up>を押すと履歴から先頭一致するものを取り出せる
" (便利なので私は<Up>, <Down>をそれぞれ<C-p>, <C-n>に割り当てています)
:'<,'><Up>

:'<,'>s//2013\/\1/

" 2013を2012に変更
:'<,'>s//2012\/\1/


また、ある程度正規表現が複雑になってきて「あ゛ーッ」という感じになってきたら、おもむろにコマンドラインを押してコマンドラインウインドウに移り正規表現を組み立てた方が楽です。
ちなみにコマンドラインウインドウでは過去の履歴もyyGpでコピペできます。
今回は「/」レジスタからパターンをコピペするのと2012を2013に変えるだけだったので、私はコマンドラインで済ませました。

username(ユーザID)を編集

無駄なので「@tyru」→「tyru」のように先頭の@を取ります。
しかし「@」で検索すると@mopp_jpさんの「カーソルキーさん@つかわない インサートモード編」がひっかかることから、ユーザ名だけにヒットする正規表現を組み立てる必要があることが分かりました*4
なので便利な「\zs」と「\ze」を使います。
「\zs」と「\ze」については Vimのニッチな正規表現で遊ぼう - HumanityVimの正規表現は戦闘用 - Humanity など何度か書いているのでそちらを参照してください。

/'\zs@\ze\w\+'

これでユーザ名の前の「@」だけにマッチします。
あとは前回と同じく

:'<,'>s///

で一括削除です。


title(記事名)は今回特に編集しません。

タブ文字をカンマに置き換える

INSERTの項目に変換するため、タブ文字をカンマに置き換えます。

:%s/\t/, /g
「insert into ... ( ... );」で囲む (まずは行頭)

ここまで正規表現を使ってきましたが、メタ文字のことを考えるのも面倒なので、私は行頭と行末に挿入するのであればを使います。
まず先頭からです。
この段階でバッファの内容は以下のようになっています。(先頭のみ)

1, '2012/12/01', 'manga_osyo', 'Vim プラグインgithub で公開するまで'

行頭でを押してGで最終行まで移動し、Iで挿入する準備*5を始めます。
そして次のように「insert into VimAdventCalendar(nth, date, username, title) values(」を入力し、で抜ければ全行の行頭に挿入されます。

insert into VimAdventCalendar(nth, date, username, title) values(1, '2012/12/01', 'manga_osyo', 'Vim プラグインgithub で公開するまで'

「insert into ... ( ... );」で囲む (行末)

次は行末です。
適当な位置でを押してGで最終行まで移動し、$で行末に移動してIで挿入する準備*6を始めます。
そして「);」を入力してで抜ければ全行の行末に挿入されます。

insert into VimAdventCalendar(nth, date, username, title) values(1, '2012/12/01', 'manga_osyo', 'Vim プラグインgithub で公開するまで');

仕上げ

ここまでくればINSERT文は出来上がっているので、DB名を指定すれば完成です。
1行目に以下のように挿入します。

USE [VimAdventCalendar]
GO

完成したファイルは data/entries.sql です。

あと書き

いかがでしたでしょうか。
普段からVimGolfで鍛えている皆様ならもっと効率の良い方法を思い付くかもしれません。
しかし普段対話的に操作するなら、操作している内にVimの独特の操作体系で一種のフロー状態に入っていくので、フローを途切れさせないためにExコマンドを多様したバッチ的な操作よりもあえてノーマルモードのコマンド多めで操作したりしています。


また本記事を読んで「正規表現だらけじゃねーか」と言ったあとに「Vim正規表現なしでは使えないのか...」
と言う人と「正規表現なら他のエディタでも使えるわー」と言う人がそれぞれいるかもしれませんが、
前者に対しては「使えないことはないが、効率的に編集しようと思ったらちょっとした正規表現がすらっと書けると便利」、
後者に対しては、先程言ったように「正規表現(コマンドラインモード)はおまけ。(フロー状態を作り出す)ノーマルモードこそ至高」と答えるでしょう。


言い換えればノーマルモードはVimmerの心のホームポジションです。
ちなみにもっとデータ量が多い場合、目視での確認を省いて、:g[lobal]コマンドや:s[ubstitute]コマンドでのバッチ処理もできるあたりVimは本当に引き出しが多く、また同時にその引き出しはいくらでも拡張可能なことに驚くばかりです。
Vimって本当に素晴らしいエディタですね。

*1:仕事中Excelとにらめっこする時間が圧倒的に多いなら、COM経由でWSHスクリプトからExcelを操作するのが一番楽できる近道のような気がします。Excel VBAがすでに使えるならWSHスクリプトを覚える必要はないかもしれませんが...

*2:Vim正規表現だとエスケープ付けないといけないですし

*3:これってSQL共通の仕様なんでしょうか?

*4:対話的に組み立てていけばいいので、事前に知る必要はありません

*5:エロい

*6:エロい