プログラミングと日々思ったことなど

ブログ名通りです。仕事でプログラミングをはじめました。

ストアドプロシージャからストアドプロシージャを呼び出す【SqlServer】

昨日は念願の(!)カーソルのあるストアドプロシージャの作成を行いました。
まだ未完成ですが、復習でコードを載せます。

USE testdb
GO

SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO

CREATE PROCEDURE testpro(
		@aaa VARCHAR(20)
		)
AS
	DECLARE @returnval int --戻り値
	DECLARE @SQL NVARCHAR(4000)
	DECLARE @lMsg(4000)

	--カーソルデータ格納変数
	DECLARE @kakunou VARCHAR(1000)

BEGIN
	BEGIN TRY
	SET @returnval = 1

	SET @lMsg = @aaa + 'のデータ移行を開始しました。'
	EXECUTE testbl '案内','',@aaa,0,@lMsg

	IF (@aaa IS NULL OR @aaa = '')
	BEGIN
		SET @lMsg = '引数が未設定'
		EXECUTE testbl 'エラー','',@aaa,1,@lMsg
		RETURN @returnval
	END

	--カーソル作成
	SET @SQL = 'DECLARE abc CUSOR FOR SELECT tosi From'
				+'(SELECT name From' + @aaa + ' .table) AS T'

	EXECUTE sp_executesql @SQL

	--カーソルオープン
	OPEN abc
	--abcから変数@kakunouに1件ずつデータを格納する
	FETCH NEXT FROM abc INTO @kakunou

	IF @@FETCH_STATUS <> 0
	BEGIN
		SET @lMsg ='テーブルが存在しません'
		EXECUTE testbl 'エラー','',@aaa,1,@lMsg
		RETURN @returnval
	END

	While @@FETCH_STATUS = 0
	BEGIN
		EXECUTE abcInsertpro @aaa,@kakunou
    #処理の後、FETCHで再び1行データを取得する
		FETCH NEXT From abc INTO @kakunou
	END

  CLOSE abc
       DEALLOCATE abc

        SET @lMsg = @aaa + 'のデータ移行を完了しました。'
	EXECUTE testbl '案内','',@aaa,0,@lMsg


	END TRY

	--例外エラー
	BEGIN CATCH
		SET @lMsg = '[ERROR_NUMBER]' + @SQL
		EXECUTE testbl 'エラー','',@aaa,1,@lMsg
		RETURN @returnval
	END CATCH
	SET returnval = 0
END

GO

@@FETCH_STATUSの設定がおかしいかな?
自分でもわかるように、土日で説明を書くつもりです。

...6月4日追加
データ以降が完了したことを判断する処理は、書かなくても良い(正常に処理されれば、下に流れていくため)


ここから雑記

金曜日に、映画「美しい星」という三島由紀夫原作のSF映画を見ました。
人間は人間自身を自然の一部だと思っていない...だったかな?
個人的にとても面白かったです。

MySqlでストアドプロシージャを作成する

MySqlではSqlServerで使えていたRETURNが使えないので、HANDLERを使用する...みたいです。
未完成なのですが、忘れないようにメモ。

.....6月1日
HANDLERでは、希望の処理ができませんでした。

# workbenchで作成しているので、DELIMITERは省略して良い

CREATE PROCEDURE SP_MGR_SetLog
(
		IN aa VARCHAR(3)
		IN bb VARCHAR(10)
		OUT returnmsg VARCHAR(2000)
         OUT returnval int;
)
BEGIN

	DECLARE ssql VARCHAR(4000);

	#このハンドラがおかしい?
        #61日...ハンドラは今回使用しません。
	/*DECLARE con1 CONDITION for 1;
	DECLARE EXIT HANDLER for con1
	BEGIN
		SET returnmsg = '引数エラー';
	END;

	#例外エラー(インサート失敗した時)
	DECLARE EXIT HANDLER for SQLEXCEPTION
	BEGIN
		SET returnmsg = 'インサート失敗';
	END;*/

	IF (aa IS NULL) OR (aa = '')
	THEN
                #エラーを検知するのはここで行なっている
                #が、条件のIF文が以下に続くので、エラーは書き換えられてしまう
		SET returnmsg = '必須項目が未設定';
                SET returnval = 0;
	END IF;

	IF NOT bb ='おはよう' OR bb='こんにちは'
	THEN
		SET returnmsg = '項目の値が間違ってます。';
                SET returnval = 0;
	END IF;

	#その後、処理が多々...省略

        #INSERTはこのように準備してから実行させる。
	PREPARE ssql From 'INSERT INTO aa VALUES(?,?);'
		SET @a = a;
		SET @b = b;
	EXECUTE ssql USING @a,@b;

