Linux Gazette 2004年11月投稿記事
目次
投稿者、
tojileon :10/22/2004 - 04:36. 開発者|ハウツー| ヒント&初心者
Qtデザイナを用い、Qtで「Hello, World!」を表示する簡単なステップを示す。目的はプッシュボタンのあるウインドウを作って、ボタンをクリックすると「Hello, World!」がウインドウに現れるようにすることだ。これらステップ全部は Fedora Core 2 マシン上でQt 3.3.2 とKDE 3.2を用いて試験済みである。
0.ターミナルウインドウでコマンドdesigner & をタイプするか又はスタートメニューのプログラム作成セクションでQT Designerをクリックするかいずれかで、Qt Designerを開く。
1.ポップアップして新たに開くダイアログボックスで C++ Projectを選びOKをクリックする。
2.プロジェクト設定ダイアログが表示されて、プロジェクト名の入力を催促する。プロジェクト名をmyfirstqtと入力する。プロジェクト名の横にある楕円形(...)ボタンをクリックしてこの新プロジェクトの位置を選ぶことが出来る。別のディレクトリに作ると良い。
3.プロジェクト概観ウインドウのプロジェクト名(myfirstqt.pro)が出る。プロジェクト概観ウインドウが出ないときは、Window->Views メニューから選ぶ。
4.これからダイアログボックスを作る。そのためFile->Newを選ぶ。
5.新ダイアログボックスでDialogを選びOKをクリック。
6.Form1と言う名の新ダイアログボックスが出る筈。
7.ファイルメニューからSaveオプションを選んでForm1をセーブする。ダイアログボックスが出て、ファイル名を催促する。ファイル名はform1.ui 自体としてSave をクリック。
8.IDEにあるツールボックスのボタン区画にあるPushButtonをクリック。ツールボックスが出ないときは、 Window->Views->Toolbox。
9.ここでForm1 をクリック(Form1のタイトルバーではない)。Form1上にプッシュボタンが出る筈。走らせるとフォームがどうなるか見たいときは Preview->Preview Formを選ぶ。
10.Edit->Slotsを選ぶ。
11.現れるEditファンクションダイアログボックスの中で、New Functionボタンをクリック。newSlot()と言う名の新ファンクションが示される。OKをクリック。
12.Edit->Connectionを選ぶ。現れるView and Edit ConnectionダイアログでNewボタンをクリック。ダイアログボックスに新しい行が現れる。
13.Senderの下で、コンボボックスからPushButton を選ぶ。
14.Signalの下で clicked() を選ぶ。
15.Receiver.の下でForm1を選ぶ。
16.Slotの下で newSlot() を選ぶ。
17.OKをクリック。
18.Form1 上で右クリックしてSource...を選ぶ。
19.メッセージボックスが出て、新しいファイル ui.h を作るか否か聞いて来る。Yesをクリック。
20.何かコメントのあるエディタ・ウインドウが表示されその中にvoid Form1::newSlot() がある。
21. void Form1::newSlot() の後の括弧の間に、qWarning("Hello,World")の行を入れる。
次のようになる:
void Form1::newSlot()
{
qWarning("Hello, World");
}
22.File->Saveをクリック。
23.ここで、File->Newをクリック。
24.ダイアログボックスで、C++ Main-File を選びOKをクリック。
25. Configure Main-File ダイアログボックスが出る。OKをクリック。
26。エディタに main.cpp が出る。File->Save をクリックしてこのファイルをセーブ。
27.ここでターミナルウインドウを開いて、自分のプロジェクト (myfirstqt.pro)が常駐するディレクトリにcdする。
28.ターミナルウインドウでコマンドqmake を走らせる。
29.エラー無しならプロンプトが返る筈。ここで、コマンドmakeを走らせる。
30.再度、エラー無しならプロンプトが返る筈。プロンプトに./myfirstqtとタイプしてENTERを押す。
31.ウインドウのプッシュボタンをクリックするとターミナルウインドウが出る。
数日前に
Qtデザイナを用いて簡単なプログラムを書く方法を書いた。今回はQtを用いて「Hello, World!」プログラムを書くスマートな方法を述べる。ここでも目的は、プッシュボタンのあるウインドウを作って、ボタンをクリックすると「Hello, World!」がウインドウに現れるようにすることだ。これらステップ全部は Fedora Core 2 マシン上でQt 3.3.2 とKDE 3.2を用いて試験済みである。
0.ターミナルウインドウでコマンドdesigner & をタイプするか又はスタートメニューのプログラム作成セクションでQT Designerをクリックするかいずれかで、Qt Designerを開く。
1.ポップアップして新たに開くダイアログボックスで C++ Projectを選びOKをクリックする。
2.プロジェクト設定ダイアログが表示されて、プロジェクト名の入力を催促する。プロジェクト名をmyfirstqtと入力する。プロジェクト名の横にある楕円形(...)ボタンをクリックしてこの新プロジェクトの位置を選ぶことが出来る。別のディレクトリに作ると良い。
3.プロジェクト概観ウインドウのプロジェクト名(myfirstqt.pro)が出る。プロジェクト概観ウインドウが出ないときは、Window->Views メニューから選ぶ。
4.これからダイアログボックスを作る。そのためFile->Newを選ぶ。
5.新ダイアログボックスでDialogを選びOKをクリック。
6.Form1と言う名の新ダイアログボックスが出る筈。注記:このダイアログボックスの名を見るには、プロパティ・エディタ・ウインドウのネーム・プロパティを見る。プロパティ・エディタ・ウインドウが出ないときはWindow->Views->Property Editor/Signal Handlers を選ぶ。
7.ファイルメニューからSaveオプションを選んでForm1をセーブする。ダイアログボックスが出て、ファイル名を催促する。ファイル名はform1.ui 自体としてSave をクリック。
8.IDEにあるツールボックスのボタン区画にあるPushButtonをクリック。ツールボックスが出ないときは、 Window->Views->Toolbox。
9.ここでForm1 をクリック(Form1のタイトルバーではない)。Form1上にプッシュボタンが出る筈。走らせるとフォームがどうなるか見たいときは Preview->Preview Formを選ぶ。
10.Edit->Slotsを選ぶ。
11.現れるEditファンクションダイアログボックスの中で、New Functionボタンをクリック。newSlot()と言う名の新ファンクションが示される。OKをクリック。
12.Edit->Connectionを選ぶ。現れるView and Edit ConnectionダイアログでNewボタンをクリック。ダイアログボックスに新しい行が現れる。
13.Senderの下で、コンボボックスからPushButton1を選ぶ。
14.Signalの下で clicked() を選ぶ。
15.Receiver.の下でForm1を選ぶ。
16.Slotの下で newSlot() を選ぶ。
17.OKをクリック。
18.ここで、 File->Newをクリック。
19.ダイアログボックスで、C++Header File を選びOKをクリック。
20.新しいheader file が表示されたら、次のコードを追加する:
#include <form1.h>
class myform1: public Form1
{
public slots:
void newSlot();
};
21.File->Save。ファイル名を myform1.h と示す。
22. File->New を選ぶ。
23.ダイアログボックスで、C++ Source Fileを選んでOKをクリック。
24.表示される新しいソースファイルに、次のコードを追加する:
#include <myform1.h>
void myform1::newSlot()
{
qWarning("Hello, World!");
}
25. File->Save を選んで、ファイル名を myform1.cppと示す。
26.ここで、File->Newをクリック。
27.ダイアログボックスで、C++ Main-File を選んでOKをクリック。
28.Configure Main-Fileダイアログボックスがでる。OKをクリック。エディタにがmain.cpp 出る。
29. main.cpp の2行目と7行目を編集して、form1.hを myform1.h に、Form1 を myform1に変更する。
30.これら二つの変更の後、main.cpp は次のようになる筈:
:
#include <qapplication.h>
#include "myform1.h"
int main( int argc, char ** argv )
{
QApplication a( argc, argv );
myform1 w;
w.show();
a.connect( &a, SIGNAL( lastWindowClosed() ), &a, SLOT( quit() ) );
return a.exec();
}
31. File->Save をクリックしてこのファイルをセーブする。
32.ここでターミナルウインドウを開いて、自分のプロジェクト (myfirstqt.pro) が常駐するディレクトリにcdする。
33.ターミナルウインドウで、コマンドqmakeを走らせる。
34.エラー無しならプロンプトが返る筈。ここで、コマンドmakeを走らせる。
35.再度、エラー無しならプロンプトが返る筈。プロンプトに./myfirstqtとタイプしてENTERを押す。
36.ウインドウのプッシュボタンをクリックするとターミナルウインドウが出る。
36. Click on the Push Button on the Window you get and see the terminal window!
次回は、記事の初めに何をしたか内部で何が起こったかを説明し、2〜3のステップを合併して一つにし、最後に次の記事の予告をする
Posted by
jayeshks on Wed, 11/03/2004 - 19:36.
Oracle10g 非ASMデータベースのASMへの転換
投稿者、
JWH :11/03/2004 - 03:28. 記事|システム管理
この記事は、Oracle 10g Rman Referenceマニュアルの指針にしたがう技術演習である。しかし、二度もRman Referenceから指針を再現することが出来なかったので、自分でしなければならなかった。データベースをファイルシステムからAMSに移植する能力を提供するバージョン10gの新規 Recovery Manager 機能は、大変役に立つ。
(詳細は原文参照)
ASMインスタンスがあるのを確認すること。ASMボリュームグループ+DATA1もまた作られていると仮定する。"dbca" を用いて /u02/oradataの中にデータベース "convdata"を作成する。対応する init.ora ファイルを$ORACLE_HOME/dbsにコピイする:
$cp /u01/app/oracle/admin/convdata/pfile/init.ora.929200412032 \
> $ORACLE_HOME/dbs/initconvdata.ora
$cd $ORACLE_HOME/dbs
以下の入力を initconvdata.ora :- に追加
CONTROL_FILES='+DATA1/controlf01'
DB_CREATE_FILE_DEST='+DATA1'
DB_CREATE_ONLINE_LOG_DEST_1='+DATA1'
$ mv spfileconvdata.ora spfileconvdata.ora.orig
$ export ORACLE_SID=convdata
$ $ORACLE_HOME/bin/rman
RMAN>connect target
connected to target database (not started)
RMAN>startup nomount;
Oracle instance started
Total System Global Area 188743680 bytes
Fixed Size 778036 bytes
Variable Size 162537676 bytes
Database Buffers 25165824 bytes
Redo Buffers 262144 bytes
RMAN> restore controlfile from '/u02/oradata/convdata/control01.ctl' ;
Starting restore at 02-NOV-04
using target database controlfile instead of recovery catalog
allocated channel: ORA_DISK_1
channel ORA_DISK_1: sid=270 devtype=DISK
channel ORA_DISK_1: copied controlfile copy
output filename=+DATA1/controlf01
Finished restore at 02-NOV-04
RMAN> alter database mount;
database mounted
released channel: ORA_DISK_1
RMAN> backup as copy database format '+DATA1';
Starting backup at 02-NOV-04
allocated channel: ORA_DISK_1
channel ORA_DISK_1: sid=270 devtype=DISK
channel ORA_DISK_1: starting datafile copy
input datafile fno=00001 name=/u02/oradata/convdata/system01.dbf
output filename=+DATA1/convdata/datafile/system.317.1 tag=TAG20041102T133452
recid=2 stamp=541172167
channel ORA_DISK_1: datafile copy complete, elapsed time: 00:01:16
channel ORA_DISK_1: starting datafile copy
input datafile fno=00003 name=/u02/oradata/convdata/sysaux01.dbf
output filename=+DATA1/convdata/datafile/sysaux.318.1 tag=TAG20041102T133452
recid=3 stamp=541172205
channel ORA_DISK_1: datafile copy complete, elapsed time: 00:00:46
channel ORA_DISK_1: starting datafile copy
input datafile fno=00005 name=/u02/oradata/convdata/example01.dbf
output filename=+DATA1/convdata/datafile/example.319.1 tag=TAG20041102T133452
recid=4 stamp=541172249
channel ORA_DISK_1: datafile copy complete, elapsed time: 00:00:37
channel ORA_DISK_1: starting datafile copy
input datafile fno=00002 name=/u02/oradata/convdata/undotbs01.dbf
output filename=+DATA1/convdata/datafile/undotbs1.320.1 tag=TAG20041102T133452
recid=5 stamp=541172262
channel ORA_DISK_1: datafile copy complete, elapsed time: 00:00:15
channel ORA_DISK_1: starting datafile copy
input datafile fno=00004 name=/u02/oradata/convdata/users01.dbf
output filename=+DATA1/convdata/datafile/users.321.1 tag=TAG20041102T133452
recid=6 stamp=541172272
channel ORA_DISK_1: datafile copy complete, elapsed time: 00:00:08
channel ORA_DISK_1: starting datafile copy
copying current controlfile
output filename=+DATA1/convdata/controlfile/backup.322.1
tag=TAG20041102T133452 recid=7 stamp=541172279
channel ORA_DISK_1: datafile copy complete, elapsed time: 00:00:03
Finished backup at 02-NOV-04
RMAN> switch database to copy;
datafile 1 switched to datafile copy "+DATA1/convdata/datafile/system.317.1"
datafile 2 switched to datafile copy "+DATA1/convdata/datafile/undotbs1.320.1"
datafile 3 switched to datafile copy "+DATA1/convdata/datafile/sysaux.318.1"
datafile 4 switched to datafile copy "+DATA1/convdata/datafile/users.321.1"
datafile 5 switched to datafile copy "+DATA1/convdata/datafile/example.319.1"
RMAN> alter database open ;
database opened
RMAN> delete copy of database;
released channel: ORA_DISK_1
allocated channel: ORA_DISK_1
channel ORA_DISK_1: sid=270 devtype=DISK
List of Datafile Copies
Key File S Completion Time Ckp SCN Ckp Time Name
------- ---- - --------------- ---------- --------------- ----
8 1 A 02-NOV-04 392632 02-NOV-04 /u02/oradata/convdata/system01.dbf
9 2 A 02-NOV-04 392632 02-NOV-04 /u02/oradata/convdata/undotbs01.dbf
10 3 A 02-NOV-04 392632 02-NOV-04 /u02/oradata/convdata/sysaux01.dbf
11 4 A 02-NOV-04 392632 02-NOV-04 /u02/oradata/convdata/users01.dbf
12 5 A 02-NOV-04 392632 02-NOV-04 /u02/oradata/convdata/example01.dbf
Do you really want to delete the above objects (enter YES or NO)? YES
deleted datafile copy
datafile copy filename=/u02/oradata/convdata/system01.dbf recid=8 stamp=541172332
deleted datafile copy
datafile copy filename=/u02/oradata/convdata/undotbs01.dbf recid=9 stamp=541172332
deleted datafile copy
datafile copy filename=/u02/oradata/convdata/sysaux01.dbf recid=10 stamp=541172332
deleted datafile copy
datafile copy filename=/u02/oradata/convdata/users01.dbf recid=11 stamp=541172333
deleted datafile copy
datafile copy filename=/u02/oradata/convdata/example01.dbf recid=12 stamp=541172333
Deleted 5 object
新規TEMP テーブルスペース作成中
SQL> create temporary tablespace temptbl;
Tablespace created.
SQL> alter database convdata default temporary tablespace temptbl;
Database altered.
SQL> drop tablespace temp including contents;
Tablespace dropped.
Redo ログを ASM に移動中
SQL> select lf.member, l.bytes
from v$log l, v$logfile lf
where l.group# = lf.group#
and lf.type = 'ONLINE'
/
MEMBER BYTES
-------- -------
/u02/oradata/convdata/redo03.log 10485760
/u02/oradata/convdata/redo02.log 10485760
/u02/oradata/convdata/redo01.log 10485760
SQL> alter database add logfile size 10485760;
Database altered.
SQL> alter database add logfile size 10485760;
Database altered.
SQL> alter database add logfile size 10485760;
Database altered.
SQL> alter system switch logfile;
System altered.
SQL> alter system switch logfile;
System altered.
SQL> alter system switch logfile;
System altered.
SQL> select lf.member, l.bytes
from v$log l, v$logfile lf
where l.group# = lf.group#
and lf.type = 'ONLINE'
/
MEMBER BYTES
-------- -------
/u02/oradata/convdata/redo03.log 10485760
/u02/oradata/convdata/redo02.log 10485760
/u02/oradata/convdata/redo01.log 10485760
+DATA1/convdata/onlinelog/group_4.323.1 10485760
+DATA1/convdata/onlinelog/group_5.324.1 10485760
+DATA1/convdata/onlinelog/group_6.325.1 10485760
6 rows selected.
旧redoロゴ削除を可能にするためデータベースのバックアップを実行
SQL> alter database drop logfile '/u02/oradata/convdata/redo01.log';
Database altered.
SQL> alter database drop logfile '/u02/oradata/convdata/redo02.log';
Database altered.
SQL> alter database drop logfile '/u02/oradata/convdata/redo03.log';
Database altered.
以上で終了
投稿者、Taran :11/08/2004 - 05:29. 記事| 開発者| システム管理
mysqlをバックアップするため私がコマンド行から走らせるPHPスクリプトを示す。これは基本的に、mysqlディレクトリ・データベース・フォルダをコピイするテーブルを基本的にロックするmysql_hotcopy を使用する。私のスクリプトが行うことのすべては、データベースをバックアップディレクトリまで通覧して、フォルダを tar.gz し、次いでコピイしたフォルダを削除することである。もちろん好みに応じて容易に変更することが出来る:
#!/usr/bin/php -q
<?php
$username = "dbusername";
$password = "password";
$backupdir= "/backup";
$link = mysql_connect($s, $username, $password);
$db_list = mysql_list_dbs($link);
while ($row = mysql_fetch_object($db_list))
{
$dbname = $row->Database;
# Hotcopy
$script = "mysqlhotcopy $dbname -u $username --password=\"$password\" --allowold $backupdir";
$results = system($script,$retval);
echo "RESULTS: $results\n RETURN VALUE: $retval\n";
# Tar files up
$script = "tar -czf $backupdir/$dbname.tgz $backupdir/$dbname";
$results = system($script,$retval);
echo "\n CREATE TAR FILE:\n $results\n RETURN VALUE: $retval\n";
# Delete dir
$script = "rm -Rf $backupdir/$dbname";
$results = system($script,$retval);
echo "\n DELETE DIR:\n $results\n RETURN VALUE: $retval\n";
}
?>
Burrokeet:データを示せ、フォーマットは重要でない
投稿者、
Taran :11/10/2004 - 03:34.
記事| 一般
Linux でのコンテンツ管理は今日の重要な政策である。正しい
コンテンツ管理システムとは、あらゆる種類の特性を満たすものである。これは、自分の仕事の解決策を求めるのが仕事でない普通の人には手に負えない(コンテンツ管理システムのリストは
ここ)。
Burrokeet もそれに相違ないが、主要な点一つだけが異なる。データフォーマットの融通性がこれの真の特徴だ。
面白いことにBurrokeetは
Trinidad and Tobago カーニバルのキャラクタだ。驢馬に乗った男、自由を見つけたキャラクタととして描かれている。フォーマット中のデータに与えられる自由を真似て降り、開発途上国にも関係している。Burrokeetは「学習コンテンツ管理システム」と説明されているが、何も学習しない。これは教育コンテンツ管理システムだが、それを遙かに超えている。
セントルチアの
Open For Business コンファレンスで、関心のある聴衆にAnil RamnananがBurrokeetを示した。彼の資料は、彼がHTMLで表示したのと同じデータからBurrokeetで作ってPDFによりプリントした。クリックするだけで別のフォーマットになる。また
OpenOffice.org と
Kupuforエディティングを用いて統合する。必要に応じ、一つのフォーマットで入力して、別のフォーマットで出力する。教育専門家用に便利だが、誰に取っ手もプレゼンテーション用に便利なことがすぐ分かる。詳細は
Burrokeet websiteへ。
プレゼンテーションの後、私はAnilと、Burrokeetが各種のフォーマットを扱うようになたときの威力と同時にコンテンツ管理システムとしての用途について話した。現在欠けていることの一つは、コメント又は文書を更新するときのWiki類似インターフェイスなど、フィードバックする手段である。しかしAnilは、これに関する特性要求は Burrokeetチームが既に受けており、それがBurrokeetの将来の改訂に見込まれていることを私に確言した。
デスクトップでの使用
Anilのプレゼンテーションで素晴らしかったことの一つは、プレゼンテーションに対しローカルに走らせているので何もかもローカルに扱うことが出来たことだ。人々がコンテンツ管理システムを考えるとき、一般的にはこの側面を考えないが、これは実際には喜ばれそうな何かなのだ。このアプリケーションではメタデータが時間を掛けて働き、自分のデータに多くの大衆がアクセスさせることに関心のある人々の暮らしに余裕を持たせる。
寄稿
プロジェクト参加に関心のある開発者は
Burrokeetサイトに向かうべきだ。ドキュメントを見て、メールリストに参加されたい。コードのダウンロードを忘れないこと。
投稿者、
Taran :11/12/2004 - 07:25. 記事| 一般
インストレーションは、フロッピイの作成など一寸入り組んでいて、「ISOのダウンロード、CDからブート」とは異なる。強力なシステム上では作業が厳しいものになるけれども、実はこのバージョンのLinuxはマシンの最低共通分母に仕立ててあるので「お下がり」マシンで働くことを意味する。Pentium II システムでは、指示通りに進めたら1時間以内で走った。「屑」として捨ててあるもののため結構使える、このシステムは
OpenOffice.org,を扱うことが出来ないが、ABIWordnihaには64メガのRAMで十分だった。
ハードウエアの最低要件は:
・インテル互換高級486プロセッサ最低 66mhz DX2。
注記:MOSIX エンハンスメントを使う積もりなら、プロセッサは最低Pentium 又は586でなければならない。
・最低16メガのram(大きければ大きい程良い)
・VESA互換ビデオカード、1メガ以上のメモリ付き
・マウス(シリアル又はPS2)
・ATAPI ide CDROMリーダ
・800メガ以上のハードディスク
実験として、Linuxのこのバージョンを私が持っている AMD 2600システムで検査した。メモリ1ギガで、期待通りの馬力を確かに発揮した。OpenOffice.orgでAMD 2600システムには何の問題もなかった。
これは、価値のあるディストリビューションだ。デスクトップに人が求める機能全部は持っていないけれども、古いシステムを走らせるには十分な機能を持っており、役立つ人がいる。古いシステムを取り出して、Linuxのこのバージョンを落とし、クリスマスのプレゼントにすると良い。自分で試しても良い。
OSPuebloにはまた、
その製品の一部として、サーバ・パケージがある。これはクライアント・パケージと連動してビジネスや学校の廉価なソリューションとして役立てることが出来る。古いハードウエアがあったら、ケースを綺麗にしてLinux をインストールすると良い。
Beepway製品:Linuxを用いるリアルタイム追跡
投稿者、
Taran :11/14/2004 - 05:04.
記事|一般|
時間によるBeepway 追跡の画面:画像を大きくするには画面をクリック
これを使う主なツールは
GSMネットワーク、
GPS,、IP ネットワーク、GNU ソフトウエア及び地図作製である。車両を追跡するには、各車両に組込Linuxsシステム(Beepway自体)があってGPSと結合しており、これが、GSMネットワークを通して緯度、経度、高度及び速度をサーバシステムに送信する。、このシステムは、車両200両を直接扱うことが出来て、拡張可能である。別のサーバを追加すると別の200両を追跡することが出来る。
組込Linux システム:Beepway:画像を大きくするには画面をクリック.
Beepway システムは、既に使用されている。2001年4月中旬に作られて、Martiniqueの郡議会で郡議会自動車の追跡に使用されている。Beepwayが有用なのは、車がそのとき何処にいるかを知るのが重要な非常用その他の用途である。これは、発明と開発技術に関する2001年の1等コンピュータPIL賞(Martiniqueで組織)を受けた。2002年には、エロールが、フランスのシラク大統領が主宰する「会社創立賞」を受賞した。
時間によるBeepway 追跡の画面:画像を大きくするには画面をクリック
過去にに同様のシステムHoneywellの内部連絡ナビゲーション装置のための書込と保守Test Softwareを用いた人のように、このようなシステムには沢山の可能性があることは明らかだ。これが、車両内ターミナルを用いるデータシステムとして花開き、車両内でもリアルタイムデータにアクセスすることが出来るようのなるくのに時間は掛からない。
Beepway プロジェクト自体は、コード全部をオープンソースにする積もりでいるが、必要上は−ソースコードを完全に利用することが出来るのを確認するまで、プロジェクトをサポートすることが出来るように企業の協賛を望んでいる。このコードを見たいので、そうなると良いと思う。・
rpmファイルの索引、検索、搭載用の簡単なスクリプト
投稿者、
arun_mah:11/14/2004 - 08:58. 記事| 興味
'rpmindex'は、rpmを索引し搭載刷るための簡単なシェルスクリプトである。
同様のツールは既に利用することが出来るが、これは小型で、コマンド行から走らせることが出来る。
#!/bin/bash
# rpmファイルをCDROMから索引、検索及び搭載する簡単なスクリプト
# @バージョン 0.1
# @理由 余り退屈なので作った
# @最終修正 第1 2004年11月14日 22:15:25
# ----------------------------------
# function to mount a CD-ROM
# ----------------------------------
function mount_cd
{
cd_title=$@
eject
echo "Insert the CD-ROM $cd_title and Press Enter.."
read
eject -t
mount /mnt/cdrom 1>&2 2>/dev/null
if [[ $? -ne 0 ]]; then
echo "Error mounting CD-ROM .. Please retry "
exit
fi
}
# ----------------------------------
# データベースを検索してパケージを搭載するファンクション
# ----------------------------------
function do_install()
{
if [[ ! -s .rpmindex.dat ]] ; then
echo "No rpm index database file found .."
echo "Run $0 to create an rpm index before proceeding .."
echo "Exiting .."
exit 1
fi
if [[ $# -ge 1 ]]; then
package=$@
else
echo -n "Enter the name of the package to install : "
read package
fi
echo "Searching for $package .. "
cur_title="null"
cur_rpm="null"
for line in `cat .rpmindex.dat 2>/dev/null`
do
if [[ -n $line && `echo $line | cut -c 1-8` == "%%TITLE:" ]] ; then
cur_title=`echo $line | cut -c 9-`
echo "$cur_title"
elif [[ -n `echo $line | grep -i $package` ]] ; then
cur_rpm=$line
echo -n "Found $cur_rpm .. Install (y/n): "
read response
response=`echo $response | tr '[:lower:]' '[:upper:]'`
if [[ -n $response && $response == "Y" ]] ; then
break
fi
fi
done
if [[ $cur_title == "null" || $cur_rpm == "null" ]] ; then
echo "$package Not found in the package index .. "
echo "Exiting .. "
exit 1
elif [[ $response != "Y" ]] ; then
exit 1
else # check if rpm is already installed
for installed_package in `rpm -qa 2>/dev/null | grep -i $package`
do
len=`echo ${#installed_package}`
if [[ $installed_package == `echo $cur_rpm | cut -c 1-$len` ]] ; then
echo "The package $installed_package is already installed"
echo "Exiting .."
exit 1
fi
done
fi
mount_new="true"
# cdrom が取付けられているときは、必要rpm があるか否かを見る
if [[ -n `mount | grep -i /dev/cdrom` ]] ; then
if [[ -n `find /mnt/cdrom/ -iname $cur_rpm` ]] ; then
mount_new="false"
fi
fi
if [[ $mount_new == "true" ]] ; then
mount_cd ""
fi
install_file=`find /mnt/cdrom/ -iname $cur_rpm`
install_dir=`dirname $install_file`
cd $install_dir
echo "Now installing $cur_rpm .."
rpm -ivh $cur_rpm
echo "Done .."
exit 1
}
# ----------------------------------
# rpmの索引を作るファンクション
# FORMAT:
# %% TITLE:
#
# ----------------------------------
function do_index()
{
if [[ -z `mount | grep -i /dev/cdrom` ]] ; then
mount_cd "to index"
fi
echo -n "Enter the CD-ROM title : "
read title
if [[ -z $title ]] ; then
title="CDROM_NONAME"
echo "No title name specified.."
echo "Using default title \"$title\" .."
fi
echo "Indexing the contents of $title .."
tmp_title=""
found_title="null"
for line in `cat .rpmindex.dat 2>/dev/null`
do
echo $line >> .rpmindex.dat.tmp
if [[ -n $line && `echo $line | cut -c 1-8` == "%%TITLE:" ]] ; then
tmp_title=`echo $line | cut -c 9-`
if [[ $tmp_title == $title ]] ; then
found_title=$tmp_title
echo "The database already contains a title named $title"
echo -n "Do you want to add this CD-ROM contents too (y/n) : "
read response
response=`echo $response | tr '[:lower:]' '[:upper:]'`
if [[ -n $response && $response == "Y" ]] ; then
for rpmfile in `find /mnt/cdrom/ -iname *.rpm`
do
basename $rpmfile >> .rpmindex.dat.tmp
done
fi
fi
fi
done
if [[ $found_title == "null" ]] ; then
echo "%%TITLE:$title" >> .rpmindex.dat.tmp
for rpmfile in `find /mnt/cdrom/ -iname *.rpm`
do
basename $rpmfile >> .rpmindex.dat.tmp
done
fi
rm -f .rpmindex.dat 1>&2 2>/dev/null
mv -f .rpmindex.dat.tmp .rpmindex.dat
echo "Done .."
echo "Ejecting the CD ROM .."
eject
}
# ----------------------------------
# オプションに基づく搭載/索引
# ----------------------------------
prog_name=`basename $0`
while getopts ":i:h" args
do
if [[ $args == "i" ]] ; then
do_install $OPTARG
exit
elif [[ $args == ":" && $OPTARG == "i" ]]; then
do_install
exit
elif [[ $args == "h" || $args == "?" ]]; then
echo "USAGE: "
echo -e "\nFor installing packages"
echo -e "\t$prog_name -i [optional package substring]"
echo -e "\nFor indexing Packages"
echo -e "\t$prog_name"
echo ""
exit
fi
done
do_index
#なすべきこと
# 1. rpm 従属生のチェック
# 2. もっと速い索引検索
# END OF FILE
投稿者、
mchirico :11/21/2004 - 21:30. 記事|開発者
Copyright (c) 2004 by Mike Chirico mchirico@users.sourceforge.net.
この素材は、1999年7月Open Publication
License v1.0, 8 以降に規定する条項にしたがってのみ配布することが出来る。
ダウンロード:
更新日:11月25日11:25:31 EST 2004
この記事は、sqlite3の能力と簡便さを検討するのに、先ず共通コマンドとトリッガから出発して、結合運用を用いる付着ステートメントを導入する。これにより、別個のデータベースにある多数のテーブルを、コピイしたり移動したりすることなく、一つに仮想テーブルに結合することが出来る。次に、簡単な署名ファンクションと、SQL選択ステートメントの中でこのファンクションを用いてデータを一度通過させるだけで複雑なクェリーを解決する強力な工夫を実証する。これは署名ファンクションが絶対値とIF条件を決定する方法に関する簡単な数学問題を作った後おこなう。
署名ファンクションは現在はsqlite3にないけれども、これを"/src/func.c"ファイルの中に作るのは極めて簡単なので、sqliteアプリケーション全部で恒久的に利用することが出来る。通常、ユーザファンクションは、C、Perl又は C++で作るが、それもまたこの記事で述べる。 sqlite3は、"blob"、バイナリデータを記憶する能力がある。ダウンロードのなかのサンプルプログラム"eatblob.c"は、任意のサイズのバイナリファイルをメモリに読み取って、そのデータをユーザ規定のフィールドに記憶する。
この指導書は、ソースバージョン3.08を用いておこなっている。
開始ー共通コマンド
データベースファイルを作るには、コマンド"sqlite3"に続けてデータベース名を入れて走らせる。データベース "test.db"を作るには次のようにsqlite3コマンドを走らせる。
$ sqlite3 test.db
SQLite version 3.0.8
Enter ".help" for instructions
sqlite> .quit
$
データベースファイル test.db が存在しなければ、作られる。このコマンドを走らせると、sqlite3 環境に止まる。この環境から安全に出るには三つの方法がある(.q, .quit又は.exit)。sqlite3会話環境に入る必要はない。代わりに、コマンド全部をシェルプロンプトで実行することが出来る。これは、bashスクリプトとコマンドをssh文字列で走らせるとき、理想的である。以下は、コマンドプロンプトから簡単なテーブルを作る方法の例である。
$ sqlite3 test.db "create table t1 (t1key INTEGER
PRIMARY KEY,data TEXT,num double,timeEnter DATE);"
テーブルt1を作った後、データを次のように挿入することが出来る:
$ sqlite3 test.db "insert into t1 (data,num) values ('This is sample data',3);"
$ sqlite3 test.db "insert into t1 (data,num) values ('More sample data',6);"
$ sqlite3 test.db "insert into t1 (data,num) values ('And a little more',9);"
予想通り、selectを行うとデータをテーブルに戻す。注意、プライマリキイ "t1key"は自動的にインクレメントするが、timeEnterに規定値はない。timeEnterフィールドに時間を入れるには、updateトリッガが必要である。プライマリキイに関する重要な注意:プライマリキイで作業をするとき略字 "INT" を用いないこと。更新するにはプライマリキイに"INTEGER"を使用しなければならない。
$ sqlite3 test.db "select * from t1 limit 2";
1|This is sample data|3|
2|More sample data|6|
上のステートメントでは、limitクローズを用いて2行だけ表示している。SQLite,で使用することの出来るSQLシンタクスステートメントの早見表はリンク
syntax.にある。
limitクローズに対してはoffsetオプションがある。例えば3行目は以下の "limit 1 offset 2"に相当する。
$ sqlite3 test.db "select * from t1 order by t1key limit 1 offset 2";
3|And a little more|9|
".table" コマンドは、テーブル名を示す。データベース内に作られるもっと包括的なテーブル、トリッガ、及びインデクスのリストでは、以下に示すようにデータベース内に作られるマスターテーブル "sqlite_master"に問い合わせる。
$ sqlite3 test.db ".table"
t1
$ sqlite3 test.db "select * from sqlite_master"
table|t1|t1|2|CREATE TABLE t1 (t1key INTEGER
PRIMARY KEY,data TEXT,num double,timeEnter DATE)
SQL情報全部とデータベースに挿入されるデータは、 ".dump" コマンドを用いて抽出することが出来る。
$ sqlite3 test.db ".dump"
BEGIN TRANSACTION;
CREATE TABLE t1 (t1key INTEGER
PRIMARY KEY,data TEXT,num double,timeEnter DATE);
INSERT INTO "t1" VALUES(1, 'This is sample data', 3, NULL);
INSERT INTO "t1" VALUES(2, 'More sample data', 6, NULL);
INSERT INTO "t1" VALUES(3, 'And a little more', 9, NULL);
COMMIT;
".dump"の内容は、filterして別のデータベースにpipeすることが出来る。下記のテーブルt1は、sedコマンドを用いてt2に変更され、test2.db データベースにpipeされる。
$ sqlite3 test.db ".dump"|sed -e s/t1/t2/|sqlite3 test2.db
トリッガ
下記でファイル"trigger1"の中に挿入トリッガが作られる。世界時(UTC)をフィールド "timeEnter"に入れると、このトリッガは、行がテーブルt1に挿入された後、発火する。もう一度、その行が挿入されると発火する。
-- ********************************************************************
-- timeEnterに関しトリッガを作る
-- 次のように走らせる
-- $ sqlite3 test.db < trigger1
-- ********************************************************************
CREATE TRIGGER insert_t1_timeEnter AFTER INSERT ON t1
BEGIN
UPDATE t1 SET timeEnter = DATETIME('NOW') WHERE rowid = new.rowid;
END;
-- ********************************************************************
"insert_t1_timeEnter AFTER..."の中にはAFTER仕様は必要である。AFTERがないと、 rowidが作られない。これはトリッガに関する普通のエラーになる。AFTERは規定値でないので、規定しなければならない。要約すると、トリッガが、作られる行からのフィールドのどれかの中で、新たに作られるデータに従属するときは、この例の場合にはrowidが必要なので、AFTER仕様が必要である。さもないと、トリッガはBEFOREとりっがとなり、フィールドにrowid又は別の関連項目が入力される前に発火する。
コメントの前には "--".を付ける。このスクリプトがファイル "trigger1"の中に作られているときは、このスクリプトを次のように容易に実行することが出来る。
$ sqlite3 test.db < trigger1
ここで、前のように新たなレコードを入力してみると、フィールドtimeEnterに時間があらわれる。
sqlite3 test.db "insert into t1 (data,num) values ('First entry with timeEnter',19);"
selectをおこなうと、以下のデータを明らかにする:
$ sqlite3 test.db "select * from t1";
1|This is sample data|3|
2|More sample data|6|
3|And a little more|9|
4|First entry with timeEnter|19|2004-10-02 15:12:19
上のステートメントを見ると、最後の値が自動的に世界時(UTC)で表されるtimeEnterフィールドを有する。地方時が入用のときは、select datetime('now','localtime')を用いる。UTCと地方時に関する章の終わりを参照されたい。
その例として、テーブル"exam" とデータベース"examScript"を用いる。テーブルとトリッガは以下に定義する。上のトリッガと同様に、UTC時を用いる。
-- *******************************************************************
-- examScript: exam テーブル作成用スクリプト
-- Usage:
-- $ sqlite3 examdatabase < examScript
--
-- Note: The trigger insert_exam_timeEnter
-- updates timeEnter in exam
-- *******************************************************************
-- *******************************************************************
CREATE TABLE exam (ekey INTEGER PRIMARY KEY,
fn VARCHAR(15),
ln VARCHAR(30),
exam INTEGER,
score DOUBLE,
timeEnter DATE);
CREATE TRIGGER insert_exam_timeEnter AFTER INSERT ON exam
BEGIN
UPDATE exam SET timeEnter = DATETIME('NOW')
WHERE rowid = new.rowid;
END;
-- *******************************************************************
-- *******************************************************************
スクリプトファイルの後で、スクリプトファイルの内容をデータベース名の続sqlite3コマンドの中にリダイレクトすることにより実行することが出来る。下記の例を参照。
$ sqlite3 examdatabase < examScript
$ sqlite3 examdatabase "insert into exam (ln,fn,exam,score)
values ('Anderson','Bob',1,75)"
$ sqlite3 examdatabase "select * from exam"
1|Bob|Anderson|1|75|2004-10-02 15:25:00
また、チェックとして、PRIMARY KEYと現在UTC時が、上の例で見るように、正しく修正されている。
挿入、更新及び削除全部の記録
下記のスクリプトは、テーブルexamlogとpdate_examlog, insert_examlog, 及びdelete_examlogの三つを作って、examテーブルに対する更新、挿入、削除モードを記録する。言い換えると、examテーブルに対する変更があるときはいつでも、変更が、新しい値と古い値を含めてexamlog テーブルに記録される。 MySQLに慣れてい人のため言えば、このログテーブルの機能は MySQLのbinlogに似ている。MySQLのbinlogについてもっと知りたい人は
( TIP 2, TIP 24 and TIP 25) を参照。
-- *******************************************************************
-- examLog: ログテーブルと関連トリッガ作成用のスクリプト
-- Usage:
-- $ sqlite3 examdatabase < examLOG
--
--
-- *******************************************************************
-- *******************************************************************
CREATE TABLE examlog (lkey INTEGER PRIMARY KEY,
ekey INTEGER,
ekeyOLD INTEGER,
fnNEW VARCHAR(15),
fnOLD VARCHAR(15),
lnNEW VARCHAR(30),
lnOLD VARCHAR(30),
examNEW INTEGER,
examOLD INTEGER,
scoreNEW DOUBLE,
scoreOLD DOUBLE,
sqlAction VARCHAR(15),
examtimeEnter DATE,
examtimeUpdate DATE,
timeEnter DATE);
-- Create an update trigger
CREATE TRIGGER update_examlog AFTER UPDATE ON exam
BEGIN
INSERT INTO examlog (ekey,ekeyOLD,fnOLD,fnNEW,lnOLD,
lnNEW,examOLD,examNEW,scoreOLD,
scoreNEW,sqlAction,examtimeEnter,
examtimeUpdate,timeEnter)
values (new.ekey,old.ekey,old.fn,new.fn,old.ln,
new.ln,old.exam, new.exam,old.score,
new.score, 'UPDATE',old.timeEnter,
DATETIME('NOW'),DATETIME('NOW') );
END;
--
-- Also create an insert trigger
-- NOTE AFTER keyword ------v
CREATE TRIGGER insert_examlog AFTER INSERT ON exam
BEGIN
INSERT INTO examlog (ekey,fnNEW,lnNEW,examNEW,scoreNEW,
sqlAction,examtimeEnter,timeEnter)
values (new.ekey,new.fn,new.ln,new.exam,new.score,
'INSERT',new.timeEnter,DATETIME('NOW') );
END;
-- Also create a DELETE trigger
CREATE TRIGGER delete_examlog DELETE ON exam
BEGIN
INSERT INTO examlog (ekey,fnOLD,lnNEW,examOLD,scoreOLD,
sqlAction,timeEnter)
values (old.ekey,old.fn,old.ln,old.exam,old.score,
'DELETE',DATETIME('NOW') );
END;
-- *******************************************************************
-- *******************************************************************
上のスクリプトは、ファイルexamLOGの中に作ったので、下に示すようにsqlite3の中のコマンドで実行することが出来る。下記にはまた、これら新たに作ったトリッガのテストのためのレコード挿入と更新をも示す。
$ sqlite3 examdatabase < examLOG
$ sqlite3 examdatabase "insert into exam
(ln,fn,exam,score)
values
('Anderson','Bob',2,80)"
$ sqlite3 examdatabase "update exam set score=82
where
ln='Anderson' and fn='Bob' and exam=2"
ここで、下記のselectステートメントを行うことにより、examlog が insert ステートメントに関する入力に加えて、更新を含む。コマンド行では一つしか更新をしていないが、トリッガ"insert_exam_timeEnter"がフィールドtimeEnterに関する更新を実行する。これは"examScript"に定義される更新である。2番目の更新で、スコアが変わっているのが判る。トリッガは働いている。テーブルに対して行われる変更はすべて、ユーザに依るものでも別のトリッガに依るものでも、examlogに記録される。
$ sqlite3 examdatabase "select * from examlog"
1|2||Bob||Anderson||2||80||INSERT|||2004-10-02 15:33:16
2|2|2|Bob|Bob|Anderson|Anderson|2|2|80|80|UPDATE||2004-10-02 15:33:16|2004-10-02 15:33:16
3|2|2|Bob|Bob|Anderson|Anderson|2|2|82|80|UPDATE|2004-10-02 15:33:16|2004-10-02 15:33:26|2004-10-02 15:33:26
ここでも、AFTERキイワードに格別の注意を払う。規定値トリッガがBEFOREであることを思い出すと、新しいあらゆる値にトリッガが働く必要があるとき、新しい値を利用することが出来るのを確実にするにはAFTERを規定しなければならない。
UTC と地方時
select DATETIME('NOW')がUTCつまり世界時を返すのに注意。だがselect datetime('now','localtime') は地方時を返す。
sqlite> select datetime('now');
2004-10-18 23:32:34
sqlite> select datetime('now','localtime');
2004-10-18 19:32:46
UTCは、テーブルに入力した後、容易に地方時に転換することが出来るので、我々が上のトリッガにしたようにUTCを挿入する方に利点がある。下のコマンドを見られたい。UTCの挿入により、地方時が同一でない多数のデータベースで作業をするときの面倒を避けることが出来る。UTCから出発すれば、常に地方時を利用することが出来る。
CONVERTING TO LOCALTIME:
sqlite> select datetime(timeEnter,'localtime') from exam;
その他の日時コマンド
sqlite3 ソースファイル "./src/date.c" を見ると、日時問題に別のオプションがあるのが判る。地方時を得るには、3.5秒足す、10分足すなどである。次のコマンドを実行する。
sqlite> select datetime('now','localtime','+3.5 seconds','+10 minutes');
2004-11-07 15:42:26
日曜=0、月曜=1、火曜=2・・・土曜=6などどして、曜日を得ることも出来る。
sqlite> select datetime('now','localtime','+3.5 seconds','weekday 2');
2004-11-09 15:36:51
オプションのリストを完成するか又はこのファイルで呼ばれるように修正するには、次のようにする:
NNN days
NNN hours
NNN minutes
NNN.NNNN seconds
NNN months
NNN years
start of month
start of year
start of week
start of day
weekday N
unixepoch
localtime
utc
加えて、"strftime" ファンクションがある。これは時間文字列を取り、それに修正を加えて規定のフォーマットに転換する。このファンクションのためのファーマットを示す。
** strftime( FORMAT, TIMESTRING, MOD, MOD, ...)
**
** Return a string described by FORMAT. Conversions as follows:
**
** %d day of month
** %f ** fractional seconds SS.SSS
** %H hour 00-24
** %j day of year 000-366
** %J ** Julian day number
** %m month 01-12
** %M minute 00-59
** %s seconds since 1970-01-01
** %S seconds 00-59
** %w day of week 0-6 sunday==0
** %W week of year 00-53
** %Y year 0000-9999
下に例を示す:
sqlite> select strftime("%m-%d-%Y %H:%M:%S %s %w %W",'now','localtime');
11-07-2004 16:23:15 1099844595 0 44
ATTACH コマンド:別個のデータベース上の多数のテーブルをまたぐ
仮想テーブル
これは極めて強力な概念である。見てきたように、sqlite3 はローカル・データベース・ファイルで働く。このローカル・データベース内で多数のテーブルを作ることが出来る。この章では、別々のデータベースファイル内に存在するのと同一のフィールドレイアウトを用いて、多数のテーブルを、単一の仮想テーブルに結合する方法を検討する。この単一仮想テーブル上で、selectを実行する方法を見る。"virtual table"の名を用いる。データのコピイや移動の手間はない。どのデータにもコピイや移動の時間はない。これは極めて大きいテーブルで作業をするとき理想的な状態である。自分のネットワーク記録ポートにあるコンピュータがローカルsqlite3をスキャンするとしよう。個別データベースファイルへのアクセスをHFSマウント又はsambaマウントを通じて持っている限り、自分のコンピュータ全部からのテーブルを仮想テーブルに仮想的に結合して、データベース・クェリーを、自分のネットワークに対するアタックの全体的パターンを認識する努力で実行する。この例は、 exam(試験)テーブルに関して用いたスクリプトがあるので、examdatabase(試験データベース)を用いておこなう。bashセルから以下のスクリプトを実行することにより、新しいデータベース "examdatabase2"を、新しいexamテーブルと同様に、容易に作ることが出来る。
$ sqlite3 examdatabase2 < examScript
$ sqlite3 examdatabase2 < examLOG
$ sqlite3 examdatabase2 "insert into exam (ln,fn,exam,score) values ('Carter','Sue',1,89);
insert into exam (ln,fn,exam,score) values ('Carter','Sue',2,100);"
$ sqlite3 examdatabase2 "select * from exam"
1|Sue|Carter|1|89|2004-10-02 16:04:12
2|Sue|Carter|2|100|2004-10-02 16:04:12
二つのデータベースファイルを結合するには、attachコマンドを用いる。examdatabaseの別名をe1とし、examdatabase2の別名をe2とする。名前が短いと、unionクローズを用いてテーブルを連結するとき手軽になる。テーブルを連結するのは "union"で、これは標準SQLコマンドである。
"attach" データベース・コマンド実行の後、個々のデータベースファイルの位置を示すのに、".database" を使うことが出来る。位置は別名にしたがう。下記の例を参照。
$ sqlite3
SQLite version 3.0.8
Enter ".help" for instructions
sqlite> attach database 'examdatabase' as e1;
sqlite> attach database 'examdatabase2' as e2;
sqlite> .database
seq name file
--- --------------- ----------------------------------------------------------
0 main
2 e1 /work/cpearls/src/posted_on_sf/sqlite_examples/sqlite_exam
3 e2 /work/cpearls/src/posted_on_sf/sqlite_examples/sqlite_exam
sqlite>
両テーブルからデータを選ぶには、下に示すように二つのselectステートメントの結合を用いる。'e1' と'e2' を夫々のselectに付加することにより、戻されるレコードがどちらのデータベースからのものか識別することが出来る。
sqlite> select 'e1',* from e1.exam union select 'e2',* from e2.exam;
e1|1|Bob|Anderson|1|75|2004-10-02 15:25:00
e1|2|Bob|Anderson|2|82|2004-10-02 15:33:16
e2|1|Sue|Carter|1|89|2004-10-02 16:04:12
e2|2|Sue|Carter|2|100|2004-10-02 16:04:12
まとめると、別個のデータベースにある二つのテーブルに関してクェリーが実行された。unionが仮想テーブルを作った。selectの構文は次の通りである:
SELECT <expression> FROM <TABLE>。
テーブル・オプションに関しては、完全文字列"(select 'e1' as db,* from e1.exam union select 'e2'as db,* from e2.exam)"を使用した。これが我々の仮想テーブルである。この仮想テーブルに対して実行するクェリーの例を示す。データベース全体をexamして最大 scoreを知りたいとする。
sqlite> select exam,max(score) from
(select 'e1' as db,* from e1.exam union select 'e2' as db,* from e2.exam)
group by exam;
1|89
2|100
簡単だ。上で各examについて最大scoreが得られたが、最大得点を取ったのは誰か?1n(名)と2n(姓)を見つけてみる。だが注意が必要だ、selectの最初の部分に "ln"と"fn" を追加すると、間違った答が出る。
sqlite> select exam,max(score),ln,fn from
(select 'e1' as db,* from e1.exam union select 'e2' as db,* from e2.exam)
group by exam;
** これは間違い Carter|Sue でなければならない**
1|89|Anderson|Bob
2|100|Anderson|Bob
上の答は正しくない。 "Anderson"、"Bob" は偶々このselectに紛れ込んだ名前だ。正しくない。このクェリーで正しい答が出るとしたら、名前を別の順序で入れたからだ。その場合は最低(得点)を取り上げる下のクェリーを実行してみる。これらの例の内一つでエラーを得る。
最低(得点)を聞いてみる。偶々、序列データがこのテーブルに入っているので、正しい答が表示される。
sqlite> select exam,min(score),ln,fn from
(select 'e1' as db,* from e1.exam union select 'e2' as db,* from e2.exam)
group by exam;
** 正答 -- 偶然 **
1|75|Anderson|Bob
2|82|Anderson|Bob
各試験について最大得点と最小得点を得た者を見出すにはもっと良い方法がある筈だ。ともかく、常に正しい答が得られる「正しい」sqlステートメントを示す。
sqlite> select db,ln,fn,exam,score from
(select 'e1' as db,* from e1.exam union select 'e2' as db,* from e2.exam)
where
(
score=(
select max(score) from
(select 'e1' as db,* from e1.exam union select 'e2' as db,* from e2.exam)
where exam=1
)
and exam = 1
)
OR
(
score=(
select max(score) from
(select 'e1' as db,* from e1.exam union select 'e2' as db,* from e2.exam)
where exam=2
)
and exam = 2
) ;
e2|Carter|Sue|1|89
e2|Carter|Sue|2|100
又は、次のように独立の二つのステートメントとしておこなうことが出来る:
sqlite> select db,ln,fn,exam,score from
(select 'e1' as db,* from e1.exam union select 'e2' as db,* from e2.exam)
where exam=1 order by score desc limit 1;
e2|Carter|Sue|1|89
二つ目のselectを示す
sqlite> select db,ln,fn,exam,score from
(select 'e1' as db,* from e1.exam union select 'e2' as db,* from e2.exam)
where exam=2 order by score desc limit 1;
e2|Carter|Sue|2|100
ピボット・テーブル
各人の得点についてトップから試験1、試験2,..試験Nをまたぐピボット・テーブルが欲しい場合はどうだろうか。例えば、得点が次のようになっているとする:
名 姓 exam1 exam2
--- -------- ----- -----
Bob Anderson 75 82
Sue Carter 89 100
また、5列目を作って、試験1と試験2間の変化7点(82-75)を表示する方法はないだろうか?これらはsignファンクションを用いておこなうことが出来る。caseステートメント異なり、signファンクションは、clauseによりグループに置くことが出来る。signファンクションは、sqliteには存在しないが、容易に作ることが出来るので、問題ではない。signファンクションを作る代わりにIF又はIIFファンクションではいけないかとの疑問があるだろう。主な理由は、IFステートメントがデータベース全部に標準ではないことだ。また標準であるデータベースでも MySQLなど、sum()ファンクションで包む必要がある。MySQLのIFファンクションの例については、。
LONGWINDED TIP 1を参照されたい。(これはPaul DuBois の指摘)
Signファンクションの力ー数学的説明
ショックかも知れないが、前節の問題はsignファンクションで解くことが出来る。これは、sign(-200)=-1,.. sign(-1)=-1, sign(0)=0, sign(1)=1,.. sign(300)=1である簡単なsignファンクションに過ぎない。だから、数が >0のとき1を返すと、0が返されるのは0のときだけになり、負の数は全部-1を返す。繰り返すが、この簡単なファンクションはsqliteに無いが、恒久的に作るのは容易だ。次章でこの作り方を述べるが、ここでは数学的特性を説明する。
signファンクションは、絶対値ファンクションabs()を数値にそのsignを掛けたもの、又はsign(x)*(x)、略してsign(x)(x)、として定義することが出来る。詳細を見よう。
sign(x)(x) = abs(x)
例、x=3とすると
sign(3)(3) = abs(3)
1*3 = 3
例、x=-3とすると
sign(-3)(-3) = abs(-3)
-1*-3 = 3
例、x=0とすると
sign(0)(0) = abs(0)
0*0 = 0
signファンクションを二つの変数xとyの間で比較することが出来る。例えば、sign(x-y)が1であるときは、xがyより大きい。
x > yであるときは、sign(x-y) = 1
x = yであるときは、sign(x-y) = 0
x < yであるときは、sign(x-y) = -1
ここで下記のステートメント三つを詳しく検討する。signファンクションは、x=yであるときのみ1が返されるIFステートメントに共通点があることから出発する。続く論議は以下のステートメントを完全に理解することが重要だ。残りの議論は、これらの例から、直ちに構築されるからだ。
IF ( X==Y ) return 1; ELSE return 0;
は次のように表現することが出来る:
1 - abs(sign(x-y)) is equal to 0 if x > y [x > yならabs(sign(x-y))=0]
1 - abs(sign(x-y)) is equal to 1 if x = y [x=yならabs(sign(x-y))=1]
1 - abs(sign(x-y)) is equl to 0 if x < y [x < yならabs(sign(x-y))=0]
x < yのときのみ1を返し、相でないときはゼロを返すことも出来る。
IF ( X < Y ) return 1; ELSE return 0;
は次のように表現することが出来る:
1-sign(1+sign(x-y)) is equal to 0 if x > y [x > yならabs(1+sign(x-y))=0]
1-sign(1+sign(x-y)) is equal to 0 if x = y [x=yならabs(1+sign(x-y))=0]
1-sign(1+sign(x-y)) is equal to 1 if x < y [x < yならabs(1+sign(x-y))=1]
最後の例は、 x<yに関する変化、即ちDelta[x<y]として知られる。この Delta表記は、長々しい形で書いたりIFステートメントを用いる代わりに使用される。したがって、以下は、Deltaファンクションつまり比較演算子全部の要約表である。
Delta[x=y] = 1 - abs(sign(x-y))
Delta[x!=y] = abs(sign(x-y))
Delta[x<y] = 1-sign(1+sign(x-y))
Delta[x<=y] = sign(1-sign(x-y))
Delta[x>y] = 1-sign(1-sign(x-y))
Delta[x>=y] = sign(1+sign(x-y))
Delta[z=x AND z=y] = sign( Delta[z=x]*Delta[z=y] )
Delta[z=x OR z=y] = sign( Delta[z=x]+Delta[z=y] )
Delta[z>x AND z<y] = sign( Delta[z>x]*Delta[z<y] )
... もっと定義することが出来るが ... 分かる筈
以下のifステートメントをまとめるには、以下の第三変数zの導入に注意。
if( x==y )
return z;
else
return 0;
上を Delta記号で表現すると次のようになる:
z*Delta[x=y]
面白い例を示す。
create table logic (value int);
insert into logic (value) values (1);
insert into logic (value) values (0);
insert into logic (value) values (-1);
xとyの可能な組合せ全部を示すため、先ずデカルト積を採用する。
sqlite> .header on
sqlite> .mode column
sqlite> select x.value,y.value from logic x, logic y;
x.value y.value
---------- ----------
1 1
1 0
1 -1
0 1
0 0
0 -1
-1 1
-1 0
-1 -1
次いで、signファンクションが作られた後、これは次節でおこなう、上のテーブルを用いて Delta[x!=y]を次のように調査することが出来る。
sqlite> .header on
sqlite> .mode column
sqlite> select x.value,y.value,abs(sign(x.value-y.value)) from logic x, logic y;
x.value y.value abs(sign(x.value-y.value))
---------- ---------- --------------------------
1 1 0
1 0 1
1 -1 1
0 1 1
0 0 0
0 -1 1
-1 1 1
-1 0 1
-1 -1 0
上で、xとyの値が異なる度にabs(sign(x.value-y.value))が1を返すことに注意。これらの例が走るのは、signファンクションを作った後である。これは極めて強力だ。where又はgroupを用いないでステートメントにより作ったことを示すため、以下の例を検討する。z.valueは、x.value != y.valueであるときのみ、右側列に表示される。
sqlite> select x.value,y.value,z.value,
z.value*abs(sign(x.value-y.value))
from logic x, logic y, logic z;
x.value y.value z.value z.value*abs(sign(x.value-y.value))
---------- ---------- ---------- ----------------------------------
1 1 1 0
1 1 0 0
1 1 -1 0
1 0 1 1
1 0 0 0
1 0 -1 -1
1 -1 1 1
1 -1 0 0
1 -1 -1 -1
0 1 1 1
0 1 0 0
0 1 -1 -1
0 0 1 0
0 0 0 0
0 0 -1 0
0 -1 1 1
0 -1 0 0
0 -1 -1 -1
-1 1 1 1
-1 1 0 0
-1 1 -1 -1
-1 0 1 1
-1 0 0 0
-1 0 -1 -1
-1 -1 1 0
-1 -1 0 0
-1 -1 -1 0
ソースの変更:恒久的Signファンクションの作成
Sqliteファンクションは、"./src/func.c" で定義される。このファイル内で、このファンクションの名称は "signFunc" になる。ユーザはこのファンクションをspliteの中で sign(n)として呼出す。これは単一変数だけを保持する。
signファンクションをabs ファンクション "absFunc" の後に作るのが役立つ。大変似ているからだ。事実、sqliteの新バージョンがリリースされる毎に abs ファンクションを調べることを強く推奨する。
三つのステップを踏むのが良い。先ずabs ファンクション "absFunc" をコピイして、次の変更をおこなう:
1.ファンクション名をabsFuncからsignFunc に変更する。
2.変数 iVal を変更する必要がある。iValは、sqlite3_value_type(argv[0]) が0よいり少ないとき1に等しくなければならない。ここでのこの値は、整数であることに注意。さもないときは、この整数がゼロであればゼロを返し、この整数が0より大きいるときは1を返す。これら全部をまとめると:
iVal = ( iVal > 0) ? 1 : ( iVal < 0 ) ? -1 : 0;
3.同じステップを、上の整数値と反対に実数値であるrVal について踏む。
rVal = ( rVal > 0) ? 1 : ( rVal < 0 ) ? -1 : 0;
4.以下の入力をaFuncs[] に追加する
{ "sign", 1, 0, SQLITE_UTF8, 0, signFunc },
5.sqlite をメインディレクトリからコンパイルし直してインストールする
$ ./configure
$ make && make install
詳しく調べると、変更された部分は下記である。
.
... cut ...
/*
** sign()ファンクションの実行
*/
static void signFunc(sqlite3_context *context, int argc, sqlite3_value **argv){
assert( argc==1 );
switch( sqlite3_value_type(argv[0]) ){
case SQLITE_INTEGER: {
i64 iVal = sqlite3_value_int64(argv[0]);
/* 第一変更は下記:下の行は if( iVal<0 ) iVal = iVal * -1; であった; */
iVal = ( iVal > 0) ? 1 : ( iVal < 0 ) ? -1 : 0;
sqlite3_result_int64(context, iVal);
break;
}
case SQLITE_NULL: {
sqlite3_result_null(context);
break;
}
default: {
/* 第二変更は下記:abs に関する行は if( rVal<0 ) rVal = rVal * -1.0; であった */
double rVal = sqlite3_value_double(argv[0]);
rVal = ( rVal > 0) ? 1 : ( rVal < 0 ) ? -1 : 0;
sqlite3_result_double(context, rVal);
break;
}
}
}
... cut ...
} aFuncs[] = {
{ "min", -1, 0, SQLITE_UTF8, 1, minmaxFunc },
{ "min", 0, 0, SQLITE_UTF8, 1, 0 },
{ "max", -1, 2, SQLITE_UTF8, 1, minmaxFunc },
{ "max", 0, 2, SQLITE_UTF8, 1, 0 },
{ "typeof", 1, 0, SQLITE_UTF8, 0, typeofFunc },
{ "length", 1, 0, SQLITE_UTF8, 0, lengthFunc },
{ "substr", 3, 0, SQLITE_UTF8, 0, substrFunc },
{ "substr", 3, 0, SQLITE_UTF16LE, 0, sqlite3utf16Substr },
{ "abs", 1, 0, SQLITE_UTF8, 0, absFunc },
/* ここで追加 */
{ "sign", 1, 0, SQLITE_UTF8, 0, signFunc },
{ "round", 1, 0, SQLITE_UTF8, 0, roundFunc },
{ "round", 2, 0, SQLITE_UTF8, 0, roundFunc },
... cut ...
新たなSign ファンクションの利用
ここで、試験得点を表計算ソフト状フォーマットで表示するピボット・テーブル作成の問題に戻る。先ず沢山のデータが必要だ。未だデータを追加していないときは、以下のスクリプト
enterExamdata が必要なテーブルを作ってデータを挿入する。
$ sqlite3 examdatabase "insert into exam (ln,fn,exam,score) values ('Anderson','Bob',3,92)"
$ sqlite3 examdatabase "insert into exam (ln,fn,exam,score) values ('Anderson','Bob',4,95)"
$ sqlite3 examdatabase "insert into exam (ln,fn,exam,score) values ('Stoppard','Tom',1,88)"
$ sqlite3 examdatabase "insert into exam (ln,fn,exam,score) values ('Stoppard','Tom',2,90)"
$ sqlite3 examdatabase "insert into exam (ln,fn,exam,score) values ('Stoppard','Tom',3,92)"
$ sqlite3 examdatabase "insert into exam (ln,fn,exam,score) values ('Stoppard','Tom',4,95)"
$ sqlite3 examdatabase2 "insert into exam (ln,fn,exam,score) values ('Carter','Sue',3,99)"
$ sqlite3 examdatabase2 "insert into exam (ln,fn,exam,score) values ('Carter','Sue',4,95)"
テーブル exams上の4回の試験に関するピボット・テーブル作成用 select ステートメントである。
select ln,fn,
sum(score*(1-abs(sign(exam-1)))) as exam1,
sum(score*(1-abs(sign(exam-2)))) as exam2,
sum(score*(1-abs(sign(exam-3)))) as exam3,
sum(score*(1-abs(sign(exam-4)))) as exam4
from exam group by ln,fn;
下に示すのは上に類似のselect ステートメントであるが、これは仮想テーブル、即ちデータベース examdatabase とexamdatabase2から合成したexam テーブルに対して働く。
$ sqlite3
SQLite version 3.0.8
Enter ".help" for instructions
sqlite> attach database examdatabase as e1;
sqlite> attach database examdatabase2 as e2;
sqlite> .database
seq name file
--- --------------- ----------------------------------------------------------
0 main
2 e1 /work/cpearls/src/posted_on_sf/sqlite_examples/sqlite_exam
3 e2 /work/cpearls/src/posted_on_sf/sqlite_examples/sqlite_exam
sqlite> .header on
sqlite> .mode column
sqlite> select ln,fn,sum(score*(1-abs(sign(exam-1)))) as exam1,
sum(score*(1-abs(sign(exam-2)))) as exam2,
sum(score*(1-abs(sign(exam-3)))) as exam3,
sum(score*(1-abs(sign(exam-4)))) as exam4
from (select 'e1' as db,* from e1.exam union select 'e2' as db,* from e2.exam)
group by ln,fn;
ln fn 試験1 試験2 試験3 試験4
---------- ---------- ---------- ---------- ---------- ----------
Anderson Bob 75 82 92 95
Carter Sue 89 100 99 95
Stoppard Tom 88 90 92 95
sqlite>
結果を詳しく見ると、Anderson, Bob が最初の試験で
75 点を取り、2回目で 82点、3回目で 92 点、
4回目で 95 点を取ったことが、簡単に分かる。同様に
Carer はそれぞれ、 88,90,92 及び 95 点を取った。
ln fn 試験1 試験2 試験3 試験4
---------- ---------- ---------- ---------- ---------- ----------
Anderson Bob 75 82 92 95
Carter Sue 89 100 99 95
Stoppard Tom 88 90 92 95
ここで、各試験に関する最高得点を一つの select ステートメントで見出す問題に戻る。つまり結合デーブルについて最高得点を見出すことだ。先ずデータ全部を調べる
$ sqlite3
SQLite version 3.0.8
Enter ".help" for instructions
sqlite> attach database examdatabase as e1;
sqlite> attach database examdatabase2 as e2;
sqlite> .header on
sqlite> .mode column
sqlite> select 'e1' as db,* from e1.exam union select 'e2' as db,* from e2.exam;
db ekey fn ln 試験 点数 入力日時
---------- ---------- ---------- ---------- ---------- ---------- -------------------
e1 1 Bob Anderson 1 75 2004-10-17 22:01:42
e1 2 Bob Anderson 2 82 2004-10-17 22:02:19
e1 3 Bob Anderson 3 92 2004-10-17 22:05:04
e1 4 Bob Anderson 4 95 2004-10-17 22:05:16
e1 5 Tom Stoppard 1 88 2004-10-17 22:05:24
e1 6 Tom Stoppard 2 90 2004-10-17 22:05:31
e1 7 Tom Stoppard 3 92 2004-10-17 22:05:40
e1 8 Tom Stoppard 4 95 2004-10-17 22:05:50
e2 1 Sue Carter 1 89 2004-10-17 22:03:10
e2 2 Sue Carter 2 100 2004-10-17 22:03:10
e2 3 Sue Carter 3 99 2004-10-17 22:05:57
e2 4 Sue Carter 4 95 2004-10-17 22:06:05
sqlite>
同一付着設定を続ける下記は、横の平均値と横の最大値の例である。
sqlite> .headers on
sqlite> .mode column
sqlite> select db,ln as lastname,fn as first,
sum(score*(1-abs(sign(exam-1)))) as exam1,
sum(score*(1-abs(sign(exam-2)))) as exam2,
sum(score*(1-abs(sign(exam-3)))) as exam3,
sum(score*(1-abs(sign(exam-4)))) as exam4,
avg(score) as avg, max(score) as max
from (select 'e1' as db,* from e1.exam union select 'e2' as db,* from e2.exam)
group by ln,fn,db ;
db lastname first 試験1 試験2 試験3 試験4 平均 最高
---------- ---------- ---------- ---------- ---------- ---------- ---------- ---- ----
e1 Anderson Bob 75 82 92 95 86 95
e2 Carter Sue 89 100 99 95 95.75 100
e1 Stoppard Tom 88 90 92 95 91.25 95
デルタ、即ち各試験得点の差を見出して見る。これに関するヒントはLONGWINDED TIPSセクションにある(
article )に続く最後をみられたい。
規格化データに対するピボット・テーブル「表計算ソフト・フォーマット」
反対を考える:ピボットテーブル、又は表計算ソフト・フォーマット状のデータがあり、試験のテーブルを規格化したいとする。この例には非規格テーブルが必要である。そのテーブルは次のようにして作る。
SQLite version 3.0.8
Enter ".help" for instructions
sqlite> attach database 'examdatabase' as e1;
sqlite> attach database 'examdatabase2' as e2;
sqlite> create table e1.nonormal as
select ln,fn,
sum(score*(1-abs(sign(exam-1)))) as exam1,
sum(score*(1-abs(sign(exam-2)))) as exam2,
sum(score*(1-abs(sign(exam-3)))) as exam3,
sum(score*(1-abs(sign(exam-4)))) as exam4
from (select 'e1' as db,* from e1.exam union select 'e2' as db,* from e2.exam)
group by ln,fn;
sqlite> .header on
sqlite> .mode column
sqlite> select * from e1.nonormal;
ln fn exam1 exam2 exam3 exam4
---------- ---------- ---------- ---------- ---------- ----------
Anderson Bob 75 82 92 95
Carter Sue 89 100 99 95
Stoppard Tom 88 90 92 95
上の非規格テーブルは、前に "e1." が名称与えられているので、examdatabase,の中に作られた。繰り返すが、ここでの目的は後向きに進んでピボットテーブルから規格化テーブルを作ることである。それは、一つのフィールドに試験得点全部を、別のフィールドに試験数全部を、試験毎に別個のフィールドを持たせることなく記載するテーブルである。加えて、目標はこれら全部を一つの select ステートメントでデータをループすることなくおこなうことである。先ず number テーブル"enum"を作る必要があり、これにはNを試験の数とするとき1からNのフィールド "e" がなければならない。今の場合Nは4とする。
sqlite> CREATE TABLE enum (e int);
sqlite> INSERT INTO "enum" VALUES(1);
sqlite> INSERT INTO "enum" VALUES(2);
sqlite> INSERT INTO "enum" VALUES(3);
sqlite> INSERT INTO "enum" VALUES(4);
この例で挿入方法としてcoalesceファンクションを用いる。
sqlite> .mode list
sqlite> select n.ln,n.fn,
1*(1-abs(sign(e.e-1)))+
2*(1-abs(sign(e.e-2)))+
3*(1-abs(sign(e.e-3)))+
4*(1-abs(sign(e.e-4))),
coalesce(0/(e.e-1),n.exam1)+
coalesce(0/(e.e-2),n.exam2)+
coalesce(0/(e.e-3),n.exam3)+
coalesce(0/(e.e-4),n.exam4)
from enum as e,e1.nonormal as n;
Anderson|Bob|1|75
Carter|Sue|1|89
Stoppard|Tom|1|88
Anderson|Bob|2|82
Carter|Sue|2|100
Stoppard|Tom|2|90
Anderson|Bob|3|92
Carter|Sue|3|99
Stoppard|Tom|3|92
Anderson|Bob|4|95
Carter|Sue|4|95
最大最小問題
姓名、年齢及び俸給に関する次のテーブルがあるとして、全体で最高給を貰う最若年者の年齢、姓名及び俸給を見出せ。又は、先ず最高給を見出して、次にこのグループから最若年者を見出せ。
create table salary (name varchar(3),age int, salary double);
insert into salary values ('dan',23,67);
insert into salary values ('bob',45,94);
insert into salary values ('tom',24,94);
insert into salary values ('sue',23,45);
insert into salary values ('joe',45,51);
insert into salary values ('sam',22,51);
又は、データを入れ終わったら、次が得られる;
sqlite> .headers on
sqlite> .mode column
sqlite> select * from salary;
name age salary
---------- ---------- ----------
dan 23 67
bob 45 94
tom 24 94
sue 23 45
joe 45 51
sam 22 51
sqlite>
以下の select が、会社で最高給を貰う最若年者を示す。
sqlite> select 1000-max(salary*1000-age)%1000 from salary;
1000-max(salary*1000-age)%1000
------------------------------
24
これは正しい答である。最高給はbobとtomの94で、tomが最若年で24才である。先ず、何故1000かと言うと誰も1000才までは生きないので年齢が >= 1000になることはない。したがって max(salary*1000-age)は、俸給が >= 1である限り、年齢に関係なく最高給になる。俸給との結合で、最若年者は俸給から最低量を差し引くから、この値は最高が戻る。ここで、この数から俸給を除去するのは容易だ。俸給には1000が掛けてあるので、1000の完全因数だから、 mod 1000で消滅する。
この働きを理解するには、ステートメント次のように分解するのが役立つ。
sqlite> select salary*1000-age,salary*1000,-age from salary;
salary*1000-age salary*1000 -age
--------------- ----------- ----------
66977 67000 -23
93955 94000 -45
93976 94000 -24
44977 45000 -23
50955 51000 -45
50978 51000 -22
sqlite>
だが、年齢が負の値なのはどうだろうか?mod関数、'%'、の非Knuth法を用いると、負の数 -x は常に -xである。
x % y は次のように定義され:
x % y == x - INT(x/y)*y
y == 0に関して定義されない。
C言語とFortran はこの方法を用いる。
対照的に数学で受け入れられるKnuth 法では、python,
でも見られるように、この関数を次のように定義する。
x mod y == x - floor(x/y),
よって、 y == 0 であれば x に等しい
二つの間の相違は、 xに関する負の値で明らかになる。
別の方法であらわすと、
-x != yである限り、-x % y = -x である。
例えば、 x=4 で y=5 とすると、 -4 % 5 は -4 を戻す。
別の例を幾つか示す。ここでもmod関数に関してはKuth法ではない。
-1 % 5 = -1
-2 % 5 = -2
-3 % 5 = -3
したがって、我々が実際におこなっているのは、下記である:
1000 + -1*(1000-age) = age
C及び C++ API
サンプル C プログラム
#include <stdio.h>
#include <stdlib.h>
#include <sqlite3.h>
static int callback(void *NotUsed, int argc, char **argv, char **azColName){
NotUsed=0;
int i;
for(i=0; i<argc; i++){
printf("%s = %s\n", azColName[i], argv[i] ? argv[i] : "NULL");
}
printf("\n");
return 0;
}
int main(int argc, char **argv){
sqlite3 *db;
char *zErrMsg = 0;
int rc;
if( argc!=3 ){
fprintf(stderr, "Usage: %s DATABASE SQL-STATEMENT\n", argv[0]);
exit(1);
}
rc = sqlite3_open(argv[1], &db);
if( rc ){
fprintf(stderr, "Can't open database: %s\n", sqlite3_errmsg(db));
sqlite3_close(db);
exit(1);
}
rc = sqlite3_exec(db, argv[2], callback, 0, &zErrMsg);
if( rc!=SQLITE_OK ){
fprintf(stderr, "SQL error: %s\n", zErrMsg);
}
sqlite3_close(db);
return 0;
}
コンパイルしてプログラムを走らせるためのコマンドを下記に示す。 "-Wl,-R/usr/local/lib" オプションに格別の注意を払うこと。これはsqlite3 ソースがインストール済みであるとき、パス "/usr/local/lib" が "/etc/ld.so.conf" ファイルに記載されていないことがあるので、必要だ。
gcc -o simplesqlite3 simplesqlite3.c -Wall -W -O2 -Wl,-R/usr/local/lib -lsqlite3
上のコンパイルオプションを用いるか、又はsqlite3ライブラリ "libsqlite3.so" がインストールされているディレクトリをファイル "/etc/ld.so.conf"に追加して、シェルからコマンド "ldconfig" を走らせる。私は "-Wl,-R/" オプションを使うのが好きだが、ステップがある:
$ locate libsqlite3.so
/usr/local/lib/libsqlite3.so.0.8.6
/usr/local/lib/libsqlite3.so.0
/usr/local/lib/libsqlite3.so <--- note directory is /usr/local/lib
$ echo "/usr/local/lib" >> /etc/ld.so.conf
$ ldconfig
プログラムを入力しコンパイルした後、次のように走らせる:
$ ./simplesqlite3 test.db "create table notes (t text)"
$ ./simplesqlite3 test.db "insert into notes (t) values ('
> This is some random
> stuff to add'
>);"
$ ./simplesqlite3 test.db "select * from notes"
t =
This is some random
stuff to add
実際に重要なステートメントが三つある。データベースの名称とデータベース・ポインタを取り上げる sqlite3_open()、SQL コマンドを argv[2]で実行するだけでなく、結果の表示に用いられる callback ファンクションを記載するsqlite3_exec()、及びデータベース接続をクローズするsqlite3_close() である。
C++ program - Building a Class to do the Work
下記に定義するクラスSQLITE3を構築することが出来る。これは戻されるデータをベクトルで読み取る。注記、sqlite3_exec() ファンクションを用いる代わりに sqlite3_get_table()を用いて、代わりにSQLステートメントの結果を文字列 resultの変数アレーに読み取る。この変数は、戻りSQLヘディングとデータをベクトルvcol_head と vdataにコピイするのに使用した後 sqlite3_free_table()に渡さなければならないことに注意。したがって、第1列はヘディングにであることに注意。
class SQLITE3 {
private:
sqlite3 *db;
char *zErrMsg;
char **result;
int rc;
int nrow,ncol;
int db_open;
public:
std::vector<std::string> vcol_head;
std::vector<std::string> vdata;
SQLITE3 (std::string tablename="init.db"): zErrMsg(0), rc(0),db_open(0) {
rc = sqlite3_open(tablename.c_str(), &db);
if( rc ){
fprintf(stderr, "Can't open database: %s\n", sqlite3_errmsg(db));
sqlite3_close(db);
}
db_open=1;
}
int exe(std::string s_exe) {
rc = sqlite3_get_table(
db, /* An open database */
s_exe.c_str(), /* SQL to be executed */
&result, /* Result written to a char *[] that this points to */
&nrow, /* Number of result rows written here */
&ncol, /* Number of result columns written here */
&zErrMsg /* Error msg written here */
);
if(vcol_head.size()>0) { vcol_head.clear(); }
if(vdata.size()>0) { vdata.clear(); }
if( rc == SQLITE_OK ){
for(int i=0; i < ncol; ++i)
vcol_head.push_back(result[i]); /* First row heading */
for(int i=0; i < ncol*nrow; ++i)
vdata.push_back(result[ncol+i]);
}
sqlite3_free_table(result);
return rc;
}
~SQLITE3(){
sqlite3_close(db);
}
};
上に定義するクラスは、main又はfunctionの中で次のように用いることが出来る。
std::string s;
SQLITE3 sql("database.db");
sql.exe("create table notes (t text)");
s="insert into notes (t) values ('sample data')";
sql.exe(s);
s="select t from notes";
sql.exe(s);
以下は、このコードがmain又はfunctionの中にあるとして、selectからデータをプリントする例である。ヘディングのセクションとデータのセクションに注意。
if( sql.vcol_head.size() > 0 )
{
std::cout << "Headings" << std::endl;
copy(sql.vcol_head.begin(),
sql.vcol_head.end(),
std::ostream_iterator<std::string>(std::cout,"\t"));
std::cout << std::endl << std::endl;
std::cout << "Data" << std::endl;
copy(sql.vdata.begin(),
sql.vdata.end(),
std::ostream_iterator<std::string>(std::cout,"\t"));
std::cout << std::endl;
}
SQLite ユーザファンクションの定義
Aggregate ファンクションと Simple ファンクション
Aggregate ファンクションと Simple ファンクションの二つの型のファンクションがある。Simple ファンクションは、上で作られあらゆる表現に用いることの出来るsign()に似ている。 "avg()"に似るAggregate ファンクションは、select ステートメントの中でだけ用いられる。或るファンクション"min"と"max"は両者のように定義することが出来る。アーギュメント一つのあるmin() はAggregate ファンクションで、無限界のアーギュメントのあるmin() はSimple ファンクションである。
相違を説明する例を示す。
$ sqlite3
SQLite version 3.0.8
Enter ".help" for instructions
create table junk (a integer);
$ sqlite3
SQLite version 3.0.8
Enter ".help" for instructions
sqlite> create table junk (a integer);
sqlite> insert into junk (a) values (1);
sqlite> insert into junk (a) values (2);
sqlite> insert into junk (a) values (3);
sqlite> select * from junk;
1
2
3
sqlite> select * from junk where a=min(1,2,3,4,5);
1
sqlite> select * from junk where a=min(1);
SQL error: misuse of aggregate function min()
sqlite>
上で、変数が一つだけの min() ファンクションは、aggregateファンクションであることが分かる。これがaggregateファンクションであるだけで、where クローズの後に用いることが出来ない。aggregateファンクション、次のように select クローズの後でのみ用いることが出来る。
sqlite> select min(a) from junk
1
しかし、二つ目のアーギュメントを追加すると、simpleファンクションを呼び出すことになる。下では、各行を 2.3に比較しているのに注意。ここには微妙だが重要な相違があるので、細かく調べられたい。
sqlite> select min(a,2.3) from junk
2
2.3
1
sqlite>
ユーザ定義 Sign ファンクション−msignの作成
以下は sign ファンクションの例である。これは前に作った恒久的signファンクションと干渉しないようmsignFunc と名付ける。
void msignFunc(sqlite3_context *context, int argc, sqlite3_value **argv){
assert( argc==1 );
switch( sqlite3_value_type(argv[0]) ){
case SQLITE_INTEGER: {
long long int iVal = sqlite3_value_int64(argv[0]);
iVal = ( iVal > 0) ? 1 : ( iVal < 0 ) ? -1 : 0;
sqlite3_result_int64(context, iVal);
break;
}
case SQLITE_NULL: {
sqlite3_result_null(context);
break;
}
default: {
double rVal = sqlite3_value_double(argv[0]);
rVal = ( rVal > 0) ? 1 : ( rVal < 0 ) ? -1 : 0;
sqlite3_result_double(context, rVal);
break;
}
}
}
以下で分かるように、このファンクションは、次のように初期化される。
sqlite3_create_function(db, "msign", 1, SQLITE_UTF8, NULL,
&msignFunc, NULL, NULL));
"msign"は、sqlite3の中のファンクションの名であることに注意。 select ステートメント "select msign(3);"の中で用いる名称である。1はアーギュメントの数である。ここでのmsignはアーギュメント一つだけを取る。SQLITE_UTF8 はテキスト表現用である。このとき、NULLを飛び超えて、&msignFuncはCファンクションの名称である。最後の二つの値、これは simple ファンクションに関し NULL でなければならない−繰り返すが、simple ファンクションはselect whereクローズの任意の部分で使うことが出来る。
Aggregate ファンクション
作成するファンクションを探すのに良い場所は sqlite3ソースの中の"./src/func.c"ファイルである。新しい加算ファンクション呼出Sを作りたいとする。これは行の合計額を作る。
このファンクションの説明に以下のデータを用いる。
$ ./myfuncpp DATABASE "create table t(a integer, b integer, c integer)"
$ ./myfuncpp DATABASE "insert into t (a,b,c) values (1,-1,2)"
$ ./myfuncpp DATABASE "insert into t (a,b,c) values (2,-2,4)"
$ ./myfuncpp DATABASE "insert into t (a,b,c) values (3,-3,8)"
$ ./myfuncpp DATABASE "insert into t (a,b,c) values (4,-4,16)"
$ ./myfuncpp DATABASE "select * from t"
a = 1 b = -1 c = 2
a = 2 b = -2 c = 4
a = 3 b = -3 c = 8
a = 4 b = -4 c = 16
a = 4 b = -4 c = 16
そこで、ファンクション S が合計のリストを作る方法だ。標準aggregate sum()ファンクションと異なり、リストが戻される。
$ ./myfuncpp DATABASE "select S(a),S(b),S(c) from t"
S(a) = (1,3,6,10,14) S(b) = (-1,-3,-6,-10,-14) S(c) = (2,6,14,30,46)
上で、テーブル(1,2,3,4,5)の中の値を含む列が、リスト中で累積和 (1,1+2=3,1+2+3=6,1+..)を示すことが分かる。これは、データが文字列でなければならないので、"./src/func.c" の中で定義されるどのファンクションとも異なる。
この例と、他の例全部を見るには、
downloadを見られたい。リストが返されるので、この例は、速くて整数、ダブる及びテキストのあらゆるデータ型に適するC++ std::stringstreamを用いる。
S は aggregate ファンクションなので、二つのファンクション "SStep"と"SFinalize"がある。aggregate ファンクションには常に、 "Step" と"Finalize" がある。"Step" は各行に関して呼び出され、最終行の後に "Finalize"ファンクションが呼び出される。
A
Step と Finalize は両方とも、各行から集める累積データを保持するため、構造体を利用する。この機能のため構造体SCtx を下記に定義する。std::stringstreamはグローバルである。SCtx "char *ss" に追加の変数をおくことは助言しない。追加変数 "char *ss" が、reallocとともに動的に増得ることが出来ると考えるだろうが、問題は、構造体を捕まえるメモリを解放することである。sqlite文書が正しく指摘するように、構造体 SCtx は解放されるが、ここでも、私のテストでは、構造内のメンバに割り当てられる追加メモリは解放されない。他方、このファンクションは、同一"select S(a),S(b),...S(100th)" の中で1回以上呼び出される。オーバーヘッドは最小であると思われる。
#define MAXSSC 100
typedef struct SCtx SCtx;
struct SCtx {
double sum; /* 項目の和 */
int cnt; /* 合計される要素の数 */
int sscnt; /* Keeps counts for ss に関する計数を続ける*/