この春プログラミング業界に就職しました新入社員です。
皆様のお知恵を拝借したく、質問させていただきました。

先日、以下のような処理を任されました。

・顧客ID,注文NO,商品NO,処理日時からなる注文テーブルに、
・同一商品の送付対象となる顧客のIDリスト(csvファイル)を元に、
・新しい注文を追加する

注文テーブルの主キーは顧客IDと注文IDを連結したものであり、
顧客001が過去に3回注文しているとすると、
今回追加すべきレコードは001,4,商品NO,処理日時となります。

急ぎの仕事でしたので格好良さは度外視し、
注文テーブルの注文NOの最大値を求め、
select文で顧客のIDリストと注文テーブルを連結して注文NOを1から最大値まで順に指定して表示し、
顧客IDごとに今回入力すべき注文NOを把握して、
IDリストのcsvファイルをエクセルで編集して顧客ID,注文NO,商品NOからなる
テーブルを作って注文テーブルに流し込み、
注文テーブルの処理日時がNULLになっているレコードを指定してgetdate()で現在時刻を放り込みました。

今回は幸いにして注文NOの最大値が3と小さく、
対象の顧客も1000人ほどと少なかったためにごり押しできましたが、
今後も同じような処理をする必要があるときに同じ手が通じるかどうか、
というかミスが怖くて二度と使いたくありません。

もっとスマートな方法があるのではないかと調べてはみたのですが、
顧客IDごとに注文IDの最大値+1を求めてinsertする辺りの処理が探し当てられず、
ここで質問させていただいた次第です。
よい方法をご存知の方がおられましたら、ご教授いただけましたら幸いです。

このQ&Aに関連する最新のQ&A

A 回答 (4件)

指摘したことの繰り返しになりますが、ユーザーA,Bがいてほぼ同時に作られたSQLを実行した場合、後続のSQLは先行のSQLの終了を待ちません。

すなまち、同じMAX値を取得します。

作られたSQLシングルユーザーで間をおいて実行している限りは正常に動くでしょう。しかし、マルチユーザーで負荷が高く鳴った時はエラーを起こす可能性をもっています。潜在的バグですね。
    • good
    • 0
この回答へのお礼

お礼が遅くなりました。
基本的にDBの更新は誰もアクセスしていないことを確認した上で行いますので、とりあえずは大丈夫なようです。
けれど、覚えておくべきことですね。ありがとうございました。

お礼日時:2011/05/10 06:41

トランザクション処理をする場合、


INSERT INTO ... SELECT MAX(注文NO) FROM 注文テーブル
が同時実行されない保証はありません。
そうすると、後続のINSERT文は重複キーエラーを起こしてしまいます。
保証しようすれば、注文テーブルをテーブル単位にロックをかける必要がありますから却ってパフォーマンスが劣化します。
現在のRDBMSはほとんど行レベルロック機能を持っていますからロック粒度を小さくして同時実行性を増やしたほうが全体のスループットが増すと思います。
    • good
    • 0
この回答へのお礼

二度目のご回答ありがとうございます。
あの後、更に調べを進めまして、どうにか実行可能なSQL文を組むことができました。


insert into 注文テーブル(顧客ID,注文NO,商品NO,処理日時)
select 注文テーブル.顧客ID,
MAX(注文テーブル.注文NO)+1,
1234,
getdate()
FROM 注文テーブル inner join IDリスト
on 注文テーブル.顧客ID = IDリスト.顧客ID
GROUP BY 注文テーブル.顧客ID


1234は仮の商品NOです。
目的の動作をすることはテスト用サーバで確認できました。
ただ情けないことに、今回いただいたお言葉である『同時実行』や『重複キーエラー』といったものについて理解が不足しております。
ですので、上記のSQL文には何か重大な欠陥があるかもしれません。
もしnora1962様からご覧になられて不適切な箇所がありましたら、ご教授いただけますでしょうか。

