VB.NET(ADO.NET)から SQL Server に対して UPDATE を実行したとき、「スカラー変数 @xxx を宣言していません」「変数を宣言していない」などの Declare variable 系エラーで止まることがあります。多くの場合は“SQL に書いたパラメータ”と“SqlCommand に追加したパラメータ”のズレが原因です。大量の日時パラメータ(@trn_date1〜20、@ntrn_date1〜20)でも事故らない実装と、切り分け手順をまとめます。
現象:VB.NET(ADO.NET)で UPDATE 実行時に「スカラー変数を宣言していません」になる
SQL Server でよく見かけるメッセージは次のようなものです。
- 「スカラー変数 “@trn_date1” を宣言してください」
- 「変数 “@ntrn_date20” を宣言していません」
- 英語環境なら “Must declare the scalar variable ‘@trn_date1’.”
これは SQL 文の中に @パラメータ名が登場しているのに、実行時にその値が渡されていないときに SQL Server が返す代表的なエラーです。つまり、VB 側では「Parameters に入れたつもり」でも、SQL 文と Parameters の対応がどこかで崩れている可能性が高いです。
最短で直すための結論
Declare variable 系エラーを最短で解消する基本は、次の4点です。
- SQL 文に登場する @xxx は、例外なくすべて SqlCommand.Parameters に追加する(名前も揃える)
- WHERE 句の “文字列連結” をやめて、WHERE 句もパラメータ化する(@Account_No など)
- コマンドを使い回すなら実行前に Parameters.Clear()(重複・残留を防ぐ)
- NULL(Nothing)を確実に DBNull.Value に変換して渡す(日付が未入力のときに安全)
原因の大半:SQL の @パラメータと Parameters が一致していない
Declare variable 系は、ほぼ次のどれかです。
| よくあるズレ | 起きること | 対策 |
|---|---|---|
| SQL に @trn_date11 があるのに Parameters に追加していない | 実行時に「@trn_date11 を宣言していません」 | SQL 上の @xxx をすべて洗い出して追加 |
| スペル違い(例:@ntrn_date11 と @ntrn_dat11) | 片方が未宣言扱い | 名前を完全に統一(コピー&ペーストで揃える) |
| @ を付けたり付けなかったり混在 | 環境・実装によって一致しないことがある | Parameters 側も SQL 側も @付きで統一 |
| コマンド再利用で同名パラメータが重複 | 意図しない値が使われたり、例外が出たりする | 実行前に Parameters.Clear() でリセット |
補足として、SQL Server 自体は通常、大文字小文字を厳密に区別しないことが多いですが、コードの保守性・他プロバイダ利用・人間の見落とし防止の観点で、SQL と Parameters のパラメータ名は完全一致を強く推奨します。大量パラメータほど「一文字違い」に気付きにくいからです。
WHERE 句の文字列連結が “事故の温床” になる
質問でよくあるのが、SET 句はパラメータ化しているのに WHERE 句だけ文字列連結しているパターンです。
やりがちな例(非推奨)
Command1.CommandText = "UPDATE names2 SET trn_date1 = @trn_date1 WHERE account_no = " & Account_No
この書き方は次の問題を抱えます。
- 型の問題:account_no が文字列なら本来は
'...'が必要。数値でも桁区切りや前後空白で事故る。 - SQL インジェクション:外部入力が混じると不正 SQL を混入される可能性がある。
- デバッグが難しい:連結結果が意図どおりか毎回確認が必要。
正攻法(推奨)
Command1.CommandText = "UPDATE names2 SET trn_date1 = @trn_date1 WHERE account_no = @Account_No"
Command1.Parameters.AddWithValue("@Account_No", Account_No)
WHERE 句も含めてすべてパラメータ化すると、Declare variable 系だけでなく、将来的な不具合とセキュリティリスクもまとめて潰せます。
コマンドを使い回すなら Parameters.Clear() が必須
ボタンを押すたびに同じ SqlCommand インスタンスを使っている場合、次のような “2回目以降だけおかしい” 現象が起こりがちです。
- 前回のパラメータが残り、今回追加したつもりの値が使われない
- 同名パラメータが重複して例外が出る
- 画面入力が空なのに前回値で更新されてしまう
回避策はシンプルで、SQL をセットする直前〜実行前に Parameters.Clear() することです。
Command1.CommandText = "UPDATE names2 SET ... WHERE account_no = @Account_No"
Command1.Parameters.Clear()
可能なら、SqlCommand 自体を都度 new して Using で閉じる書き方に寄せると、そもそも「残留」が起きません(後述の実践コード参照)。
NULL(Nothing)を DBNull.Value に変換できていない
日付入力コントロールの EditValue は、未入力のときに Nothing や DBNull.Value、または空文字など、UI ライブラリや設定によって戻り方が異なることがあります。ここが曖昧だと、次の問題が起きます。
AddWithValueにNothingを渡してしまい、意図しない型推論や例外が起きる- SQL 側の列が NULL 許可なのに、VB 側で NULL を表現できず更新が失敗する
安全策は、“NULL 判定 → DBNull.Value に置換” を必ず挟むことです。
Dim v = datTrn_Date1.EditValue
Command1.Parameters.AddWithValue("@trn_date1", If(v Is Nothing OrElse v Is DBNull.Value, DBNull.Value, v))
さらに堅くするなら、Date/DateTime として渡す前提で型チェックまで入れると、データの混入(文字列が入っていた等)にも早期に気付けます。
AddWithValue を “使っても動く” が、ハマりやすい
AddWithValue は手軽ですが、値から型推論します。そのため、日付列に対して次のような事故が起きがちです。
- UI の値が文字列扱いになり、SQL Server 側で暗黙変換が走って遅くなる/変換失敗する
- 意図しない型(例:Date のつもりが DateTime 扱い)になり、比較やインデックス利用に影響する
- ロケール依存の文字列日付が混ざり、環境によって変換できたりできなかったりする
おすすめは、SqlDbType を明示して追加し、Value だけ入れるやり方です。
Dim p = Command1.Parameters.Add("@trn_date1", SqlDbType.Date) ' 列が datetime なら DateTime
p.Value = If(v Is Nothing OrElse v Is DBNull.Value, DBNull.Value, CType(v, Date))
列が SQL Server の date 型なら SqlDbType.Date、datetime/datetime2 型なら SqlDbType.DateTime か SqlDbType.DateTime2 を合わせます。ここを揃えると、予期せぬ暗黙変換が減り、実行計画も安定しやすくなります。
大量パラメータでミスを減らす設計:仕組み化が勝ち
@trn_date1〜@trn_date20、@ntrn_date1〜@ntrn_date20 のように 40 個を手で追加すると、どうしても次が起きます。
- 1つだけ追加し忘れる
- 名前の数字を間違える(date12 を入れるつもりが date21 など)
- SQL 側の列名だけ直して VB 側を直し忘れる
そこでおすすめなのが、パラメータ追加を関数化して、さらに可能なら ループで生成することです。
NULL 変換を共通化するヘルパー
Private Function ToDbNull(value As Object) As Object
If value Is Nothing OrElse value Is DBNull.Value Then
Return DBNull.Value
End If
Return value
End Function
この 1 本を用意しておくだけで、「NULL 変換漏れ」が劇的に減ります。
日付パラメータ追加を共通化するヘルパー
Private Sub AddNullableDate(cmd As SqlClient.SqlCommand, name As String, value As Object)
Dim p = cmd.Parameters.Add(name, SqlDbType.Date) ' datetime列なら DateTime
If value Is Nothing OrElse value Is DBNull.Value Then
p.Value = DBNull.Value
Exit Sub
End If
' UI の戻り値が Date/DateTime である想定。文字列が混ざるなら TryCast/Parse を検討。
p.Value = CType(value, Date)
End Sub
この形にすると、各項目は AddNullableDate(cmd, "@trn_date1", datTrn_Date1.EditValue) のように統一でき、可読性も上がります。
実践:names2 を UPDATE する “壊れにくい” VB.NET(ADO.NET)例
ここからは、よくある構成(SqlConnection + SqlCommand)で、宣言系エラーを起こしにくい形を載せます。ポイントは次のとおりです。
- SQL に登場するパラメータを 必ず全部追加
- WHERE 句も 必ずパラメータ化
- NULL は DBNull.Value に統一
- できれば AddWithValue を使わず型指定
- Using で 接続とコマンドを確実に破棄(パラメータ残留を根絶)
Imports System.Data
Imports System.Data.SqlClient
Public Sub UpdateNames2(accountNo As String,
trnValues As Object(), ' 1..20 相当(配列は 0..19 でもOK)
ntrnValues As Object()) ' 1..20 相当
Dim sql As String =
"UPDATE names2 SET " &
" trn_date1 = @trn_date1," &
" trn_date2 = @trn_date2," &
" trn_date3 = @trn_date3," &
" trn_date4 = @trn_date4," &
" trn_date5 = @trn_date5," &
" trn_date6 = @trn_date6," &
" trn_date7 = @trn_date7," &
" trn_date8 = @trn_date8," &
" trn_date9 = @trn_date9," &
" trn_date10 = @trn_date10," &
" trn_date11 = @trn_date11," &
" trn_date12 = @trn_date12," &
" trn_date13 = @trn_date13," &
" trn_date14 = @trn_date14," &
" trn_date15 = @trn_date15," &
" trn_date16 = @trn_date16," &
" trn_date17 = @trn_date17," &
" trn_date18 = @trn_date18," &
" trn_date19 = @trn_date19," &
" trn_date20 = @trn_date20," &
" ntrn_date1 = @ntrn_date1," &
" ntrn_date2 = @ntrn_date2," &
" ntrn_date3 = @ntrn_date3," &
" ntrn_date4 = @ntrn_date4," &
" ntrn_date5 = @ntrn_date5," &
" ntrn_date6 = @ntrn_date6," &
" ntrn_date7 = @ntrn_date7," &
" ntrn_date8 = @ntrn_date8," &
" ntrn_date9 = @ntrn_date9," &
" ntrn_date10 = @ntrn_date10," &
" ntrn_date11 = @ntrn_date11," &
" ntrn_date12 = @ntrn_date12," &
" ntrn_date13 = @ntrn_date13," &
" ntrn_date14 = @ntrn_date14," &
" ntrn_date15 = @ntrn_date15," &
" ntrn_date16 = @ntrn_date16," &
" ntrn_date17 = @ntrn_date17," &
" ntrn_date18 = @ntrn_date18," &
" ntrn_date19 = @ntrn_date19," &
" ntrn_date20 = @ntrn_date20" &
" WHERE account_no = @Account_No;"
Dim connStr As String = "YOUR_CONNECTION_STRING"
Using conn As New SqlConnection(connStr)
conn.Open()
Using cmd As New SqlCommand(sql, conn)
cmd.CommandType = CommandType.Text
' WHERE 句のパラメータ
Dim pAcc = cmd.Parameters.Add("@Account_No", SqlDbType.NVarChar, 50)
pAcc.Value = accountNo
' trn_date1..20
For i As Integer = 1 To 20
Dim paramName As String = "@trn_date" & i.ToString()
Dim v As Object = Nothing
If trnValues IsNot Nothing AndAlso trnValues.Length >= i Then
v = trnValues(i - 1)
End If
AddNullableDate(cmd, paramName, v)
Next
' ntrn_date1..20
For i As Integer = 1 To 20
Dim paramName As String = "@ntrn_date" & i.ToString()
Dim v As Object = Nothing
If ntrnValues IsNot Nothing AndAlso ntrnValues.Length >= i Then
v = ntrnValues(i - 1)
End If
AddNullableDate(cmd, paramName, v)
Next
cmd.ExecuteNonQuery()
End Using
End Using
End Sub
Private Sub AddNullableDate(cmd As SqlCommand, name As String, value As Object)
' 列が datetime/datetime2 の場合は SqlDbType.DateTime / DateTime2 に変更
Dim p = cmd.Parameters.Add(name, SqlDbType.Date)
If value Is Nothing OrElse value Is DBNull.Value Then
p.Value = DBNull.Value
Exit Sub
End If
' UI の EditValue が Date/DateTime の前提
p.Value = CType(value, Date)
End Sub
この例では、SQL 文に書いたパラメータ名をループで機械的に作ることで、追加漏れや数字のズレを減らしています。さらに、WHERE 句も @Account_No に統一しているため、文字列連結由来の事故も避けられます。
もし画面側に datTrn_Date1 のようなコントロールが並んでいるなら、呼び出し側で配列に詰めて渡すと整理できます。
Dim trn(19) As Object
trn(0) = datTrn_Date1.EditValue
trn(1) = datTrn_Date2.EditValue
' ...(必要に応じて)
trn(19) = datTrn_Date20.EditValue
Dim ntrn(19) As Object
ntrn(0) = datNtrn_Date1.EditValue
' ...
ntrn(19) = datNtrn_Date20.EditValue
UpdateNames2(Account_No, trn, ntrn)
切り分けを速くする:実行前に “SQL とパラメータ一覧” を目視できる形にする
Declare variable 系は、「SQL にあるのに渡していない」が原因なので、実行直前の状態を可視化すると解決が早まります。デバッグ用に次を仕込むと効果的です。
- CommandText をログに出す(どの SQL を投げているか)
- Parameters の一覧(ParameterName / SqlDbType / Value)をログに出す
For Each p As SqlParameter In cmd.Parameters
Dim v = If(p.Value Is Nothing OrElse p.Value Is DBNull.Value, "(NULL)", p.Value.ToString())
Debug.WriteLine($"{p.ParameterName} / {p.SqlDbType} / {v}")
Next
ログを見て 「SQL には @trn_date12 があるのに、Parameters 側に存在しない」が分かれば、そこが犯人です。逆に Parameters 側にはあるのにエラーになる場合は、次のような “別方向の事故” を疑います。
- 実際に投げている SQL が想定と違う(CommandText を後で上書きしている等)
- SQL の文字列が途中で途切れている、引用符が崩れている
- 動的 SQL(EXEC(@sql) など)にしていて、内側の SQL にパラメータが引き継がれていない
動的 SQL を使っている場合の注意(EXEC / sp_executesql)
たとえば次のように、SQL 文字列を組み立てて EXEC していると、外側の Parameters は内側に自動では渡りません。
DECLARE @sql nvarchar(max) = N'UPDATE names2 SET trn_date1 = @trn_date1 WHERE account_no = @Account_No';
EXEC(@sql);
このケースは sp_executesql にパラメータ定義と値を渡す必要があります。アプリ側で普通に UPDATE ... WHERE ... を投げる分には不要ですが、もし「SQL を文字列で組み立てて EXEC している」構成なら、Declare variable エラーの原因として非常に強いです。
チェックリスト:Declare variable 系エラーを再発させない
| チェック項目 | OK の基準 | NG だと起きがちなこと |
|---|---|---|
| SQL 文中の @xxx は全件 Parameters に追加したか | SQL を検索して @ が付く名前を全列挙できる | 未追加の @xxx が「宣言していません」 |
| WHERE 句はパラメータ化しているか | WHERE account_no = @Account_No になっている | 型ズレ、引用符ミス、インジェクションリスク |
| コマンドの再利用時に Parameters を初期化しているか | Parameters.Clear() または Using で毎回生成 | 2回目以降だけ挙動が変わる/重複例外 |
| 未入力値は DBNull.Value に統一しているか | Nothing/DBNull を必ず DBNull.Value に置換 | NULL のつもりが例外、または変換エラー |
| 日付は型を明示して渡しているか | SqlDbType.Date/DateTime を列型に合わせる | 暗黙変換で遅い/環境差で変換失敗 |
長期的な改善:同じような列が 20 個ある設計はミスが起きやすい
実装でカバーできますが、そもそも trn_date1〜20 のように「同種の列が多数並ぶテーブル」は、次の理由で保守が難しくなります。
- SQL も VB も変更箇所が増え、追加漏れ・番号ズレが発生しやすい
- 「〇番目の列」が業務的に何を意味するかがコードから読み取りにくい
- 検索や集計が複雑になり、SQL が肥大化しやすい
もし将来的に手を入れられるなら、子テーブル化(account_no + 連番 + 日付)などの正規化を検討すると、UPDATE の実装もシンプルになります。ただし既存運用がある場合は、まず本記事の対策で “壊れない UPDATE” を作るのが現実的です。
まとめ:宣言系エラーは「渡していない」ではなく「一致していない」ことが多い
VB.NET(ADO.NET)で UPDATE を実行して Declare variable 系エラーが出るときは、SQL Server 側の問題に見えて、実際は SQL と Parameters の対応崩れがほとんどです。特にパラメータが多いほど、人間の手作業がボトルネックになります。
- SQL 文にある @パラメータを全件追加する
- WHERE 句も必ずパラメータ化する
- コマンド再利用なら Parameters.Clear()、できれば Using で毎回生成
- NULL は DBNull.Value に統一し、日付は型を明示する
- 大量パラメータはヘルパー化・ループ化でミスを消す
この形に揃えると、「宣言していません」だけでなく、実行ごとに挙動が変わる系の不具合もまとめて減らせます。

コメント