SQL Server「Must declare the scalar variable ‘@n’」の原因と解決策|VB.NET(SqlCommand)のドット付きパラメータ対策

VB.NET の SqlCommand から SQL Server に UPDATE を実行した瞬間、「Must declare the scalar variable “@n”」が出て処理が止まることがあります。テーブル別名 n を使っているだけなのに、なぜ @n が未宣言扱いになるのか。原因と、現場で再発しない直し方を整理します。

目次

症状:SQL Server の「Must declare the scalar variable “@n”」とは

このエラーは直訳すると「スカラー変数 @n を宣言してください」です。SQL Server は、クエリの中で @n という変数(またはパラメータ)参照を見つけたのに、DECLARE されていない(またはストアド実行時に渡されていない)と判断したときにこのメッセージを返します。

VB.NET 側で SqlParameter を追加していても、SQL 文そのものが解析(パース)できないと、パラメータが届く前にエラーになります。今回のケースはまさにそれで、SQL 文中のパラメータ名の書き方が引き金です。

よくある発生パターン:テーブル別名「n」と同じ見た目にしたくなる

UPDATE の対象を読みやすくするために、テーブル別名を付けて列を n.cc_date のように書くのは自然です。ところが、値を渡す側も同じ見た目にしたくなり、パラメータ名を @n.cc_date のように作ってしまうことがあります。

しかし、列参照(n.cc_date)とパラメータ参照(@xxx)は別物です。ここを混ぜると、SQL Server は「@n」という変数を参照している、と解釈してしまいます。

原因:SQL Server の変数(パラメータ)名に「.(ドット)」は使えない

結論から言うと、原因はこれです。SQL Server(T-SQL)の変数名・パラメータ名は、@ で始まり、英数字とアンダースコア等の規則に従う必要があり、ドット(.)は変数名として扱えません。

そのため @n.cc_date は、SQL Server の解析段階で「@n という変数」+「.cc_date という何か」として分割され、結果として 未宣言の @n を参照しているとみなされます。これが「Must declare the scalar variable “@n”」の正体です。

書きたい意図正しい書き方やってしまいがちな書き方なぜダメか
別名 n の列を更新するn.cc_date = @cc_daten.cc_date = @n.cc_date変数名にドットは使えず、@n が変数として解釈される
別名 n の列を条件に使うn.account_no = @account_non.account_no = " + strAccount_NoSQL インジェクションや型変換ミスの原因になる

最短の解決策:パラメータ名から「n.」を外して一致させる

対処はシンプルです。SQL 文中のプレースホルダー(@xxx)と、コード側で追加するパラメータ名を一致させるだけ。列側の別名(n.)は列参照にだけ使い、パラメータには付けません。

修正例:SQL(UPDATE)

テーブル別名を付けた UPDATE は、次のように書くのが分かりやすく安全です。

UPDATE n
SET
    n.cc_date  = @cc_date,
    n.cpr_date = @cpr_date,
    n.fa_date  = @fa_date
FROM dbo.YourTable AS n
WHERE n.account_no = @account_no;

ポイントは、左側(列)だけに n. があり、右側(値)は @cc_date のような単純なパラメータ名になっていることです。

修正例:VB.NET(SqlCommand)

VB.NET 側は、SQL 文中の @cc_date などと同じ名前でパラメータを追加します。

Dim sql As String =
"UPDATE n " &
"SET n.cc_date = @cc_date, n.cpr_date = @cpr_date, n.fa_date = @fa_date " &
"FROM dbo.YourTable AS n " &
"WHERE n.account_no = @account_no;"

Using conn As New SqlConnection(connStr)
Using cmd As New SqlCommand(sql, conn)
cmd.Parameters.Add("@cc_date", SqlDbType.DateTime).Value = ccDateValue
cmd.Parameters.Add("@cpr_date", SqlDbType.DateTime).Value = cprDateValue
cmd.Parameters.Add("@fa_date", SqlDbType.DateTime).Value = faDateValue
cmd.Parameters.Add("@account_no", SqlDbType.VarChar, 20).Value = strAccount_No


    conn.Open()
    cmd.ExecuteNonQuery()