何度もお手間を取らせてしまい、大変心苦しいのですが、ご検討いただけましたら幸いです。

お礼日時:2011/04/26 10:27

自分も深くは理解していないため、


間違っているかもしれませんが、

INSERT INTO
 注文テーブル
( 顧客ID
 ,注文回数
 ,商品NO
 ,処理日時
) VALUES (
 'hoge1'
 ,(SELECT
   MAX(注文回数)
  FROM
   注文テーブル
  WHERE
   顧客ID = 'hoge1'
  GROUP BY
   顧客ID) + 1
 ,'hoge2'
,GETDATE()
);

こういった感じに書いて、hoge1とhoge2を指定すれば
一度で最大数+1を取得してInsertができると思います。

テストはしていないので、
間違っていたら済みません。
    • good
    • 0
この回答へのお礼

すばやいご回答ありがとうございます。
初心者という言葉が免罪符になるとは思っていませんが、なにぶん知識・経験ともに不足しておりますので見当違いの解釈をしているかもしれません。
その際は「頭の悪いやつだなあ」と笑って流してやっていただければ幸いです。

挙げていただいたSQL文について、hoge2に関しては単一の商品NOですので直接指定できます。
問題はhoge1のほうでして、対象となる顧客IDの羅列から、一つずつ取り出してhoge1に指定し、一気に実行する方法がわからないのです……。

お礼日時:2011/04/25 16:40

顧客IDごとに採番レコードを持つ採番テーブルを使用するのはどうですか。



トランザクション開始
SELECT 連番値 FROM 採番テーブル WHERE 顧客ID='該当顧客ID' FOR UPDATE
INSERT INTO 注文テーブル VALUES ( '該当顧客ID', 連番値+1 ... )
UPDATE 採番テーブル SET 連番値 = 連番値 + 1
トランザクション COMMIT
    • good
    • 0
この回答へのお礼

すばやいご回答ありがとうございます。
初心者という言葉が免罪符になるとは思っていませんが、なにぶん知識・経験ともに不足しておりますので見当違いの解釈をしているかもしれません。
その際は「頭の悪いやつだなあ」と笑って流してやっていただければ幸いです。

注文テーブルの更新は、通常アプリからの操作によってなされております。
今回は大量発注ということで、1000人以上に同じ操作をアプリ上でするよりもDBに直接追加したほうが早いとクライアントから依頼をいただきました。
今後同様の依頼がある可能性を考慮すると、これまでになかった採番テーブルを新たに追加し、更に採番テーブルを常に最新の状態に保つため、注文テーブルへのINSERT命令発行と同時に採番テーブルを更新するトリガーを組む必要があると思うのですが、これは今回の『注文の追加』という依頼の範囲を若干オーバーしているように思えてなりません。
単に私が臆病なだけかもしれませんが……新入社員が上司に進言しても差し支えない範囲の処理でしょうか?

お礼日時:2011/04/25 16:21

このQ&Aに関連する人気のQ&A

お探しのQ&Aが見つからない時は、教えて!gooで質問しましょう!

このQ&Aを見た人はこんなQ&Aも見ています

関連するカテゴリからQ&Aを探す

このQ&Aを見た人が検索しているワード

このQ&Aと関連する良く見られている質問

QPDOのバインドをforeachでまとめて処理したいができません…。

いつもお世話になっております。
PHP5.2.5
---------------------------
DB(フィールドは以下3項目)
・id(primary key)
・color
・num
---------------------------

//DBにインサートするデータ群(配列に格納してある)
$insert_array = array('color'=>'red','num'=>6);

//==================================================
// *フィールド名とそれに対応するデータをバインドする。
//==================================================

//==================================================
// *バインド:方法1
//==================================================

foreach($insert_array as $field => $value){

 //確認処理
 //echo $field.'<br/>';
 //echo $value.'<br/>';

 //バインド(foreachで、1つずつバインドしていく)
 $stmt->bindParam(':'.$field,$value);

}