END;

INSERT文でコーテーションを考えなくてもよかったので、とても楽になりました。
このprepareは、インジェクションの考え方と同じ(これはよくわからないので、調べる)だそうです。

また、MySqlでは文字列と認識されると緑色になります。
・・SqlServerでは、文字列が赤色で、変数が緑色になっていたような・・これも確認します。


少しずつわかってきました。
今度はカーソルのあるストアドを作りたいなー。
楽しみです。

SqlServerでストアドプロシージャを作成する

ストアドプロシージャを作成したので、復習です。
このストアドプロシージャはインサート処理と、エラー表示を行います(多分・・・)。

USE [AA_DB]
GO

CREATE PROCEDURE [aa]
	(@log VARCHAR(5)				--パラメータを宣言
	,@name VARCHAR(30)
	,@suuzi  int				
	,@MsgReturn VARCHAR(2000) OUTPUT
	)

AS
	DECLARE @SQL NVARCHAR(4000)		--変数を宣言
	DECLARE @DefMsg VARCHAR(1000)
	DECLARE @ReturnVal int
	DECLARE @Err INTEGER

BEGIN
	SET @DefMsg='ストアドエラー'
	SET @ReturnVal = 2                     --失敗したら2がかえってくる

	/** 引数チェック **/
	IF(@log IS NULL OR @log = '' OR
		@name IS NULL OR @name = '' OR
		)
	BEGIN
		SET @MsgReturn = @DefMsg + '引数未設定'
		RETURN @ReturnVal
	END

	BEGIN TRY

	SET @SQL = N'INSERT INTO [AA] VALUES
		('''+ @log + ''''			--引数を文字列として認識するための、コーテーション
		+N',''' + @name +''''	--NはUnicodeとして処理させるためにつける
		+N',' + CAST(@suuizi AS VARCHAR)+N''')' --数値を文字列と認識させるために、キャストする。
	EXECUTE @Err = sp_executeaql @SQL --@Errに、インサートが失敗したのか成功したのかわかる値が入っている
	IF @Err <> 0
	BEGIN
		SET @MsgReturn = @DefMsg + 'インサート失敗'
		RETURN @ReturnVal
	END

	SET @ReturnVal = 0

	END TRY
	BEGIN CAHTCH
		SET @MsgReturn = @DefMsg + '[ERROR_NUMBER]'+@SQL
		RETURN @ReturnVal
	END CAHTCH
END

GO

変数とパラメータが、少しだけ混乱する時があるので気をつけます。

キャストについて【C#】

昨日は、キャストが思いつかず悔しい思いをしました。
忘れないように、コードを残します。

問題のキャスト処理を書く前に、条件分岐の処理も復習します。

ublic void kakunin_Click(C1.WPF.DataGrid.DataGridEventArgs e)
{
	this.IsRegistEnable = true; //登録ボタンにバインドさせている。
	bool isError = false;
	string name = e.Column.Name;

	DataRowView dv = (DataRowView)e.Row.DataItem;
	dv.Row.ClearErrors();
	e.Row.Errors.Clear();

	DataGridRowError  dgreroor = new DataGridRowError();

	//必須入力チェック
	//sentakuメソッドで、DataGridの項目の選択か必須かを判断している。
	if(!this.sentaku(name))
	{
		//DtoCheckメソッドにて、エラーの出ているメソッドを判別。
		isError = this.DtoCheck(e.Column,dv.Row)
		if(isError)
		{
			dgreroor.ColumnNames.Add(name);
			dgreroor.Message="必須入力エラー";
			e.Row.Errors.Add(dgreroor);
			dv.Row.SetColumnError(name,"[項目:"+ name + "]を入力してください");
		}
	}

	//switchで、入力した値が項目に合う値なのか(数字なのか、半角英数なのかなど)判別。
	switch(name)
	{
		case"aa":
		case"bb":
		if(!InputAuxiliaryUtil.IsNumeric(dv[name].Tostring()))
		{
			dgreroor.ColumnNames.Add(name);
			dgreroor.Message = "入力エラー"
			e.Row.Errors.Add(dgreroor);
			dgreroor.Row.SetColumnError(name,"数字を入力してください");
		
			isError = true;
		}
		break;

		case"cc":
		if(....)
		{
			//略...
		}
		break;

	}

	//エラーがあった場合、登録ボタンを使えないようにする。
	if(isError)
	{
		e.Cansel = true;
		this.IsRegistEnable = false;

	}

}