End Using


End Using

この形にすると、「@n」を宣言しろと言われる余地がなくなります。SQL Server が認識するのは @cc_date などのパラメータであり、別名 n は列参照にだけ登場します。

補足:なぜ「@n.cc_date」が「@n」扱いになるのか(SQL の読み取り順)

SQL Server はまず SQL 文を字句解析し、トークンに分割して意味を解釈します。ドット(.)は通常、スキーマ.テーブルやテーブル.列のように「階層を表す記号」として扱われます。

一方で @ は「ローカル変数/パラメータ」を表します。したがって @n.cc_date のように書くと、エンジンは「@n(変数)」と「.cc_date(階層記号+識別子)」に分けて解釈しようとします。ところが、変数に対して「.列」のような参照はできません。結果として、最初に見えた @n が未宣言という形でエラーになります。

ここで重要なのは、テーブル別名 n と、変数名 @n は無関係だということです。見た目が似ているだけで、SQL Server の中では別の文法として処理されています。

より安全・実務的に直す:WHERE 句の文字列連結をやめて完全パラメータ化する

同じ UPDATE 文の中で、SET 句だけパラメータ化し、WHERE 句だけ文字列連結しているコードは現場でよく見かけます。

例えば次のような形です。

... WHERE n.account_no = " & strAccount_No

これは動いてしまうこともありますが、実務では避けるべきです。理由は大きく3つあります。

  • SQL インジェクションのリスク(入力に悪意がなくても事故りやすい)
  • 型変換・クォート漏れ(数値/文字列/日付で条件の書き方が変わる)
  • 実行計画の再利用が効きにくく、性能問題につながる場合がある

WHERE 句も @account_no にしておけば、入力値の扱いが統一され、SQL 文の安定性が上がります。

AddWithValue の落とし穴:動けばOKが性能・不具合の種になる

VB.NET でパラメータを追加するとき、手軽な AddWithValue を使うケースが多いですが、実務では慎重に扱った方が安全です。理由は、型推論が意図通りにならないことがあるためです。

よくある状況AddWithValue の挙動起こり得る問題推奨
文字列を渡すNVARCHAR(4000) 相当で推論されがちインデックスが効かずスキャン、暗黙変換SqlDbType.VarChar と Size を指定
日付を渡すDateTime として推論列が DATE / DATETIME2 の場合に変換が絡む列型に合わせて SqlDbType.Date / DateTime2 を検討
数値を渡すInt32 など .NET 側型に寄るBIGINT/DECIMAL とズレて暗黙変換列型に合わせて SqlDbType.BigInt / Decimal を指定

必ずしも AddWithValue が「悪」ではありませんが、テーブル定義が明確で、将来の保守も考えるなら、SqlDbType を明示する書き方が安定します。特に UPDATE が大量データに当たる環境では、暗黙変換による性能劣化があとから効いてきます。

NULL(Nothing)の扱い:DBNull.Value を意識する

日付列や任意入力の列を更新するとき、値が入らないケースがあります。VB.NET 側で Nothing のまま Value に入れると例外になったり、意図しない既定値になったりすることがあります。

SQL Server に「NULL を入れる」意思を伝えるには、.NET の Nothing ではなく DBNull.Value を渡すのが基本です。

Dim p = cmd.Parameters.Add("@cc_date", SqlDbType.DateTime)
If ccDateValue.HasValue Then
    p.Value = ccDateValue.Value
Else
    p.Value = DBNull.Value
End If

列が NOT NULL の場合は当然 NULL を入れられないので、アプリ側の入力チェックと合わせて設計します。

日付列の更新でハマりやすいポイント(DATE / DATETIME / DATETIME2)

cc_date / cpr_date / fa_date のように日付を扱う列は、環境によってデータ型が異なります。ここが曖昧だと、次のような不具合が出ます。

  • 日付だけのつもりが時刻が入る、または比較条件がズレる
  • SQL Server 側で暗黙変換が発生し、意図しない丸めが入る
  • 地域設定や文字列表現に依存した変換で事故る(文字列連結時に多い)

