デバッグでNOエラー、実行でエラー(DBにSQL)

[戻る]
DK 2003/10/20(月) 10:22:32
++環境++
  VC6.0
  WinNT
  Access2000(DBのみ)
  ODBC接続
  MFCサポートのコンソールアプリで作成

題名のとおり、デバッグ(F5)でエラーが出なくて、実行時(Ctrl+F5)にエラーが発生します。

エラーというのは、
「インデックス、主キー、またはリレーションシップで値が重複しているので、テーブルを変更
できませんでした。重複する値を変更するか、インデックスを削除してください。または。重複
する値をしようできるように再定義してください。」です。
そこでEnterキーを押すと次は「接続は使用中です。」のエラーが発生します。
ともにエラーのタイトルはsqlで、下のコードの部分で出ています。

データベース構造は
REF_TIME(主キー) 日付型(COleDateTimeに格納)
PICS_NAME    テキスト型(CString)
REM_NAME     テキスト型(CString)
REM_WEIGHT    数値型[単精度](float)
となっておりまして、

「主キー」でエラーになるなら、と思い、外してみたところ、
238件ほど同じデータが追加され、239件目で値が変わっておりました。
値を変更する前に、データベースに接続に行っている、と言うことでしょうか?
だとすれば、どこかのコードで処理に時間がかかっているのでしょうか?

実際のコードですが、

実行する.CPPファイル
// DB2.cpp : コンソール アプリケーション用のエントリ ポイントの定義
//

#include "stdafx.h"
#include "DB2.h"
#include "test.h"

#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif

/////////////////////////////////////////////////////////////////////////////
// 唯一のアプリケーション オブジェクト

CWinApp theApp;
CDatabase db;

using namespace std;

void SQLrun(COleDateTime date,CString gazo,CString zan,CString wei);
void jikan();
void _tmain(int argc, TCHAR* argv[], TCHAR* envp[])
{
    int nRetCode = 0;
    // MFC の初期化および初期化失敗時のエラーの出力
    if (!AfxWinInit(::GetModuleHandle(NULL), NULL, ::GetCommandLine(), 0))
    {
        // TODO: 必要に応じてエラー コードを変更してください。
        cerr << _T("Fatal Error: MFC initialization failed") << endl;
        nRetCode = 1;
    }
    else
    {
        // TODO: この位置にアプリケーションの動作を記述してください。
        jikan();        
    }
}
void jikan (){
    Ctest ct;
    float wei;
    CString flg;
    CString gazo,zan;
    COleDateTimeSpan nowtime;
    try{
        ct.Open(AFX_DB_USE_DEFAULT_TYPE, ct.GetDefaultSQL() );
    }catch( CDBException* e )    // 例外エラー発生
    {
        // エラーの処理
        ::MessageBox(NULL, e->m_strError, "DB", MB_OK);
         e->Delete();
    }
    do{
        ct.Requery();
        try{
            ct.MoveLast();
            COleDateTime lasttime(ct.m_REF_TIME);
            CString weig;
            nowtime.SetDateTimeSpan(0,1,0,0);
            lasttime += nowtime;
            gazo=lasttime.Format("G1%Y%m%d%H.jpg");
            zan=lasttime.Format("Z1%Y%m%d%H.csv");
            wei=ct.m_REM_WEIGHT +0.1;
            weig.Format("%0.2f",wei);
            SQLrun(lasttime,gazo,zan,weig);
            flg=lasttime.Format("%m"); 
        }catch( CDBException* e )    // 例外エラー発生
        {
            // エラーの処理
            ::MessageBox(NULL, e->m_strError, "set", MB_OK);
            e->Delete();
        }
    }while(flg!="10");
    ct.Close();
}
void SQLrun(COleDateTime date,CString gazo,CString zan,CString weight)
{
    try{
        COleDateTime ole(date);
        CString rt=ole.Format("%Y/%m/%d %H:%M");
        
        db.Open("ODBC;DSN=pitdata");
        db.ExecuteSQL("INSERT INTO  Gpit1 ( REF_TIME, PICS_NAME, 
REM_NAME, REM_WEIGHT ) VALUES ( #" + rt + "# ,'" + gazo + "','" + zan 
+ "',"+weight+")");
        db.Close();
    }catch( CDBException* e )    // 例外エラー発生
    {
        // エラーの処理
        ::MessageBox(NULL, e->m_strError, "sql", MB_OK);←ここでエラー
        e->Delete();
    }
}

