プログラミングTips

参考になった!役立った!・・・・そのときは一言メールお願いします!

Oracle ヒント

VisualBasicヒント VisualC/C++ヒント Linuxヒント




Oracleヒント


DATETIME型への日数・時間の足し算/引き算
 
DATETIME型の変数(列)に日数を足す(引く)ときは
DT := DT + 1;       /* 1日進める */
DT := DT - 5;       /* 5日戻す */
のようにします。

さらに時間単位で足し引きしたいときは

DT := DT + 1/24;              /* 1時間進める */
DT := DT - 10/24/60;       /* 10分戻す */
のようにします。

Iniファイルをストアドプロシージャから読む(WindowsAPIをコールする)
Oracle8(もしかしたら、Oracle7の高バージョン)ではプロシージャからDLLを呼ぶことができます。
この機能を利用すれば、当然WindowsAPIだって呼べちゃいます。

ここでは、Iniファイルを読むAPIを使ってみます。

Iniファイルを読む関数としてはGetPrivateProfileIntA、GetPrivateProfileStringAがあります。
VBのAPIビューアを見ると、それぞれ以下のように定義されています。
 

Declare Function GetPrivateProfileInt Lib "kernel32" Alias "GetPrivateProfileIntA"
(ByVal lpApplicationName As String, ByVal lpKeyName As String, ByVal nDefault As Long, ByVal lpFileName As String) As Long

Declare Function GetPrivateProfileString Lib "kernel32" Alias "GetPrivateProfileStringA"
(ByVal lpApplicationName As String, ByVal lpKeyName As Any, ByVal lpDefault As String, ByVal lpReturnedString As String, ByVal nSize As Long, ByVal lpFileName As String) As Long


この情報を利用してPL/SQLの外部プロシージャを定義します。

--↓ここからプログラム開始

-- DLLファイルの名称定義 ディレクトリは環境によって変えてください
create or replace library kernel32 is 'c:\winnt\system32\kernel32.dll';
show errors

-- APIのGetPrivateProfileIntA(Iniファイルから数値を取り出す)
create or replace function GetIniInt(
lpApplicationName CHAR,
 lpKeyName CHAR,
 nDefault BINARY_INTEGER,
 lpFileName CHAR
 ) RETURN BINARY_INTEGER AS
EXTERNAL
  LIBRARY kernel32
  NAME "GetPrivateProfileIntA"
  LANGUAGE C
  CALLING STANDARD C;
/
show errors

-- APIのGetPrivateProfileStringA(Iniファイルから文字列を取り出す)
create or replace function GetIniString(  --この関数名は好きなものを付けられます
lpApplicationName  String,
lpKeyName  String,
lpDefault  String,
lpReturnedString OUT String,
nSize BINARY_INTEGER,
lpFileName String
 ) RETURN BINARY_INTEGER AS
EXTERNAL
  LIBRARY kernel32
  NAME "GetPrivateProfileStringA"
  LANGUAGE C
  CALLING STANDARD C;
/
show errors
--↑ここまでプログラム

