Oracle SQL その他・Tips
おすすめ書籍
本書用のWebアプリを使ってSQLを実行し学習していきます。初心者の方に分かりやすく、図表を多く使って解説されています。 学習の最後には200問越えのドリルが付いていてSQLのスキルが試せる様になっています。
RETURNING・・・INTO
INSERT文にRETURNING句をつけることでPL/SQLの変数に挿入した値が取得できます。
ただし、INSERT ... SELECT の構文では使用できません。
(INSERT ... SELECT では複数の行が挿入されるため当たり前といえばそうですが)
このRETURNING句を利用できるのは、バージョンがOracle10g以降の様です。
以下に例を示します。
SQL> INSERT INTO TM_得意先(得意先コード, 得意先名) |
3 RETURNING 得意先コード, 得意先名 INTO :v1, :v2; |
PL/SQLでの実行ですので、RETURNING句で戻される変数を「var」コマンドで宣言します。
RETURNING句で変数を指定し、実行した結果を「print」コマンドで表示しています。
VALUES句で指定された値が、RETURNING句により戻されていることが分かります。
このRETURNING句を利用することで、INSERT文の中で使用していた順序の値をINSERT直後に知ることができます。
伝票データの伝票番号に順序を使用する場合、伝票データのINSERTを行う前に、SELECT文により順序から値を取得し、
その値をINSERTのVALUES句に設定して値を渡してやります。
このやり方では、最初のSELECT文の前にトランザクションを開始し、次の伝票データのINSERT文の後でトランザクションの
コミットをする必要があります。(このあたりの一連の処理を、PL/SQLで組んだりVBのロジックで組んだりしますが)
RETURNING句で順序により設定された値を返す例を、VB.NETで行う例を示します。
02 | Dim cnn As New OracleConnection |
03 | cnn.ConnectionString = "User Id=xxx;Password=xxx;Data Source=xxx" |
07 | Dim cmd = New OracleCommand |
09 | cmd.CommandText = "INSERT INTO TT_売上(売上番号, 売上日, 得意先コード, 担当者コード)" & _ |
10 | " VALUES(TS_伝票番号.NEXTVAL, TO_CHAR(SYSDATE, 'YYYY/MM/DD'), :CUSTOM, :PERSON)" & _ |
11 | " RETURNING 売上番号, 売上日, 得意先コード, 担当者コード" & _ |
12 | " INTO :URINO, :URIDATE, :CUSTNO, :PERNO" |
16 | Dim pCustom As OracleParameter = cmd.Parameters.Add( "CUSTOM" , 1) |
17 | Dim pPerson As OracleParameter = cmd.Parameters.Add( "PERSON" , 1) |
19 | Dim pUrino As OracleParameter = cmd.Parameters.Add( "URINO" , Oracle.DataAccess.Client.OracleDbType. Decimal ) |
20 | pUrino.Direction = Data.ParameterDirection.ReturnValue |
21 | Dim pUridate As OracleParameter = cmd.Parameters.Add( "URIDATE" , Oracle.DataAccess.Client.OracleDbType. Date ) |
22 | pUridate.Direction = Data.ParameterDirection.ReturnValue |
23 | Dim pCustno As OracleParameter = cmd.Parameters.Add( "CUSTNO" , Oracle.DataAccess.Client.OracleDbType. Decimal ) |
24 | pCustno.Direction = Data.ParameterDirection.ReturnValue |
25 | Dim pPerno As OracleParameter = cmd.Parameters.Add( "PERNO" , Oracle.DataAccess.Client.OracleDbType. Decimal ) |
26 | pPerno.Direction = Data.ParameterDirection.ReturnValue |
31 | MsgBox( "売上番号 : " & pUrino.Value.ToString & " " & pUridate.Value.ToString & " " & pCustno.Value.ToString & " " & pPerno.Value.ToString) |
33 | MsgBox( "insert error:" & ex.Message) |
39 | MsgBox( "open error:" & ex.Message) |
このRETURNING句はINSERTのみならず、UPDATE、DELETE文でも使用できます。
但し、この方法では戻されるデータ件数が1件の場合のみ可能です。(UPDATE、DELETE対象の行が1行の場合)
SQL> SELECT * FROM TT_売上 WHERE 得意先コード=3; |
SQL> UPDATE TT_売上 set 得意先コード=3 |
3 RETURNING 売上番号 INTO :v1; |
1つ以上のバインド・ハンドルに、必要なコールバックが登録されていません。 |
尚、以下の様にコレクション型を宣言し、「INTO」の前に「BULK COLLECT」を記し、コレクションに値を返すことができます。
UPDATE処理後は、必要に応じて返されたコレクションのデータ処理を行います。
2 TYPE URINO_ARRAY_TYP IS TABLE OF TT_売上.売上番号%TYPE INDEX BY BINARY_INTEGER; |
3 URINO_ARRAY URINO_ARRAY_TYP; |
5 UPDATE TT_売上 set 得意先コード=3 |
7 RETURNING 売上番号 BULK COLLECT INTO URINO_ARRAY; |
9 FOR i IN 1..URINO_ARRAY. COUNT LOOP |
10 DBMS_OUTPUT.PUT_LINE( '売上番号(' || TO_CHAR(i) || ')=' || URINO_ARRAY(i)); |
DATE型の考察
DATE型の内部は、DATE型の整数部に日付データを、小数部に時刻データを持っています。
小数部は、1日の秒数分の1を最小の単位としています。つまり「1/(24*60*60)」を(24*60*60)回足すと1日になるということです。
以下にPL/SQLでDATE型への整数部への加算、減算、及び小数部への加算、減算を行った例を示します。
4 WDATE := TO_DATE( '2011/07/31 13:10:56' , 'YYYY/MM/DD HH24:MI:SS' ); |
5 /* 数値の1を加算すると日付は次の日付となる */ |
7 DBMS_OUTPUT.PUT_LINE( 'WDATE=' || TO_CHAR(WDATE, 'YYYY/MM/DD HH24:MI:SS' )); |
8 /* 数値の1を減算すると日付は前の日付となる */ |
10 DBMS_OUTPUT.PUT_LINE( 'WDATE=' || TO_CHAR(WDATE, 'YYYY/MM/DD HH24:MI:SS' )); |
12 /* 1日は(24*60*60)秒で,(1/24*60*60)を1日分合計すると日付は次の日付となる */ |
13 FOR I IN 1 .. (24*60*60) LOOP |
14 WDATE := WDATE + (1 / (24*60*60)); |
16 DBMS_OUTPUT.PUT_LINE( 'WDATE=' || TO_CHAR(WDATE, 'YYYY/MM/DD HH24:MI:SS' )); |
18 /* 1日は(24*60*60)秒で,(1/24*60*60)を1日分減算すると日付は前の日付となる */ |
19 FOR I IN 1 .. (24*60*60) LOOP |
20 WDATE := WDATE - (1 / (24*60*60)); |
22 DBMS_OUTPUT.PUT_LINE( 'WDATE=' || TO_CHAR(WDATE, 'YYYY/MM/DD HH24:MI:SS' )); |
WDATE=2011/08/01 13:10:56 |
WDATE=2011/07/31 13:10:56 |
WDATE=2011/08/01 13:10:56 |
WDATE=2011/07/31 13:10:56 |
このDATE型の性質は日付を使ったループ処理等で有効に使えるような気がしますが...。(裏技的なのかもしれませんが)
上記のPL/SQLのことをSELECT文で行うことも可能です。
2 TO_CHAR(TO_DATE( '2011/07/31' , 'YYYY/MM/DD' ) + 2, 'YYYY/MM/DD' ) , |
3 TO_CHAR(TO_DATE( '2011/07/31 13:10:56' , 'YYYY/MM/DD HH24:MI:SS' ) + (1 / (24*60*60)), |
4 'YYYY/MM/DD HH24:MI:SS' ) |
TO_CHAR( TO TO_CHAR(TO_DATE('20 |
2011/08/02 2011/07/31 13:10:57 |
転職を本気で考えている方向けのプログラミングスクール!【WebCampPRO】
外部結合演算子 (+) とANSI規格について
SELECT文などでテーブルの外部結合を行う場合、WHERE句でカラム名の直後に「(+)」の演算子を付加して行います。
ただこの演算子「(+)」はOracleだけの表現方法で、他のSQL-SERVERやMySQLなどのデータベースではANSIの規格に準拠しています。
Oracleも9iのバージョンからこのANSIの規格もサポートしています。
さて、今までこのサイトで載せてきたSQL文は全て演算子「(+)」を使ってきましたが、ANSIの規格でどうなるのかを以下に示します。
「売上明細」テーブルと連結される「商品」マスタで、商品コードが「商品」マスタ側に無い場合でも
「売上明細」を抽出したい場合には以下の様なSQLとなります。
SQL> SELECT TU.売上番号, TU.明細番号, TU.商品コード, TS.商品名 |
2 FROM TT_売上明細 TU , TM_商品 TS |
3 WHERE TU.商品コード = TS.商品コード(+) |
4 ORDER BY TU.売上番号, TU.明細番号; |
上は、売上明細のデータの中にある商品コードの「8」の商品マスタのデータを仮に「81」とした結果を示しています。
商品コードが存在しない部分の商品名はNULLが表示されています。
これをANSIの規格で書き直したのが以下のSQLです。
SQL> SELECT TU.売上番号, TU.明細番号, TU.商品コード, NVL(TS.商品名, '商品が未登録' ) AS 商品名 |
2 FROM TT_売上明細 TU LEFT JOIN TM_商品 TS |
4 ORDER BY TU.売上番号, TU.明細番号; |
少し、SELECT句の中を変えましたが、結果は同じものが表示されます。 LEFT JOIN のやり方をまとめますと以下の様な感じです。尚、結合するキーが2個以上ある場合は、「AND」で条件を追加します。
FROM <表名A> LEFT JOIN <表名B>
ON <表名A>.<キーA1> = <表名B>.<キーB1>
AND <表名A>.<キーA2> = <表名B>.<キーB2>
...
演算子「(+)」が付かない場合、いわゆる等結合では「LEFT」の部分を「INNER」に変えます。
SQL> SELECT TU.売上番号, TU.明細番号, TU.商品コード, TS.商品名 |
2 FROM TT_売上明細 TU INNER JOIN TM_商品 TS |
4 ORDER BY TU.売上番号, TU.明細番号; |
■関連記事
⇒
表の結合・クロス結合
⇒
表の結合・等結合
⇒
表の結合・外部結合
⇒
表の結合・自己結合
⇒
表の結合2(JOIN)・等結合「INNER JOIN」での結合
⇒
表の結合2(JOIN)・外部結合「INNER JOIN」「LEFT JOIN」での結合
⇒
表の結合2(JOIN)・JOINのAND条件
転職を本気で考えている方向けのプログラミングスクール!【WebCampPRO】
NULLについて再考
最近の仕事で少し簡単なところでつまずいたのですが、それがNULLの取扱でした。
例として「TM_商品」のテーブルに「削除フラグ」のカラムを追加します。「削除フラグ」はNUMBER型として、「1」が立っていたら削除済みと考えます。
取り合えず、テストとして削除済みのデータを追加してみます。
SQL> INSERT INTO TM_商品 VALUES (999, 'TEST999' , '' , 9000, 1500, 1); |
SQL> SELECT * FROM TM_商品; |
商品コード 商品名 商品区分 仕入単価 売上単価 削除フラグ |
1 PC-9001 デスクトップパソコン 65000 98000 |
2 PC-9002 デスクトップパソコン 95000 120000 |
3 PC-9003 デスクトップパソコン 150000 190000 |
4 NOTE-1010 ノートパソコン 125000 188000 |
5 NOTE-1020 ノートパソコン 145000 200000 |
6 NOTE-1030 ノートパソコン 155000 220000 |
7 PRT-3001 プリンタ 45000 88000 |
81 PRT-4001 プリンタ 115000 180000 |
9 CRT-1001 ディスプレイ 45000 78000 |
10 CRT-2001 ディスプレイ 55000 98000 |
11 HUB-A001 ネットワーク 20000 40000 |
12 HUB-B001 ネットワーク 40000 60000 |
そこで削除されていないデータの一覧を抽出しようとして以下のSQLを実行すると、結果は何もありませんでした。
SQL> SELECT * FROM TM_商品 WHERE 削除フラグ != 1; |
当然といえばそうなのですが、「削除フラグ」を追加した時点では、既に存在する「TM_商品」のデータの「削除フラグ」はNULLになります。
WHERE句の「!=」判定ではNULLのデータは除外されるのを忘れていました。
それで、NVL関数を用いた以下のSQLで正常な結果が得られる様になりました。
SQL> SELECT * FROM TM_商品 WHERE NVL(削除フラグ, 0) != 1; |
商品コード 商品名 商品区分 仕入単価 売上単価 削除フラグ |
1 PC-9001 デスクトップパソコン 65000 98000 |
2 PC-9002 デスクトップパソコン 95000 120000 |
3 PC-9003 デスクトップパソコン 150000 190000 |
4 NOTE-1010 ノートパソコン 125000 188000 |
5 NOTE-1020 ノートパソコン 145000 200000 |
6 NOTE-1030 ノートパソコン 155000 220000 |
7 PRT-3001 プリンタ 45000 88000 |
81 PRT-4001 プリンタ 115000 180000 |
9 CRT-1001 ディスプレイ 45000 78000 |
10 CRT-2001 ディスプレイ 55000 98000 |
11 HUB-A001 ネットワーク 20000 40000 |
12 HUB-B001 ネットワーク 40000 60000 |
最初のところで「削除フラグ」のカラムを追加したときに、DEFAULTで既定値を0とし、「NOT NULL」を指定しておけばこんなことにはならなかったのですが。
既に存在するカラムに対して、以下の変更はできますが、DEFAULT指定をするとエラーになります。
カラムデータが既にNULLだった場合には、変更はできないようです。
SQL> ALTER TABLE TM_商品 MODIFY 削除フラグ DEFAULT 0; |
SQL> ALTER TABLE TM_商品 MODIFY 削除フラグ DEFAULT 0 NOT NULL ; |
ALTER TABLE TM_商品 MODIFY 削除フラグ DEFAULT 0 NOT NULL |
ORA-02296: (xxxxx.)を有効化できません - NULL 値が見つかりました。 |
SUBSTR関数でVBのRight、Left関数の様な使い方
Oracleの関数には、VB.NETで用意されているRight関数やLeft関数が存在しません。
文字列の抽出で、左から何桁とか、右から何桁とかを切り出す処理はよくあるものです。
OracleではSUBSTR関数しかないので、これを使うしかありません。
SUBSTR関数の引数は以下の様になっています。
SUBSTR ( string , position [, length] )
SUBSTRB ( string , position [, length] )
・string:抽出元の文字列
・position:抽出開始位置(1~)
(負の場合は右端から逆順に処理)
・length:抽出文字列長
・関数の戻り値:抽出文字列
SUBSTRは文字列をキャラクタ単位での処理を行い、
SUBSTRBは文字列をバイト単位での処理を行います。
文字列に漢字等のマルチバイト文字が含まれている場合にはSUBSTRBは問題があります。
文字コードがシフトJISの場合で例を示しますと以下の様になります。
2 '/' || SUBSTR( '123457890' , 2, 2) || '/' AS 半角SUBSTR |
3 , '/' || SUBSTR( '1234567890' , 2, 2) || '/' AS 全角SUBSTR |
4 , '/' || SUBSTRB( '123457890' , 3, 2) || '/' AS 半角SUBSTRB |
5 , '/' || SUBSTRB( '1234567890' , 3, 3) || '/' AS 全角SUBSTRB |
半角SUBS 全角SUBSTR 半角SUBS 全角SUBSTR |
SUBSTRは全角文字があっても、1文字は1文字として処理されます。
ただし、SUBSTRBは4つ目の例でも分かるように、漢字の泣き別れが発生し、
3桁目には半角の空白が返ってきます。
漢字の泣き別れの件はどうするのかは、テーブル設計や、出力する時に対応する様に
するしかないと思います。
上の例の position のところをマイナスにすると以下の様になります。
文字列の最後からの処理になっているのが分かります。
2 '/' || SUBSTR( '123457890' , -2, 2) || '/' AS 半角SUBSTR |
3 , '/' || SUBSTR( '1234567890' , -2, 2) || '/' AS 全角SUBSTR |
4 , '/' || SUBSTRB( '123457890' , -3, 2) || '/' AS 半角SUBSTRB |
5 , '/' || SUBSTRB( '1234567890' , -3, 3) || '/' AS 全角SUBSTRB |
半角SUBS 全角SUBSTR 半角SUBS 全角SUBSTR |
さて、Right関数 Left関数 的な利用ですが、
Right関数 では position を 「1」として length に文字数を設定します。
Left関数 では position を文字数のマイナス値として length に文字数を設定します。
2 '/' || SUBSTR( '123457890' , 1, 2) || '/' AS 半角 Left 関数的 |
3 , '/' || SUBSTR( '1234567890' , 1, 2) || '/' AS 全角 Left 関数的 |
4 , '/' || SUBSTRB( '123457890' , -2, 2) || '/' AS 半角 Right 関数的 |
5 , '/' || SUBSTRB( '1234567890' , -4, 4) || '/' AS 全角 Right 関数的 |
半角 LEFT 全角 LEFT 関数 半角RIGH 全角 RIGHT 関 |
尚、番号などを先頭「0」付きで編集する場合、以下の様な使い方もあります。
2 '/' || SUBSTR(TO_CHAR(123, '000000000000' ), -8, 8) || '/' AS 数値先頭0付加 |
3 , '/' || TO_CHAR(123, 'FM00000000' ) || '/' AS 先頭0付加書式 |
TO_CHAR関数 でフォーマットを使えば2行目の様にできますが、
変換する数値が8桁より大きく、表示が下8桁のみの場合は1行目の方法も有用かと思います。
■関連記事
⇒
Oracle SQL 関数その1・SUBSTR
ログインエラーでアカウントがロックされた場合の、強制ログインを使った対処方法
オラクルでログイン処理で何回もエラーを起こした場合には、
既定回数を超えるとアカウントがロックされてしまいます。
以下のSQLの実行を見てください。(SQLコマンドラインから実行しています)
なお、既定回数はデフォルトでは10回の様です。
10回目の「CONNECT」でアカウントがロックされました。
ORA-28000: the account is locked |
このTESTユーザのアカウントのロックを解除する為に、強制的にログインします。
「/ AS SYSDBA」でのログインはOS認証で「SYSDBA」の権限を持つログインになります。
SQL> ALTER USER TEST ACCOUNT UNLOCK; |
「ALTER USER」命令によりアカウントをアンロックしています。
尚、強制ログインの方法は「CONNECT SYSTEM/MANAGER AS SYSDBA」などもあります。