//==================================================
// *バインド:方法2(↓こちらだと上手くいく。)
//==================================================

/*
$stmt->bindParam(':'.'color',$insert_array['color']);
$stmt->bindParam(':'.'num',$insert_array['num']);
*/


//==================================================
// *バインド後、「$stmt->execute();」した結果
// *DBにインサートされたものをprint_r()にて確認
//==================================================

★方法1

Array
(
[id] => 15
[color] => 6
[num] => 6
)

//---------------------------
★方法2

Array
(
[id] => 16
[color] => red
[num] => 6
)

//---------------------------

★方法1の結果の、「 [color] => 6」って一体…?!

方法2のように、同じバインド処理を手書きで繰り返す分にはうまくいくのですが、
方法1のようなforeachでまとめて処理するやり方だとうまくいきません。
方法1の問題箇所をどなたか教えて下さい。
宜しくお願い致します。

いつもお世話になっております。
PHP5.2.5
---------------------------
DB(フィールドは以下3項目)
・id(primary key)
・color
・num
---------------------------

//DBにインサートするデータ群(配列に格納してある)
$insert_array = array('color'=>'red','num'=>6);

//==================================================
// *フィールド名とそれに対応するデータをバインドする。
//==================================================

//=====================================...続きを読む

Aベストアンサー

bindParamでバインドされるのは変数の値ではなく変数です。また、マニュアルから引用すると
「変数は参照としてバインドされ、PDOStatement::execute() がコールされたときのみ評価されます。」
なので、foreach内での
>$stmt->bindParam(':'.$field,$value);
は、言ってみれば$valueの位置を記憶しているにすぎません。
で、execute実行時に初めて中身の値を取りに行き、そこに入っているもの(要するにforeachの最後の値)をセットしてしまっています。

参考URL:http://www.php.net/manual/ja/pdostatement.bindparam.php

QINSERT文でサブクエリ

SQLServer初心者です。
使用DBはSQLServer2005です。

SQLServerではInsert文でサブクエリは使用できませんか?

例)
INSERT INTO TmpA (a,b) VALUES (
1,(SELECT TmpB.b FROM TmpB))

を実行しましたところ、
『メッセージ 1046、レベル 15、状態 1、行 12
このコンテキストではサブクエリは許可されません。スカラ式だけが許可されます。』
とのエラーが発生しました。

何らかの工夫で実装できる方法がありましたら、
教えて下さいませんか?

Aベストアンサー

>ご回答下さった方法ですと、
>同じ形式のテーブルをそのままInsertは可能なんですね。

>今回は、固定値と別テーブルからの情報との両方を
>1つのInsert文に含めることが可能な活用方法が見つけられずに
>困っております。
→例は固定値と別テーブルからの情報を含めますから、まだ、何が
出来ないですか?

>例)
>INSERT INTO TmpA (a,b) (SELECT 1,b FROM TmpB)
→1 は 固定値よね
 b は 別テーブルからの情報
 同じ形式のテーブルには限らないです。

Qテーブルの一部の内容をまとめて、別テーブル上に更新したい

テーブル(tA)のカラム(cA cB)を入れ替えたいと思っています。
(全カラムではなく、一部分だけの入れ替えです。)

そこで、一時テーブルを作成し、
 元テーブル→一時テーブル(入れ替え部分だけ)→元テーブル
で対象となるカラムを入れ替えようと思いました。

一時テーブルに、カラムを入れ込むまでは出来たのですが、一時テーブルから、元のテーブルに入れ込むSQLがどうしてもわからず、教えていただきたく書き込みました。

行ったSQLとしては、

1)INSERT INTO TEMP(Id,Date,cA,cB) SELECT Id,Date,cA,cB FROM tA WHERE Id=XXXX;