…コードが汚くて申し訳ないです。
データベース自体はローカルにあるので、時間はあまりかかっていないと思います。

データベースを扱うとぶつかる現象なのでしょうか?
対策等がありましたら、御教授下さい。

Euglena 2003/10/20(月) 15:00:17
質問からはずれた回答だと思いますが、

エラーメッセージと質問本文を見る限りでは、
キーとしているREF_TIMEカラムが重複禁止になっていて、
そこに同じ値がコードの「do〜while」によって追加されているようです。

Ctestクラスがどうなっているか判らないのでなんともいえませんが、
同一値が登録される可能性があり、かつ、テーブル定義を変えられるなら
REF_TIMEの重複禁止をはずして別カラムを主キーとして
追加したほうがいいかもしれません。

同一値が登録されるわけがない、というならば
その値の元となっている「ct.Requery()」「ct.m_REF_TIME」あたりを
確認したほうがよいかもしれません。

「接続は使用中です。」のメッセージは、
db.ExecuteSQL()で例外が発生したときにdb.Close()されていないからでしょう。
「try〜catch」を適切にかければ回避できると思います。

DK 2003/10/20(月) 15:18:10
回答ありがとうございます。

----リクエリー後--------
>ct.MoveLast();
>COleDateTime lasttime(ct.m_REF_TIME);
   最終行(さっき追加した行)から、m_REF_TIMEゲット
>CString weig;
>nowtime.SetDateTimeSpan(0,1,0,0);
   1時間を格納
>lasttime += nowtime;
   それを足します

ここで、同一の値ではなくしているはずなんですが…

情報不足ですいません。
test.hとtest.cppのコードです。
(いちいちコードを見るのも面倒かと思うのでコードの下にまとめます)
−−−−−−−−−−−以下test.hコード−−−−−−−−−−−−−−−−−−−−−−−
#if !defined(AFX_TEST_H__134D4271_4A2E_4C75_AB66_5AE3D5C20BBE__INCLUDED_)
#define AFX_TEST_H__134D4271_4A2E_4C75_AB66_5AE3D5C20BBE__INCLUDED_

#if _MSC_VER > 1000
#pragma once
#endif // _MSC_VER > 1000
// test.h : ヘッダー ファイル
//

/////////////////////////////////////////////////////////////////////////////
// Ctest レコードセット

class Ctest : public CRecordset
{
public:
    Ctest(CDatabase* pDatabase = NULL);
    DECLARE_DYNAMIC(Ctest)

// フィールド/パラメータ データ
    //{{AFX_FIELD(Ctest, CRecordset)
    COleDateTime    m_REF_TIME;
    CString    m_PICS_NAME;
    CString    m_REM_NAME;
    float    m_REM_WEIGHT;
    //}}AFX_FIELD


// オーバーライド
    // ClassWizard は仮想関数のオーバーライドを生成します。
    //{{AFX_VIRTUAL(Ctest)
    public:
    virtual CString GetDefaultConnect();    // デフォルトの接続ストリング
    virtual CString GetDefaultSQL();    // レコードセットのデフォルト SQL 
    virtual void DoFieldExchange(CFieldExchange* pFX);  // RFX サポート
    
    //}}AFX_VIRTUAL

// インプリメンテーション
#ifdef _DEBUG
    virtual void AssertValid() const;
    virtual void Dump(CDumpContext& dc) const;
#endif
};

//{{AFX_INSERT_LOCATION}}
// Microsoft Visual C++ は前行の直前に追加の宣言を挿入します。

#endif // !defined(AFX_TEST_H__134D4271_4A2E_4C75_AB66_5AE3D5C20BBE__INCLUDED_)
−−−−−−−−−−−−−−−−−test.hコード終わり−−−−−−−−−−−−−−−−

−−−−−−−−−−−−−−−−−以下test.cppコード−−−−−−−−−−−−−−−−
// test.cpp : インプリメンテーション ファイル
//

#include "stdafx.h"
#include "DB2.h"
#include "test.h"