SetColumnErrorというのが、DataGridの問題セルに出現するエラーです。(見た目はフキダシみたいなものです)
InputAuxiliaryUtil.IsNumericというメソッドで、判定させます。

#region 文字チェック

public static bool IsNumeric(string value)
{
	if(null == value)
	{
		return true;
	}
	if(string.IsNullOrEmpty(value.Trim()))
	{
		return true;
	}
	if(!Regex.IsMach(value.Trim(),REGEX_Numeric))
	{
		return false;
	}

	return true;
}

#endregion


また、この文字チェック内で使われているREGEX_Numericは、辿っていくと正規表現にたどり着きます。

//こんなの
//これは例なので、正規表現は半角英数字の判別設定です。

private static string REGEX_Numeric = "^[-_/0-9a-zA-Z]+$";

DataGridは表示されるだけなので、入力文字についてはこのように自分で書いていかなければならないのです。

次はキャストです。

else if(e.Column.Name == "ff")
{
	dv[name] = dv[name].Tostring().PadLeft(2,'0')
}

これです。
間違って書いてコードは以下になります。

//このコードは間違っています。
else if(e.Column.Name == "ff")
{
	var i = (DataRowView)e.Row.DataItem;
	i.PadLeft(7,'0')

	string col = e.Column.Tostring();
	col.PadLeft(7,'0')
}

iやcolには値が入っていますが、オブジェクトを知りません。
ただ値を入れているという処理をしているだけなので、DataGridには何も変化がない、です。

値が入っているプロパティを見つけ、キャストして使用できるようにするまでが難しいな・・・。

オブジェクト指向プログラミングについて

オブジェクト指向プログラミング(Object Oriented Programming = OOP)とは
プログラムを書く人自身が、プログラムを把握しきれなくなることを防ぐためのプログラム設計方法です。

このプログラム設計方法の特徴は、3つあります。
カプセル化
 フィールドへの読み書きやメソッドの呼び出しを制限します。
 制限することによって、重要(プログラム中で、書き換えてはいけないもの)な情報(フィールド)や操作(メソッド)を保護することができます。
 これは誤りの起きにくいプログラム、つまりはクラスを設計する時に役立ちます。

②継承
 新しいクラスを作るとき、そのクラスがすでに作られているクラスと類似している部分がある場合があります。

 例)
 車の設計が書かれたクラスがもともとあって、新しくスポーツカーの設計を書く必要がある。

 そのときに、すでに作られたクラスのメンバ変数・メソッドを受け継ぐ仕組みです。
 もともとあったクラスは、基本クラスや親クラスと言います。
 新しく作るクラスを、派生クラスや子クラスと言います。