更新対象の列が DATE なら SqlDbType.Date、DATETIME2 なら SqlDbType.DateTime2 を検討し、列型とパラメータ型を揃えるのが実務的です。

トラブルシュート:まだ同じエラーが出るときの確認ポイント

「ドットを外したのに、まだ Must declare the scalar variable が出る」という場合は、SQL 文の別の箇所に同種の参照が残っていることが多いです。次の観点でチェックしてください。

  • SQL 文中に @n で始まるトークンが残っていないか(検索)
  • SET 句だけでなく WHERE / JOIN / CASE の中に @n.xxx が混ざっていないか
  • 動的 SQL を組み立てている場合、文字列結合の途中で意図せず @n が生まれていないか
  • SqlCommand の SQL 文が想定通りか(ログに出す、デバッグで見る)

SQL 文をログに残せるなら、実行直前の cmd.CommandText をそのまま出力し、エラー箇所のトークンを目視で追うのが最短です。

再発防止:命名ルールを決める(別名とパラメータ名を混ぜない)

今回のエラーは、「列の見た目」と「パラメータの見た目」を揃えたくなる心理から起きがちです。チーム開発なら、パラメータ命名ルールを一つ決めてしまうと再発が減ります。

例として、次のようなルールが実務では扱いやすいです。

  • パラメータは @p_ 接頭辞を付ける(例:@p_cc_date)
  • 列は常に別名付き(例:n.cc_date)
  • 文字列結合で SQL を作らない(必ずパラメータ)

こうすると、見た目で「これは列」「これはパラメータ」が即判別でき、@n.cc_date のような混同が起きにくくなります。

変数名・パラメータ名の基本ルール(SQL Server / T-SQL)

今回のような事故を防ぐために、T-SQL の「変数名(ローカル変数)」「パラメータ名(プレースホルダー)」のルールを最低限押さえておくと楽になります。ここで言うパラメータは、ストアドプロシージャの引数だけでなく、アプリから投げる @xxx 形式のプレースホルダーも同じ文法で解釈されます。

分類例使える文字の考え方注意点
ローカル変数DECLARE @n INT;@ に続けて識別子(英数字・_ など)ドットやハイフンのような区切り記号は基本的に使わない
プレースホルダー(アプリから渡す)... WHERE id = @id変数と同じルールで解析される「列名に寄せた命名」をすると混同しやすい(今回の @n.cc_date など)
オブジェクト名dbo.TableNameスキーマ.テーブル のようにドットが普通に出るここに出るドットは“階層”であり、変数名のドットとは意味が違う

実務上は、「変数/パラメータはドットで区切らない」だけ覚えておけば十分です。もし複数の意味を持たせたいなら、ドットの代わりにアンダースコアで @n_cc_date のように繋げる方が安全です。

整理:列参照とパラメータ参照を同じ見た目にしない

混乱が起きやすいポイントを、見た目と役割で整理します。特にレビュー時にこの表を見ながらチェックすると、ドット付きパラメータが入り込むのを防げます。

要素見た目意味よくある誤解
テーブル別名+列n.cc_date「n という別名で参照しているテーブルの cc_date 列」右辺にも同じ形を置きたくなる
パラメータ@cc_dateSQL Server に“値”を渡すための入れ物「@が付けば何でもOK」と思ってしまう
ローカル変数@nDECLARE して使う変数@n.cc_date のように書けると勘違いする

実装テンプレート:型・サイズ・NULL を意識したパラメータ追加

「とりあえず動く」から一歩進めて、保守に強い SqlCommand の書き方をテンプレートとして持っておくと便利です。特に UPDATE は、あとから列型が変わったときに AddWithValue が地雷化しやすいので、列定義に合わせて SqlDbType と Size を決めるのが無難です。

' 例:列が VARCHAR(20), DATE, DATE の想定
cmd.Parameters.Add("@account_no", SqlDbType.VarChar, 20).Value = strAccount_No