関数名、引数名は好きなものを付けられます。引数の並びはAPIに合わせて下さい。
また、VBの定義でlongだった部分は、BINARY_INTEGERを使用してみました。LONG型はOracleでは2Gbyteまで扱える文字変数なので気を付けましょう。と、いうか、私は見事に引っかかりました・・(^-^;A

ついでに呼び出しのスクリプトの例も記述しておきます。

set serveroutput on
declare
  dat varchar2(255);
  ret int;
begin

 ret := GetIniInt('intl','iCountry', 0, 'c:\winnt\win.ini');
 dbms_output.put_line(ret);

 ret := GetIniString('intl','sCountry', 'Default', dat, 255, 'c:\winnt\win.ini');
 dbms_output.put_line(dat || '  ' || ret);

end;




Oracleインストーラーが異常終了する(Oracle8)
 
Oracleのインストール完了直前にインストーラーが異常終了する事があります。
どうやら、Y2K関係の問題らしいです。
これを回避するには、オラクルのHPからY2K対策済みのインストーラーをDLして使用する必要があります。




ProC/C++を使ったプログラムが不可解なオラクルエラーで落ちる!
 
ProC/C++で作成したどー考えても単純なSQL文が原因不明のエラーを出すときは、ProCのpreファイル(メークファイルみたいなもの??)を 新規に作成しなおしてみましょう。特に、Oracle7で作成したpreファイルをOracle8にバージョンアップするときは必ず新規で作った方が無難です。 私はこれで、3時間潰しました・・・(泣)
あっ、落ちるっていっても、異常終了じゃないですよ〜
(現象が起きたのはOracle8.0.5でした)

SQL文の効率を調べたい!(実行計画の取得方法)
 
作成したSQL文が実際にDBエンジンでどんな風に解釈され検索されるか・・・・これって以外と解らないものです。
単純なSQL文であればそれほど問題にはなりませんが、大量データの、しかも複数のテーブルを連結している場合はどんな順序でテーブルが検索されているか解るとパフォーマンス改善に役立ちます。
そんな時にはSQL文の実行計画を取得すると一目瞭然です。
詳しくはオラクル社のHPの技術資料(アプリケーション開発資料)にPDFドキュメントとしてUPされています。
ここでは簡単に説明したいと思います。

@ SQL*Plus、OracleSQLWorksheet等で実行計画を取得したいユーザーにログイン。
   utlxplan.sqlスクリプトを実行し実行計画を取得したいユーザー毎にPLAN_TABLE表を作成する。(初回のみ)

SQL>@%ORACLE_HOME%\RDBMS80\ADMIN\UTLXPLAN.SQL
(下線部分はOracleのバージョン等の環境により変化します。)
 
A 以下のスクリプトを実行する。
explain plan set statement_id = 'stateid1' for
  select * from tablename                       ←この部分に調べたいSQL文を記述する
/
select lpad(' ', 2*(level-1)) || operation || ' ' || options
          || ' ' || object_name || ' ' || DECODE(id, 0, 'Cost=' || position) "Query Plan"
  from plan_table
start with id = 0
   and statement_id = 'stateid1'
connect by prior id = parent_id
         and statement_id = 'stateid1'
/
delete from plan_table where statement_id = 'stateid1'
/
commit
/
 
ここでは表示結果の詳しい内容については説明しません。(詳しくは技術資料のPDFドキュメントを参照)
基本的には表示結果の上から順に評価されているようです。
表の絞り込みの順序は正しいか?インデックスを効果的に使用しているか?等をSQL文を変えながら確認してください。
 

※注 たとえ同一のSQL文であったとしてもANALYZE(統計情報)を取得すると実行計画の内容が変わる事があります。


PL/SQL中で一時停止(スリープ)したい!(CPU負荷分散等のため)
 
久々の更新です。 PL/SQL中で一時停止したいときは以下のとおりです。
dbms_lock.sleep(1); -- 1sスリープする
ちなみに1秒以下の値も設定できます。
dbms_lock.sleep(0.1); -- 100msスリープする
dbms_lock.sleep(0.01); -- 10msスリープする

(Oracle9.2)PL/SQLでDELETEしたレコードの件数を取得したい!(暗黙のカーソル定義)
 
PL/SQL中で「DELETE FROM TblName;」とかって実行した時に何件削除したかは、SQL%ROWCOUNTに格納されます。 暗黙のカーソルには”SQL”っていう名前が付いていたんですね〜。 知りませんでした・・・ これを応用して以下の様に記述すると、大量件数削除する時にサーバーの負荷を上がりにくできます。
loop  DELETE FROM aaa WHERE bbb = 1 and ROWNUM < 100;  dbms_lock.sleep(0.1); -- 100msスリープする  EXIT WHEN SQL%ROWCOUNT=0; end loop;

utl_file_dirパラメータに複数ディレクトリを定義する時の注意事項!
 
utl_file_dirで複数のディレクトリを指定するときはカンマで区切るのですが、単にカンマで区切るとORA-20091(INVALIDPATHパスのエラー)が発生します。
複数指定したい時は、SPFILEに以下の様にディレクトリ名を”(シングルクオーテーション)でくくっって記述してください。
utl_file_dir='C:\aaa','C:\bbb'

クライアントからの接続を早くしたい!
 
クライアントのoracle\ora92\network\ADMIN\sqlnet.ora の
SQLNET.AUTHENTICATION_SERVICES= (NTS)
という行を ↓
#SQLNET.AUTHENTICATION_SERVICES= (NTS)
SQLNET.AUTHENTICATION_SERVICES= (NONE)
に変更します。 これでログイン認証時に無駄にかかっていた時間を短縮できます。

(Oracle9.2)現在時間をms(ミリ秒)単位で取得したい!
 
デバッグ時等に時間計測したりするときにms(ミリ秒)単位の現在時間を取得したい時は以下の様に記述します。

set serveroutput on
declare
 ti TIMESTAMP;
begin
 ti := SYSTIMESTAMP;
 dbms_output.put_line(ti);
end;
/

実行結果は・・
04-03-23 09:41:12.460000
日付書式はOracle 初期化パラメータのNLS_TIMESTAMP_FORMATで規定できます。(オラクルの再起動が必要となります)

utl_fileパッケージについて
 
utl_fileパッケージには、C言語に慣れていると間違いやすい幾つかの「クセ」があるようです。

その@

utl_file.putを使用してファイル出力する時は、putした文字列が32Kbyteになる前に「utl_file.fflush」する必要がある。
32Kbyteを超えて出力後にファイルクローズしても、32Kbyteまでの分しか出力されない。
(今マニュアルを読んでみたらそう書いてありました・・・)
しかし、いちいちflushしないといけないなんて面倒です。(−−;

そのA

2つのファイルをオープンし(1つはReadモード。もう1つはWriteモード)片方でget_lineしたものをもう片方にputするような時、改行文字(chr(10))を最後に付けないで複数行書き込むとエラー(WRITE_ERROR)になる。
get_lineした文字列には改行文字が含まれていると思われるのですが、putの時それを認識せず1行のバッファをオーバーしてしまう様です。


VisualBasicヒント


テキストBOXを右ボタンクリックしたときのメニューを独自のものにする
 
テキストBOX中で右ボタンクリックすると標準では"コピー”、”張り付け”等のメニューがでます。
これを自分で作成したメニューに変えるときはMouseDownイベント中に以下の様に記述します。
Private Sub text1_MouseDown(Button As Integer, Shift As Integer, X As Single, Y As Single)
  If Button = vbRightButton Then
    text.Enabled = False
    text.Enabled = True
    text.SetFocus
    PopupMenu mymenu, vbPopupMenuRightButton
  End If
End Sub
(mymenuはメニューエディタで編集したメニューの名称)
また、アプリケーションキー(キーボードの右下のコントロールキーの左側に付いているキー)や、Shift+F10を押してメニューを表示する場合のためにKeyDownイベントに以下の様に記述します。
Private Sub text1_KeyDown(KeyCode As Integer, Shift As Integer)
    If KeyCode = 93 Or (KeyCode = 121 And Shift = 1) Then
      text1.Enabled = False
      text1.Enabled = True
      text1.SetFocus
      PopupMenu mymenu, vbPopupMenuRightButton
    End If
End Sub


ウインドウの枠やタイトルバーのサイズを取得する(API使用)
 
Formのプロパティでは枠やタイトルバーの大きさ分からないですよね!
そんなときは、WINDOWS APIのGetSystemMetrics()関数を使用します。
Public Declare Function GetSystemMetrics Lib "user32" (ByVal nIndex As Long) As Long

Const SM_CYCAPTION = 4  ' タイトルバーの高さ取得
Const SM_CXFRAME = 32 'フォームのフレームの幅取得
Const SM_CYFRAME = 33 'フォームのフレームの高さ取得

Global gCapHeight As Integer
Global gFrmWidth As Integer
Global gFrmHeight As Integer
Global gTwipX As Integer
Global gTwipY As Integer

Public Sub GetSystem()
  gTwipX = Screen.TwipsPerPixelX
  gTwipY = Screen.TwipsPerPixelY
  gCapHeight = GetSystemMetrics(SM_CYCAPTION) * gTwipY
  gFrmWidth = GetSystemMetrics(SM_CXFRAME) * gTwipX
  gFrmHeight = GetSystemMetrics(SM_CYFRAME) * gTwipY
End Sub



DLLにバイナリーデーターを渡す方法
 
32ビット版のVBではString型の内部形式がUNICODEのためDLL呼び出し時にVBが勝手にUNICODE→ASCII変換を行います。
したがって、文字以外のバイナリーデーターを渡す時はSTRING型ではなくてByte型の配列を使います。
ところが・・・これがうまくいかなくて悩みました(^−^;
結論としては、VBでDLLの関数を宣言する時に、ByRefのAny型(Byte()のような配列ではない)で宣言し、関数を使用する時は
'宣言部分
Public Declare Function DllFunc Lib "tekito.dll" (ByRef Msg As Any) As Long

'呼び出し部分
DllFunc(A(0))


の様に、配列の先頭を指定する様にする必要があるようです。A(0)の(0)が重要です。
けっこう悩みました・・・



MsFlexGridの表上で右ボタンクリックした時にカレントセルをクリックした位置に移動する

通常グリッド上で左ボタンを押すとカレントのセルがマウスの位置に移動します。
でも右ボタンを押したときは移動しません。
右ボタンを押したときにもカレントセルを移動したいときはMouseCol、MouseRowプロパティを使用します。
たとえば、GRDListという名前でMsFlexGridコントロールを定義している場合に行のみ右クリックした位置に移動したいとします。
その場合はMouseDownイベントに以下の様に記述します。

Private Sub GRDList_MouseDown(Button As Integer, Shift As Integer, X As Single, Y As Single)
  If Button = vbRightButton Then
    If GRDList.MouseRow > 0 Then
      GRDList.Row = GRDList.MouseRow
    End If
  End If
End Sub

”If GRDList.MouseRow > 0 Then”としているのは範囲外(−1)とタイトル行(0)を対象外にしているためです。




VisualC/C++ヒント


ディレクトリ内のファイル一覧を取得する
 
あるディレクトリに含まれるファイル名の一覧を取得する方法なんですが、VCでこれを実現するのに結構苦労しちゃいました。それと言うのも、昔やったことのある方法が全然使えなかったからなんです。(^_^;
昔にやったのは、カーニハン/リッチー著の「プログラミング言語C」という本にも載っているやり方で、ファイルをオープンするのと同じ関数でディレクトリをオープンしてファイルの中身を読むようにディレクトリリストを取得するというやり方でした。
int fd;

if((fd = open(dirname, O_RDONLY, 0)) != -1) {
  while(read(fd, (char*)dirbuf, sizeof(dirbuf)) == sizeof(dirbuf)) {
    printf("%s\n", dirbuf);
  }
  close(fd);
}

ところが、VC(ver.5)で上記のようにしてディレクトリーをオープンしようとすると-1が帰ってきて終わっちゃうんです!(知ってるひとから見れば当たり前?)
で、オンラインマニュアルを調べて見ると・・・ありました。find系関数というものが。
この関数は指定したディレクトリ内のファイル(ワイルドカード指定もできる!)を取得できます。
  long   hFile;
  struct _finddata_t c_file;

   /* ファイル一覧取得 */
  if((hFile = _findfirst("c:\*.*", &c_file)) != -1L) {
    printf("%s\n", c_file.name);

    while( _findnext(hFile, &c_file) == 0) {
     printf("%s\n", c_file.name);
    }
  }
  _findclose( hFile );

のようにします。
_finddate_t構造体にはアトリビュート情報とかいろいろ入っているみたいです。(詳しくはオンラインマニュアルを)




Linuxヒント


時間指定のshutdown実行後、一般ユーザーのログインができなくなった!
 
明日は早朝6:30から全館停電。メールとWebのサーバーは"shutdown -h 06:00"というコマンドで朝6時に自動的に落とすようコマンドを打って帰りました。
どうやら無事shutdownができたようですが・・・・・なぜかWebサーバーの方にTelnetでLoginできません。
メールサーバーに一旦Loginしてからリモートログインしようとすると
The system is going down on Fri Feb 16 06:00:51 2001
Login incorrect
というメッセージが表示されてしまいます。
使用しているOSはメールサーバーの方がTurboLinux3.0でWebがTurboLinux4.0です。
なんでじゃ〜!!と調べて見ると・・・ありました。
/etc/nologin
どうやらこのファイルがあると、管理者権限を持たないユーザーのLoginができなくなるらしいのです。
案の定、このファイルが残っていました。
メールサーバの方はこの現象が起きなかったので、shutdownの仕様が変わったのかバグでしょう(^−^;
このファイルを削除したらちゃんとLoginできるようになりました。



 by いっしー