VisualStudio2019で、データベースの使用を勉強しています。
Accessに接続して、OleDbCommandBuilderを実行したところ、コメントのようなCommandTextを取得することができました。
UPDATE文、DELETE文のCommandTextの中の(? = 1 AND 苗字 IS NULL)の意味を教えて頂けませんか?
「苗字 IS NULL」は、レコードの苗字列の値がDbNullの場合はTrueを返すという事は理解していますが、「? = 1」の意味が解りません。
詳しい方、宜しくお願い致します。
(Accessのテーブル構成)
インスタンス名 Access2010
データベース名 データベース勉強用DB
論理テーブル名 Test
物理テーブル名 INSERT文.accdb
列定義
No 論理名 物理名 データ型 Null許容 主キー
1 ID ID 長整数(8,0) No Yes
2 苗字 苗字 テキスト型(10) No
3 名前 名前 テキスト型(10) No
4 年齢 年齢 十進数(18) No
Dim oledb As String = "Microsoft.ACE.OLEDB.16.0;"
Dim acfile As String = "D:\データベース勉強用DB\INSERT文.accdb"
Dim Cn As New OleDb.OleDbConnection($"Provider={oledb} Data Source={acfile}")
Dim Da As New OleDbDataAdapter("SELECT * FROM 既存テーブル", Cn)
Dim Ds As New DataSet
Da.Fill(Ds, "Test")
Dim builder As OleDbCommandBuilder = New OleDbCommandBuilder(Da)
Dim INS_Cmd = builder.GetInsertCommand()
Dim UPD_Cmd = builder.GetUpdateCommand()
Dim DEL_Cmd = builder.GetDeleteCommand
以下のCommandTextが設定されます。
'INS_ Cmd.CommandText : INSERT INTO 既存テーブル (苗字, 名前, 年齢) VALUES (?, ?, ?)
'UPD_ Cmd.CommandText :UPDATE 既存テーブル SET 苗字 = ?, 名前 = ?, 年齢 = ?
WHERE ((ID = ?) AND
((? = 1 AND 苗字 IS NULL) OR (苗字 = ?)) AND
((? = 1 AND 名前 IS NULL) OR (名前 = ?)) AND
((? = 1 AND 年齢 IS NULL) OR (年齢 = ?)))
' DEL_Cmd.CommandText:DELETE FROM 既存テーブル
WHERE ((ID = ?) AND
((? = 1 AND 苗字 IS NULL) OR (苗字 = ?)) AND
((? = 1 AND 名前 IS NULL) OR (名前 = ?)) AND
((? = 1 AND 年齢 IS NULL) OR (年齢 = ?)))
> 「苗字 IS NULL」は、レコードの苗字列の値がDbNullの場合はTrueを返すという事は理解していますが、「? = 1」の意味が解りません。
空文字列と NULL を区別するための措置です。
たとえば WHERE (? = 1 AND 苗字 IS NULL) OR (苗字 = ?) という部分を WHERE ([引数1] = 1 AND 苗字 IS NULL) OR (苗字 = [引数2]) のように読み替えてみます。
そのうえで、VB 側からは Dim 引数1 As Integer, 引数2 As String If NULLをセットしたい場合 Then 引数1 = 1 引数2 = Nothing '未使用 Else 引数1 = 0 引数2 = TextBox1.Text End If のような引数が渡されるイメージです。
■No35088に返信(Wanさんの記事)
> 'UPD_ Cmd.CommandText :UPDATE 既存テーブル SET 苗字 = ?, 名前 = ?, 年齢 = ? > WHERE ((ID = ?) AND > ((? = 1 AND 苗字 IS NULL) OR (苗字 = ?)) AND > ((? = 1 AND 名前 IS NULL) OR (名前 = ?)) AND > ((? = 1 AND 年齢 IS NULL) OR (年齢 = ?)))No35089 の機能は、SourceColumnNullMapping プロパティによって制御されています。
Dim sb As New System.Text.StringBuilder()
sb.AppendLine($"UpdateCommand のパラメータ数={UPD_Cmd.Parameters.Count}")
For Each p As OleDbParameter In UPD_Cmd.Parameters
sb.Append($"{p.ParameterName,-4}{vbTab}")
sb.Append($"{p.SourceVersion,-8}{vbTab}")
sb.Append($"{If(p.SourceColumnNullMapping, "T", "F")}{vbTab}")
sb.Append($"{p.OleDbType,-8}{vbTab}")
sb.AppendLine($"{p.SourceColumn}")
Next
Debug.WriteLine(sb.ToString())
上記を実行すると、このような表が得られます。
p1,p5,p6 の違いに着目してみてください。
================================================
UpdateCommand のパラメータ数=10
p1 Current F VarWChar 苗字
p2 Current F VarWChar 名前
p3 Current F Numeric 年齢
p4 Original F Integer ID
p5 Original T Integer 苗字
p6 Original F VarWChar 苗字
p7 Original T Integer 名前
p8 Original F VarWChar 名前
p9 Original T Integer 年齢
p10 Original F Numeric 年齢
================================================
Original は SELECT した時点の元の値を意味し、
Current は、これから反映させたい編集後の値です。
「p5 と p6」「p7 と p8」「p9 と p10」では、
SourceColumn は同じですが、
SourceColumnNullMapping が異なっていますよね。これが
(? = 1 AND {SourceColumn} IS NULL) OR ({SourceColumn} = ?)
の正体となります。
--- 以下蛇足 ---
> インスタンス名 Access2010
Access 2010 世代のプロバイダー名は、本来は
"Microsoft.ACE.OLEDB.12.0" ですね。もちろん
"Microsoft.ACE.OLEDB.16.0" からでもアクセス可能ですが。
> Dim oledb As String = "Microsoft.ACE.OLEDB.16.0;"
oledb という変数名を付けて後で、それと同名の
> Dim Cn As New OleDb.OleDbConnection(…
な OleDb 名前空間を記述されていますね。
名前が被って分かりにくくなりませんか?
それ以外の
> Dim Da As New OleDbDataAdapter(…> Dim builder As OleDbCommandBuilder = …
では、OleDb. が無い状態にそろえてあるのに対し、
oledb 変数名が必要なところだけが意図的に
OleDb. 付きの表記なので、ちょっと混乱しそうです。
もちろん、文法的には問題ないのですけれども。
> Dim Cn As New OleDb.OleDbConnection($"Provider={oledb} Data Source={acfile}")
プロバイダー名にセミコロンを入れるのは不自然ですね。
Dim oledb As String = "Microsoft.ACE.OLEDB.16.0;"
$"Provider={oledb} Data Source={acfile}"
よりは
Dim oledb As String = "Microsoft.ACE.OLEDB.16.0"
$"Provider={oledb};Data Source={acfile}"
の方が好ましいですよ。他のパラメーターもセミコロン区切りなので。
実際のところ、接続文字列は
Dim csb As New OleDbConnectionStringBuilder()
csb.Provider = "Microsoft.ACE.OLEDB.16.0"
csb.DataSource = "D:\データベース勉強用DB\INSERT文.accdb"
Dim Cn As New OleDbConnection(csb.ConnectionString)
のようにして組み立てることもできるのですが、この際に
csb.Provider = "Microsoft.ACE.OLEDB.16.0;"
としてしまうと、正しく接続できなくなります。
> Dim Da As New OleDbDataAdapter("SELECT * FROM 既存テーブル", Cn)
[既存テーブル] なのですね。冒頭の記述から [Test] かと誤解してました。
> (Accessのテーブル構成)> インスタンス名 Access2010> データベース名 データベース勉強用DB> 論理テーブル名 Test> 物理テーブル名 INSERT文.accdb
■No35091に追記(魔界の仮面弁士の記事)
> No35089 の機能は、SourceColumnNullMapping プロパティによって制御されています。
おぉ。追加説明のコードを書いているうちに、
ピンポイントでその点についての追加質問が投稿されていた…!
SQL における「列名 = 値」という記述について考えてみます。
SET などの『代入処理』の場合は、
値が null/非null いずれでも、「列名 = 値」と書けます。
しかし、WHERE などの『比較処理』の場合は、
「列名 = NULL」では TRUE 判定されないため、
「列名 = 非NULL値」と「列名 IS NULL」を使い分けねばなりません。
そのための仕組みです。
ついでなので、DELETE と INSERT のケースも載せておきます。
DeleteCommand のパラメータ数=7
p1 Original F Integer ID
p2 Original T Integer 苗字
p3 Original F VarWChar 苗字
p4 Original T Integer 名前
p5 Original F VarWChar 名前
p6 Original T Integer 年齢
p7 Original F Numeric 年齢
InsertCommand のパラメータ数=3
p1 Current F VarWChar 苗字
p2 Current F VarWChar 名前
p3 Current F Numeric 年齢
DataTable 内のデータは、DataRowVersion によってバージョン管理されており、
SELECT 直後の値(Original)と
編集結果を考慮した現在値(Current)の 2 種類を持ちます。
※ DataRow の RowState プロパティも参照
Original なパラメーターは、WHERE 句のために使われます。
INSERT の場合は WHERE が無いので、Current のみです。
逆に DELETE の場合は、現在値が存在しないため Current がなく
Original だけになります。
そして UPDATE の場合は、SET 句用の Current と
WHERE 句用の Original の両方があるというわけです。