#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif

/////////////////////////////////////////////////////////////////////////////
// Ctest

IMPLEMENT_DYNAMIC(Ctest, CRecordset)

Ctest::Ctest(CDatabase* pdb)
    : CRecordset(pdb)
{
    //{{AFX_FIELD_INIT(Ctest)
    m_PICS_NAME = _T("");
    m_REM_NAME = _T("");
    m_REM_WEIGHT = 0.2f;
    m_REF_TIME = 0L;
    m_nFields = 4;
    //}}AFX_FIELD_INIT
    m_nDefaultType = snapshot;
}


CString Ctest::GetDefaultConnect()
{
    return _T("ODBC;DSN=pitdata");
}

CString Ctest::GetDefaultSQL()
{
    return _T("[Gpit1]");
}

void Ctest::DoFieldExchange(CFieldExchange* pFX)
{
    //{{AFX_FIELD_MAP(Ctest)
    pFX->SetFieldType(CFieldExchange::outputColumn);
    RFX_Date(pFX, _T("[REF_TIME]"), m_REF_TIME);
    RFX_Text(pFX, _T("[PICS_NAME]"), m_PICS_NAME);
    RFX_Text(pFX, _T("[REM_NAME]"), m_REM_NAME);
    RFX_Single(pFX, _T("[REM_WEIGHT]"), m_REM_WEIGHT);
    //}}AFX_FIELD_MAP
}

/////////////////////////////////////////////////////////////////////////////
// Ctest 診断

#ifdef _DEBUG
void Ctest::AssertValid() const
{
    CRecordset::AssertValid();
}

void Ctest::Dump(CDumpContext& dc) const
{
    CRecordset::Dump(dc);
}
#endif //_DEBUG
−−−−−−−−−−−−−−−−test.cppコード終わり−−−−−−−−−−−−−−−−

testクラスは、ウィザードで作成しております。
基本クラスをCRecordsetとして作成し、本当は、「CTime」の場所を「COleDateTime」に変更
しました。
あとは特に変更した部分はないと思います。

Euglena 2003/10/20(月) 16:07:39
CRecordsetクラスをこういうふうに使ったことがないので・・・

ただ、ちょっと気になったところが・・

>>ct.MoveLast();
>>COleDateTime lasttime(ct.m_REF_TIME);
>   最終行(さっき追加した行)から、m_REF_TIMEゲット

ct.Open()ではデータのソート順を指定していないので、
最終レコード=最後に追加したレコード
にならないときがあります。(レコード更新・削除などを行ったテーブルに対して)

ct.Open( AFX_DB_USE_DEFAULT_TYPE, "select * from Gpit1 order by REF_TIME" );

に変更してどうでしょうか?

あと、最後に書き込む値(lasttime)が判っているならば、
最初に最大のREF_TIMEを取得するだけにして、
Requery()で取得することをぜずにlasttimeを増加させて
レコード追加してやればよいような気もします。

DK 2003/10/20(月) 18:26:25
>ct.Open()ではデータのソート順を指定していないので、
>最終レコード=最後に追加したレコード
>にならないときがあります。(レコード更新・削除などを行ったテーブルに対して)

ソートしてみました。
以前に耳にしたことがあったので、自分もこれかと思ったのですが、変化はありませんでした。

ついでに、REQUERYで並びがおかしくなるかと思い、データベースの方も、並べ替え指定して保
存しましたが、変化ありませんでした。

>あと、最後に書き込む値(lasttime)が判っているならば、
>最初に最大のREF_TIMEを取得するだけにして、
>Requery()で取得することをぜずにlasttimeを増加させて
>レコード追加してやればよいような気もします。

おっしゃる通りですが、
このプログラムは、途中で出てくるクラス名を見るとわかるように、テスト段階です。
レコードセットを使ったデータの取得
SQLを使用したデータの構築
を兼ねて、DBに自動でデータを増やすと言うことでやっています。

動作の結果的には、動いているようなので、
どこかの処理の遅延等を調べてみます。

Euglena 2003/10/21(火) 16:33:50
提示されているソースを試しに実行してみました。

レコードがないときの処理やループ判定変数の初期化などは置いておいて・・・

ct.Requery()を行うときに下記処理が必要となるようです。