2)UPDATE tA SET cA=(SELECT cA FROM TEMP WHERE Id=XXXX AND tA.Date = TEMP.Date ) WHERE Id=XXXX;

といった形で試してみました。

ご教授、宜しくお願いします。

Aベストアンサー

さっきのだと、1行ずつしか変更できないんで、相関サブクエリーを使った方がいいでしょう。

UPDATE tA SET cA = (SELECT cB FROM temptable WHERE id = tA.id),
SET cB = (SELECT cA FROM temptable WHERE id = tA.id)
WHERE 任意の抽出条件;

QInsert文 で 変数を使いたい

いつもお世話になっております。
現在、VB2005でSQLのプログラムを書いているのですが。
下記のInsert分に関してなのですが・・・

sCom = New SqlClient.SqlCommand(_
& "insert into janDB"(区分,コマンド種別,No) " _
& "values('A01','send',100)", scn)

としたらうまくいくのですが、
valuesの実データを変数にしたいのですが,
そういった処理はできないでしょうか?

何分初心者の上、試行錯誤しているのですがうまくいきません。
アドバイス等を頂けたら幸いです。

SQL Server 2005 Express Edition
Visual Studio 2005 Standard Edition
Windows XP Pro SP2

Aベストアンサー

sCom = New SqlClient.SqlCommand(_
& "insert into janDB"(区分,コマンド種別,No) " _
& "values('A01','send',100)", scn)

sCom = New SqlClient.SqlCommand(_
& "insert into janDB"(区分,コマンド種別,No) " _
& "values('" & <A01の変数> & "','" & <sendの変数> & "'," <100の変数> & ")", scn)
にすれば?

試験環境なくて試験できてませんが。
sComを組み立てたあとで、直接記述したものと同じ文字列を作成できればOKのはず。

QOutlook Expressまとめて保存

Outlook Expressの中のメールをまとめて保存したいのですが。

一つづつ 保存先に移動するのではなく まとめて移動したいのですが
そのまとめ方のやり方が分かりません。

何か方法あるのでしょうか 教えてください。

Aベストアンサー

 まず、新規のパソコンに今までのメールを移動したい場合。
 受信トレイを右クリック→プロバティを選択すると、保存場所がかかれて
います。この保存フォルダに大抵、送信トレイやゴミ箱といったフォルダも
ありますので、このフォルダを保存すればよいでしょう。
 新規のパソコンに移動したい場合は、新規のOEで保存場所を確認し、その場所に
先程バックアップしたフォルダを移動させれば終了です。

 次に、単純にメールをまとめて保存したい場合。
私はこれを使用しています→「RenEml」
(フリーソフト・Win98、Me用)

参考URL:http://www.vector.co.jp/soft/win95/net/se202206.html

QINSERT文で発行したオートナンバーを取得したい

いつも勉強させて頂いています。SQL文について困っています。

-環境-
.NET FreamWork2.0
VB SQLSERVEREXPRESS

-SQL-

インサート時にオートナンバーを取得してデータを追加しているのですが、追加後にオートナンバーの値を戻す(取得する)ことは可能でしょうか?SQL文は下記です。