Dim p1 = cmd.Parameters.Add("@cc_date", SqlDbType.Date)
p1.Value = If(ccDateValue.HasValue, CType(ccDateValue.Value, Object), DBNull.Value)

Dim p2 = cmd.Parameters.Add("@cpr_date", SqlDbType.Date)
p2.Value = If(cprDateValue.HasValue, CType(cprDateValue.Value, Object), DBNull.Value)

Dim p3 = cmd.Parameters.Add("@fa_date", SqlDbType.Date)
p3.Value = If(faDateValue.HasValue, CType(faDateValue.Value, Object), DBNull.Value)

このテンプレートにしておくと、入力が未設定でも例外になりにくく、SQL Server 側の型変換も最小限になります。

パフォーマンス面でもパラメータ化は得をする

セキュリティの話が目立ちますが、パラメータ化は性能面でもメリットがあります。文字列連結で SQL 文を毎回変えると、SQL Server は「別の SQL」として扱いやすく、実行計画の再利用(キャッシュ)が効きにくくなります。

一方、同じ SQL 文に対して値だけをパラメータで変える形にすると、実行計画が再利用されやすく、負荷が安定しやすいです。特に更新が多いバッチや、同じ UPDATE を繰り返す業務処理では効いてきます。

ログの残し方:原因調査を速くする最低限の工夫

今回のような「SQL の文字列が少し違うだけで落ちる」系のトラブルは、実行直前の SQL とパラメータ一覧が残っているかどうかで復旧速度が大きく変わります。社内ルールや環境に合わせつつ、最低限次の情報があると調査が楽になります。

  • 実行した SQL(cmd.CommandText)
  • パラメータ名、型、サイズ、値(NULL の場合も分かる形)
  • 対象キー(account_no など)

本番環境では個人情報や機密情報の扱いに注意が必要ですが、「どのパラメータが渡っていないか」「どこに @n っぽいトークンが残っているか」を追えるだけで、同種の障害の再発防止にも繋がります。

よくある質問

パラメータ名の @ は付けても付けなくてもいい?

SqlParameter の名前は、慣例的に @ を付ける書き方が多いです。重要なのは、SQL 文中のプレースホルダーと一致することです。プロジェクト内で統一しておけば混乱が減ります。

どうしてもドット区切りで管理したい(画面項目と対応させたい)

「画面のグループ名.項目名」のような管理は便利ですが、SQL のパラメータ名には持ち込まない方が安全です。画面側のキーは n.cc_date のように持ち、SQL に渡す直前で @cc_date のように変換する、という設計が現実的です。

別名を n ではなく別の文字にすると解決する?

別名を変えても、@別名.列 の形を続ける限り同じ問題が起きます。根本原因は「変数名にドットが使えない」なので、パラメータ名自体を変える必要があります。

ストアドプロシージャでも同じ?

ストアドプロシージャの引数も同じく @ で始まる識別子で、ドットは使えません。アプリから渡すパラメータ名も、プロシージャ側の引数名に合わせて単純な形にしておくのが安全です。

まとめ

  • 「Must declare the scalar variable “@n”」は、SQL Server が @n という変数参照を見つけたが宣言されていないと判断したときに出る
  • @n.cc_date のように、パラメータ名にドットを含めると SQL Server が @n として解釈し、未宣言エラーになりやすい
  • 解決策は、パラメータ名から別名(n.)を外すこと。SQL 文は n.cc_date = @cc_date の形にする
  • ついでに WHERE 句も完全パラメータ化し、必要なら SqlDbType を明示して安定性と性能を守る

この記事を書いた人

実務の現場で詰まりがちなポイントを地図にするITブログ「IT trip」を運営。Windows/Office(Teams・Excel)からSQL、サーバ運用、ガジェットまで、再現性のある手順と“なぜそうなるか”を丁寧に解説します。読んだらすぐ試せること、そして迷った人の次の一歩が見えることを大切にしています。

コメント

コメントする

目次