ct.m_strFilter = _T( "" ); ← where節の設定
ct.m_strSort = _T( "REF_TIME" ); ← order by節の設定
ct.Requery();

Open()で指定したソート順を保持しているわけではなかったみたいです。
私自身勉強不足でした。

Euglena 2003/10/21(火) 16:36:50
話が前後しましたが・・・(汗)

追加、削除を何度か行ったテーブルで実行させたところ、
上記ソート順を設定しなかったときには、
DKさんと同じメッセージが表示されました。

DK 2003/10/21(火) 16:59:46
こちらもいろいろやってみました。
主キーをはずして起動している場合は
2003/10/21 10:00
  ||
2003/10/21 10:00
2003/10/21 11:00
  ||
2003/10/21 11:00
2003/10/22 12:00
  ||
2003/10/22 12:00
("||"記号は複数件の意)
となっていて、その複数の数は徐々に減少していたので
(DBの性質上、一度接続したところは連続して接続すると接続にかかる時間は減少する)
ct.Requery();
Sleep(2000);
としました。
が2秒後にエラーが発生する状態でした^^

あと、Requeryに問題があるのかと思い、ループ内に
オープン-クローズを入れ、SQL発行毎にオープンしてみましたが、やはり、エラーがでまし
た。

そして、今やっているのは
追加した日付をバッファに格納し、
ループ内で、
IF(バッファ!=追加日付){
  SQL発行
}
をやろうとしているところです。

DK 2003/10/21(火) 17:00:46
Euglenaさんの方法も試してみようと思います。
ありがとうございます。

DK 2003/10/21(火) 17:36:34
結果報告
以前より進歩した点
  F10を押しっぱなしにしてもトラップエラーが発生しなくなった。

以前より、だめになった点
  実行を押すとASSERT系エラーが発生。
  ちなみにどこでエラーしているかと言うと、
  Recordsetを開く時にエラーを起こしているようですね、
  予想ですと、閉じる操作が間に合わずに、開く操作をしてしまっているのではないでしょう
  か?

Euglena 2003/10/22(水) 00:30:04
ACCESSを使って試したところ、最初に言われていたエラーが出ました。
先ほど私が試したのはSQL Serverだったのでエラーが出なかったのでしょう。

どうもDBの多重オープンが原因?
(ct宣言時にオープンした状態でExecuteSQLで同一DBをオープン)
ACCESSでは多重オープンで意図しない結果がでるようです。

ACCESS使用のときにはct.Open()の前にctの構築をし、
SQLrun()呼び出し前にctを破棄するようにしたところうまくいきました。

Ctest* ct;

do{
  ct = new Ctest;
  ct->Open(AFX_DB_USE_DEFAULT_TYPE,
        "select * from TBL_TEST order by REF_TIME");
  COleDateTime lasttime( 2003, 9, 28, 0, 0, 0 );
  wei = 0.0;
  if( ct->GetRecordCount() > 0 ){
    ct->MoveLast();
    lasttime = ct->m_REF_TIME;
    wei = ct->m_REM_WEIGHT + 0.1;
  }
  ct->Close();
  delete ct;

  CString weig;
    ・
    ・
  SQLrun(・・・);
    ・
} while( flg != "10" );

(例外処理およびエラー処理省略)

DK 2003/10/22(水) 10:17:37
ありがとうございます。
Delete コマンドですね。
ヘルプで見たところ、「フレームワークは CDatabase オブジェクトで delete 演算子を使っ
たときにハンドルを解放します。Close メンバ関数ではハンドルを解放しないことに注意して
ください。」
のように書いてありました。

早速、試してみます。

DK 2003/10/22(水) 10:31:24
[[解決]]
長期に渡って、御世話になりました。
ありがとうございます、解決です。

Delete 演算子というのは、
Ctest ct;
では、使えなく
Ctest *ct;
で使えるようですね?(ハンドル削除ですし…)

実は今まで、"->"の記号が分かっていなかったのですが、今回理解できました。

Delete演算子はVBAやVBで言うところの、db=Nothing(DBに割り当ててるキャッシュを空にする
(?))と同じような事ではないでしょうか?

ありがとうございました。

古いスレッドには追加発言できません。