③多様性(ポリモーフィズム
 メソッドに多様な振る舞いをさせることです。
 多様な振る舞いをさせるメソッドは、抽象的になります。
 抽象的になるというのは、簡単に言えば(おそらく)「5m動く」を「動く」にする、ということです。
 「動く」にすると、各クラス内で何メートル動くのかを決めることができます。
 そうすることによって、効率的にプログラム開発を行えます。


今はこれが限界です。
1年後、詳しく書けるように頑張ろう。

DataGridの必須入力チェック【WPF】

今週も頑張りました。
オブジェクト指向の前に、昨日の復習を記事にします(難しかった・・・)

昨日会社で教えてもらったのは、DataGridの必須入力チェックです。
DataGridに表示されているデータを編集したとき、必須項目が未入力だった際にその項目の枠が赤くなり、かつ、「必須エラーです」とメッセージが表示される処理です。

この必須入力チェックは、登録ボタン(コマンド)を押すと処理されます。
まずコマンド内でどの項目がエラーなのか判別処理・表示文章設定します。
その後ViewのTargetUpdated内で、実際にエラーメッセージが表示されるように処理していきました。


DataGridViewModel.cs
//このbtnRegist_Clickは、登録ボタンのコマンドに仕込んであるもの。
public void btnRegist_Click()
{
	bool isError = this.DtoCheck();

	//チェック処理
	if(isError)
	{
		this.RaisePropertyChanged("Views");
		return;
	}

	if(MessageBoxResult.Yes == System.Windows.MessageBox.Show("変更項目を登録しますか?","登録変更確認",MessageBoxButton.YesNo))
	{
		testDataGridModel model =
			new testDataGridModel(this.Dto);
		model.InsertData(updateflag);

		//登録変更が成功したか

		isSuccess = model.IsSuccess;
		if(isSuccess)
		{
			//略...登録更新の成功・失敗メッセージ処理...
		}
	}
}

private bool DtoCheck()
{
	bool isError = false;
	//エラーフィールドのクリア
	//ここで中身をクリアにしておかないと、連続処理したときに前の処理が残ってしまう。
	foreach(DataRow dr in this.Dto.testData.Rows)
	{
		dr["ERROR_FIELD"] = string.Empty;
		dr["ERROR_MSG"] = string.Empty;
		dr.ClearErrors();
	}

        //foreachの中にforeachでRowとColumnを1つずつ取り出している(結果1つのCellずつ処理ができる)
	foreach(DataRow dr in this.dto.testData.Rows)
	{
		//testDataの列を一列ずつ読み込む
		foreach(DtaColumn dc in this.dto.testData.Columns)
		{
			//必須か選択項目か確認 
			if(sentakuFields(dc.ColumnName))
			{
				continue;
			}

			if((dr[dc.ColumnName] == null) || (dr[dc.ColumnName].Tostring() == string.Empty))
			{
				//ここで空のERROR_FIELD行の中身を作っている
				if(dr["ERROR_FIELD"].Tostring() == string.Empty)
				{
					dr["ERROR_FIELD"] = dc.ColumnName;
				}
				else
				{
					dr["ERROR_FIELD"] += " " + dc.ColumnName; //区切り指定
				}

				dr["ERROR_MSG"] = dc.ColumnName + "で必須エラーです。¥r¥n";
				dr.RowError = dc.ColumnName + "で必須エラーです。¥r¥n";
				dr.SetColumnError(dc,dr["ERROR_MSG"].Tostring());

				isError = true;
			}
		}
	}
	return isError;

}

//選択項目を、ここで抜き出し
private bool sentakuFields(string strFieldName)
{
	bool isSentaku = false;

	switch(strFieldName)
	{
		case "aa":
		case "bb":

		isSentaku = true;
		break;
	}

	return isSentaku;
}
	
DataGrid.cs(View)
public test()

{
	InitializeComponent();

	//画面が表示されたときに、色々なデータを取得する処理
	ThisForm = ExPageParamsUtil.GetPageParams("TForms") as ExMenuWindow;
	//...略

	this.DataContext =
		new testViewModel(CommonTbls,exWin,cmbAuth);
	this.testDataGrid.TargetUpdated += 
	new EventHandler<DataTransferEventArgs>(testDataGrid_TargetUpdated);

}

private void testDataGrid_TargetUpdated(object sender,DataTransferEventArgs e)
{
	//エラー内容のクリア
	for(int idx = 0;idx < this.testDataGrid.Rows.Count;idx++)
	{
		this.testDataGrid.Rows[idx].Errors.Clear();
	}

	BindingListCollectionView chkView =
		this.testDataGrid.ItemSorce as BindingListCollectionView;

	//フィルタかける前のすべてのデータを取得する。
	DataTable dt = ((DataView)chkView.SorceCollection).ToTable().Copy();

	//フィルタ圧縮かける <>''←これは値が入っているかどうかを判断することができる。
        //RowFiletrで圧縮をかけると、番号がおかしくなるので注意。
	DataRow[] drs = dt.Select("ERROR_FIELD<>''");

	foreach(DataRow dr in drs)
	{
		//エラー項目及びエラー内容の取得
		DataGridRowError dgErr = new DataGridRowError();
		string[] errField = dr["ERROR_FIELD"].Tostring().Split('');

		foreach(string fld in errField)
		{
			dgErr.ColumnsNames.Add(fld);
			dgErr.Message = dr.GetColumnError(fld);

			//エラーオブジェクトを対象データグリッド行にセットする

			//エラー行番号の取得
			int intRowIdx = dt.Rows.IndexOf(dr);
			//データグリッドエラー対象行にエラーセット
			this.testDataGrid.Rows[intRowIdx].Errors.Add(dgErr);
		}
	}
}

以上なのですが、このコードだとセレクターにメッセージが表示されません。
どうすれば良いのか、調べてみようと思います。


-----ここから雑記

教えてもらっていたのに、復習してみるとわかっていないところが出てきました。
書いてみるとわかっていないところがわかるので、これからもコードを書いていこうと思います。

あと、色々コードを教えてもらっていて思っていたことを、昨日やっと聞くことができました。
foreachの中に、foreachやifが入っているのが変な感じがしていたのですが、それは処理が多くなりそうであれば、別のメソッドへ飛ばしても良いそうです。

些細なことも答えてくれる先輩方が、会社にたくさんいるのがとてもありがたいです。

そういやボーナスは出るのかなー。
出たらマイコンキットか、Windowsを購入したいのです・・・うーん難しいかな・・・。

Listでコンボボックスの項目を追加する(グリッドコントロール)

朝早く起きてしまったので、昨日の復習を書きます。

昨日はC1.WPF.DataGridのテキストボックス列(デフォルト)からコンボボックス列へ変更する処理をしました。
以下はそのコードです。

 private void c1DataGrid_AutoGeneratingColumns(object sender, C1.WPF.DataGrid.DataGridAutoGeneratingColumnsEventArgs e)
    {
        if(e.Property.Name == "aaaa")
        {
        //コンボボックス設定
        var cmbClmn = new C1.WPF.DataGrid.DataGridComboBoxColumn(e.Property);
        cmbClmn.DisplayMemberPath = "a.NAME";
        cmbClmn.SelectedValuePath = "a.CODE";

        DataView dv = new DataView(this.aTbls.Tables["a.CODE"]);
        dv.RowFilter = "a.CODE.ccc = '00'";

        DataTable dtCode = dv.ToTable();

        List<Model> models = new List<Model>();
        foreach(DataRow dr in dtCode.Rows)
        {
            Model data = new Model();
            data.a.NAME = dr["a.CODE"].ToString() + " " + dr["a.NEME"].ToString();
            data.a.CODE = dr["a.CODE"].ToString();
            models.Add(data);
        }

        Model data1 = new Model();
        data1.a.NAME = "00 その他";
        data1.a.CODE = "00";
        models.Add(data1);

            cmbClmn.ItemSource = models;
        }

        if(e.Property.Name == "bbbb")
        {
            var cmbClmn = new C1.WPF.DataGrid.DataGridComboBoxColumn(e.Property);
            cmbClmn.DisplayMemberPath = "a.NAME";
            cmbClmn.SelectedValuePath = "a.CODE";

            List<Model> models = new List<Model>();

            Model data1 = new Model();
            data1.a.NAME = "00 選択";
            data1.a.CODE = "00";
            models.Add(data1);

            cmbClmn.ItemSource = models;

        }
  

Listは別クラスにてModelの箱(a.NAMEと、a.CODEのプロパティと、WPFなのでプロパティチェンジを記述)を作成しています。

if文が2つあるのですが、Gridのヘッダー名で違った項目を表示することができるようにするためです。
1つ目のif文の中には、ループ文があります。
これは、もともとデータベースにあった項目を使用しているためです。
直接データベースからは持ってこれないので、
DataView dv = new DataView(this.aTbls.Tables["a.CODE"]); ←ここ
にて、DataViewの中身を指定したのちにフィルターをかけて使用する項目を指定しています。

2つ目はもともとある項目を使用していないので、新しい項目だけを追加しています。


また、デフォルトのテキストボックスだった時、各テキストボックスのデータは数字で表示されるようになっていました。
それはDtoから取得してきたデータが、数字であるためです。
それをこの処理をすることによって、数字の意味に適応した文字に変更しています。
変更したのは、数字だと何を意味しているのかわかりづらいからです。

以上です。

昨日はまだよくわかっていないListと、DataViewが作業で出てきました。
これで少し整理できてよかったです。