psql 出力のレイアウト

psql を使うと SQL 言語を使ったデータベースの処理が手軽にできますが、出力はシンプルです。例えば次のような 成績管理の テーブルに、

create table test (
	date date,
	id int4,
	name text,
	kamoku_id int4,
	kamoku text,
	tokuten int4
);

次のようなデータが入っていたとします。

mydb=> select * from test;
      date|id|name    |kamoku_id|kamoku|tokuten
----------+--+--------+---------+------+-------
04-10-2000| 1|nakayama|        1|kokugo|     75
04-10-2000| 1|nakayama|        2|sansu |     58
04-10-2000| 1|nakayama|        3|shakai|     62
04-10-2000| 1|nakayama|        4|rika  |     78
04-10-2000| 2|watanabe|        1|kokugo|     52
04-10-2000| 2|watanabe|        2|sansu |     43
04-10-2000| 2|watanabe|        3|shakai|     26
04-10-2000| 2|watanabe|        4|rika  |     48
06-02-2000| 1|nakayama|        1|kokugo|     82
06-02-2000| 1|nakayama|        2|sansu |     68
06-02-2000| 1|nakayama|        3|shakai|     75
06-02-2000| 1|nakayama|        4|rika  |     88
(12 rows)

ここで id = 1 の nakayama さんのデータを見たいときは次のようになります。

mydb=> select * from test where id = 1;
      date|id|name    |kamoku_id|kamoku|tokuten
----------+--+--------+---------+------+-------
04-10-2000| 1|nakayama|        1|kokugo|     75
04-10-2000| 1|nakayama|        2|sansu |     58
04-10-2000| 1|nakayama|        3|shakai|     62
04-10-2000| 1|nakayama|        4|rika  |     78
06-02-2000| 1|nakayama|        1|kokugo|     82
06-02-2000| 1|nakayama|        2|sansu |     68
06-02-2000| 1|nakayama|        3|shakai|     75
06-02-2000| 1|nakayama|        4|rika  |     88
(8 rows)

しかし、これでは、時間的に各科目の点数がどう変わって行ったかを見るのには不便です。psql の \g | コマンドを使うと検索結果を shell コマンドにパイプで送ることができますから、後で述べる layout.pl にこのデータをパイプラインでおくると、次のような表示になります。

mydb=> \g | layout.pl 
 1  nakayama
           04-10-2000 06-02-2000 
kokugo         75         82
sansu          58         68
shakai         62         75
rika           78         88

この結果をさらに lpr にパイプすると印刷することができます。

mydb=> \g | layout.pl | lpr

layout.pl のソースは次のようになります。

#!/usr/bin/perl

while(<>) {
	push @line, $_;
}
shift @line;
shift @line;
pop @line;
pop @line;

while( $line = shift @line ) {
	chop $line;
	($date, $id, $name, $kamoku_id, $kamoku, $tokuten) = split( /\|/, $line );
	push @date, $date;
	push @kamoku_id, $kamoku_id;
	$label[$kamoku_id]=$kamoku;
	$tensu[$kamoku_id]{$date}=$tokuten;
}

unique( \@date, \@date_out );
unique( \@kamoku_id, \@kamoku_id_out);

print "$id  $name\n";
print "           ";
foreach $day (@date_out) {
	print $day, ' ';
}
print "\n";

foreach $kid (@kamoku_id_out) {
	print $label[$kid];
	foreach $day (@date_out) {
		printf "%11s", $tensu[$kid]{$day};
	}
	print "\n";
}

sub unique {
	my $list = shift;
	my $out = shift;
	my @list = sort @$list;
	my $prev = '';
	my $cur;
	while ( $cur = shift @list ) {
		push @$out, $cur if $prev ne $cur;
		$prev = $cur;
	}
}

プログラムのアイディアを簡単に説明すると次のようになります。まず、psql の出力の表の頭2行と足2行を切り捨てます。つぎに各行のデータを split 関数でとりだして、配列や2次元配列にいれます。後はそれを加工して、レイアウトします。unique サブルーチンは、配列をソートし同じ内容の項目があったら取り除きます。SQL の distinct のような働きをします。単純な発想のスクリプトなので簡単に応用できると思います。

また、次のようなシェルスクリプト student を作ると、上に述べた操作を自動化できます。student は chmod 755 で実行可能にしておきます。

#!/bin/sh
psql mydb -qc "select * from test where id=$1" | layout.pl

student を使って検索するには次のように引数に生徒の id を指定します。

$ ./student 1
 1  nakayama
           04-10-2000 06-02-2000 
kokugo         75         82
sansu          58         68
shakai         62         75
rika           78         88