INSERT INTO マスタテーブル VALUES((SELECT MAX(オートナンバー) + 1 FROM マスタテーブル), 商品名・・・・

上記で登録されたオートナンバーを別テーブルに格納したい為です、リアルタイムで処理しますので、発行したオートナンバーが即時に必要です。
テーブル更新時に 
Dim Ret_Table As DataTable = AS_Cmd.ExecuteScalar()
とするとテーブル内容がNothingになります。

他の方法も御座いましたら教えて頂けると幸いです。宜しくお願い致します。

Aベストアンサー

実行直後のIDENTITYはSCOPE_IDENTITY()で取得できますので、
以下の2クエリをまとめて渡せば番号を受け取れると思います。
strSQL =
"INSERT INTO マスタテーブル VALUES((SELECT MAX(オートナンバー) + 1 FROM マスタテーブル), 商品名・・・・;SELECT SCOPE_IDENTITY();"

Qテキストファイルの中身をまとめて削除したい

テキストファイルの中身をまとめて削除したいのですが
例えばa01.txtからz01.txtまである場合
fopen'w'でまとめて削除する場合、どう記述するのでしょうか?

ご教示お願い致します。

Aベストアンサー

ごく普通にループさせます。

foreach (range('a', 'z') as $alpha) {
fclose(fopen("{$alpha}01.txt", 'w'));
}

なお、同時アクセスを考慮してファイルロックを行う場合は

foreach (range('a', 'z') as $alpha) {
$fp = fopen("{$alpha}01.txt", 'a');
flock($fp, LOCK_EX);
ftruncate($fp, 0);
flock($fp, LOCK_UN);
fclose($fp);
}

と書きましょう。

ちなみにfcloseは書く人が多いので "何となく" 書いていますが、書かなくても勝手にメモリ解放されるので必ずしも必要ではありません。C言語の場合は書かない場合「プログラム終了時」に解放される仕様となっているので書く方が望ましいのですが、PHPの場合は変数のガベージコレクションと同時にリソースのガベージコレクションも行われるので不要です。

Q副問合せをいれたINSERT文で、問合せ結果が無い場合

副問合せをいれたINSERT文で、問合せ結果が無い場合

環境はSQL Server2005です。

テーブルA,テーブルBが存在し、テーブルAにレコード追加する際に一部をテーブルBから抽出して、
INSERTしようとしています。

[SQL文]
 INSERT INTO テーブルA(フィールド1, フィールド2, フィールド3,フィールド4・・・)
  SELECT 'AAA', 'BBB' ,B.フィールド3, B.フィールド4 ・・・
  FROM テーブルB B WHERE ~

この場合、テーブルBにWHEREで指定した条件のレコードが存在しない場合はINSERTされなくなってしまいます。
存在しない場合は、該当のフィールドにはNULLをいれたいのですが、テーブルBからの結果が存在しない場合でも
テーブルAにINSERTする方法はありますか?

Aベストアンサー

今回のやつは、
固定値をAとして、インラインテーブルを作るといういみなので、
そこにテーブルBの列を書いたらエラーになりますね。
なら、

select A.*,B.COLC COLC, B.COLD COLD, =======>>★
from
(
select 'AAA' COLA, 'BBB' COLB
) A
left join TABLEB B on B.コード・・・・

では?
「どのテーブルとどのテーブルを外部結合したいか」
をイメージするといいかと思います。

Qスキル上げはまとめて?

素材はまとめて合成派?

1体ずつでも合成する派?

Aベストアンサー

どちらも期待値は同じと信じたいですが、万が一成功確率が変動するなら、まとめて合成した場合に下がると思います。高速周回している廃課金者はまとめて合成が多いので。
なので自分は一体づつでも合成してます。

Q複雑なSELECT文について

ID 名前 日付 点数 合否
----------------------------------------------------------

1 太郎 4/1 80 合格
2 太郎 4/2 90 合格
3 太郎 4/3 100 合格
4 花子 4/5 20 不合格
5 太郎 4/5 30 不合格
6 花子 4/6 100 不合格
7 太郎 4/7 40 不合格
8 花子 4/7 100 合格

上のようなテーブルから、太郎と花子が最初に合格するまでの
実施回数を取得するようなSELECT文は可能でしょうか?
結果として望むのは以下になります。

ID 名前 合格までの実施回数
---------------------------------------------------------------
1 太郎 1
2 花子 3

どなたかご教授願います。

Aベストアンサー

select 名前,count(*) from test as A
where A.日付 <= (select min(B.日付) from test as B where B.名前 = A.名前 and B.合否= '合格')
group by 名前;

な感じ?

SQLServerの環境が無いので、動作確認していません。


人気Q&Aランキング