SQL Server のストアドプロシージャで sp_executesql を使って動的SQLを実行したとき、SQL文を単体実行すると動くのに、プロシージャ経由だと突然「条件式に非 boolean(真偽値以外)が指定された」系のエラーが出ることがあります。多くの場合、原因は文法そのものではなく、動的SQL文字列が途中で切れて“不完全なSQL”になっていることです。
ストアドプロシージャで「An expression of non-boolean type…」が出る現象
動的SQLを実行するコードをストアドプロシージャにまとめ、複数の入力パラメーターを受け取って WHERE 条件を組み立てる…という形はよくあります。ところが、次のようなエラーが発生するケースがあります。
An expression of non-boolean type specified in a context where a condition is expected, near 'seque'.
このメッセージは直訳すると「条件が期待される場所に、真偽値以外の式が指定された(’seque’ の近く)」という意味です。たとえば WHERE 句や AND/OR の後には「比較式(= や > など)」「EXISTS」「IN」など“真/偽”に評価できる式が必要ですが、そこに別のものが来てしまっている、という扱いになります。
厄介なのは、エラーの原因が「WHERE の書き方」ではなく、動的SQLが途中で欠けた結果、SQL Server が文法的に解釈できない状態になっていることが多い点です。エラー文の末尾にある near 'seque' のような断片は、欠けたSQLの“切れ目の近く”を示していることがよくあります。
なぜ「SQLを単体で実行すると動く」のに、プロシージャ経由だと壊れるのか
動的SQLを組み立てた最終文字列(例:@SQL)をコピーしてSSMSで単体実行すると正しく動くのに、プロシージャとして実行するとエラーになる。こうした現象が起きる代表的なパターンは次の通りです。
| 状況 | 起きていること | 結果 |
|---|---|---|
| SSMSでSQL文を単体実行 | 完全なSQL文字列を実行している | 正常に動く |
| プロシージャで動的SQLを実行 | @SQL 変数のサイズ不足で途中までしか格納できていない | 途中で切れたSQLが実行され、文法エラーになる |
つまり「SQLそのものは正しい」のに、プロシージャ内部では“正しいSQLが実行されていない”状態になっている、というのがポイントです。
原因の本命:動的SQLを格納する変数のサイズ不足で SQL 文字列が途中で切れる
今回のケースで最も可能性が高い原因は、動的SQLを格納している変数 @SQL が NVARCHAR(500) など短い長さで宣言されており、組み立てたSQLが途中で切れていたことです。
SQL Server は、文字列を変数に代入する際にサイズが足りないと、(テーブルへの INSERT のように)必ずしもエラーを出してくれるわけではありません。変数への代入では黙って切り捨てられることがあります。動的SQLの場合、途中で切れたまま sp_executesql に渡されるため、実行時に“謎の文法エラー”として表面化します。
「非 boolean」エラーに見える理由
動的SQLが切れると、たとえば次のような中途半端な状態になります。
WHERE ... AND sequeのように、ANDの後が比較式にならず、識別子の途中で終わるWHERE ... AND Col = @Paのように、パラメーター名や演算子が欠けるCASE WHENの途中で切れ、条件式が未完成になる
SQL Server は WHERE や AND の後に来るものを「条件式」として解釈しようとしますが、切れたSQLでは条件式として成立しません。その結果、「条件式に真偽値以外が来た」という系統のエラーになります。
そして near 'seque' のような表示は、たとえば本来 sequence や sequencer、あるいは別の単語の途中だったものが切れた結果、その断片が“怪しい近辺”として報告される、という構図です。
まず疑うべきチェックポイント(切り分けが速くなる)
動的SQL絡みの文法エラーは、SQLそのものを眺めても原因が分からないことが多いです。最初にやると切り分けが早いチェックをまとめます。
| チェック項目 | 確認方法 | ありがちな落とし穴 |
|---|---|---|
| @SQL の宣言サイズ | DECLARE @SQL nvarchar(500) のように短くないか確認 | 「500文字あれば足りるはず」と思い込みやすい(条件追加で簡単に超える) |
| 生成されたSQLの長さ | SELECT LEN(@SQL), DATALENGTH(@SQL) | LEN は末尾スペースを数えない/NVARCHAR は 1文字=2バイト |
| 生成されたSQLの全文確認 | SELECT @SQL で結果グリッドに出す | PRINT は長文が途中で切れて表示される(切れていることに気づけない) |
| @ParmDef の長さ | パラメーター定義文字列が長くなっていないか確認 | @ParmDef も短いと、定義が途中で切れ別エラーの原因になる |
PRINT でデバッグする場合の注意点
動的SQLのデバッグで PRINT @SQL を使う例は多いですが、長い文字列は途中で切れて表示されます。表示が切れているのに「SQLは最後まで出ている」と誤解すると、原因の特定が遠回りになります。
長いSQLの確認は、基本的に次のどちらかが安全です。
- 結果グリッドに出す:
SELECT @SQL AS GeneratedSQL; - 長さも同時に確認:
SELECT LEN(@SQL) AS LenChars, DATALENGTH(@SQL) AS LenBytes;
解決策:@SQL を NVARCHAR(MAX) に変更する
原因が文字列の途中切れであれば、対処はシンプルです。動的SQLを格納する変数を NVARCHAR(MAX) に変更する(または十分な長さに拡張する)だけで解消することが多いです。
| 項目 | 修正前(例) | 修正後(推奨) |
|---|---|---|
| @SQL 変数 | DECLARE @SQL nvarchar(500); | DECLARE @SQL nvarchar(max); |
| @ParmDef 変数 | DECLARE @ParmDef nvarchar(200); | DECLARE @ParmDef nvarchar(max); |
修正例(動的SQLの基本形)
典型的な修正例を、プロシージャ内のコードとしてまとめます。ポイントは「SQL本文」「パラメーター定義」の両方を MAX にし、sp_executesql にパラメーターを渡して実行する形を崩さないことです。
CREATE OR ALTER PROCEDURE dbo.SearchSomething
@UserId int = NULL,
@Keyword nvarchar(100) = NULL,
@FromDate date = NULL,
@ToDate date = NULL
AS
BEGIN
SET NOCOUNT ON;
DECLARE @SQL nvarchar(max) = N'';
DECLARE @ParmDef nvarchar(max) = N'@UserId int, @Keyword nvarchar(100), @FromDate date, @ToDate date';
SET @SQL = N'
SELECT
t.Id,
t.UserId,
t.Title,
t.CreatedAt
FROM dbo.TargetTable AS t
WHERE 1=1
';
IF @UserId IS NOT NULL
SET @SQL += N' AND t.UserId = @UserId';
IF @Keyword IS NOT NULL AND @Keyword <> N''
SET @SQL += N' AND t.Title LIKE N''%'' + @Keyword + N''%''';
IF @FromDate IS NOT NULL
SET @SQL += N' AND t.CreatedAt >= @FromDate';
IF @ToDate IS NOT NULL
SET @SQL += N' AND t.CreatedAt < DATEADD(day, 1, @ToDate)';
-- デバッグ(必要時)
-- SELECT @SQL AS GeneratedSQL, LEN(@SQL) AS LenChars, DATALENGTH(@SQL) AS LenBytes;
EXEC sys.sp_executesql
@stmt = @SQL,
@params = @ParmDef,
@UserId = @UserId,
@Keyword = @Keyword,
@FromDate = @FromDate,
@ToDate = @ToDate;
END;
この変更だけで「条件式に非 boolean」エラーが消えるなら、ほぼ確実に「途中で切れていた」ことが原因です。
「@SQL を MAX にしたのに直らない」場合に疑うこと
大半は @SQL のサイズ不足ですが、似た症状を作る落とし穴もあります。ここを押さえておくと、次に同種のトラブルが起きたときの復旧が速くなります。
文字列連結の途中で型が “短い方” に寄ってしまう
動的SQLの組み立てで、どこかに短い型(例:nvarchar(100) や varchar(8000))が混ざると、式全体の型推論の結果として意図せず切れることがあります。特に注意したいのは次のようなケースです。
ISNULL(長いnvarchar(max)式, N'')の戻り型が短い方に寄る- 途中で
varcharを混ぜてしまい、暗黙変換が起きる - 一時的な変数に入れ直すときに
nvarchar(500)など短く宣言している
対策としては、組み立てに使う変数・式を最初から nvarchar(max) に揃える、必要に応じて CAST(... AS nvarchar(max)) を挟む、という方針が安定します。
@ParmDef(パラメーター定義)が切れると別のエラーになる
今回の本筋は @SQL の切れですが、パラメーターの数が増えるほど @ParmDef も長くなります。@ParmDef が途中で切れると、たとえば次のような別エラーや不可解な動作につながることがあります。
- 定義されていないパラメーターとして扱われる
- 型や長さが途中で欠け、構文エラーになる
- 一見するとSQL本文側の問題に見える
「SQL本文を MAX にしたのに直らない」ときは、@ParmDef も MAX にしているかを必ず確認してください。
動的SQLを減らせるなら減らす(デバッグ性と保守性が上がる)
今回のようなトラブルは、動的SQLの“文字列としての脆さ”が原因で起こります。入力値によって検索条件をON/OFFしたいだけなら、必ずしも動的SQLが必要とは限りません。よくある代替案を知っておくと、そもそもこの手の事故を減らせます。
代替案:静的SQLで「条件を任意」にする
たとえば次のように書くと、SQL文字列を組み立てずに済みます。
SELECT
t.Id, t.UserId, t.Title, t.CreatedAt
FROM dbo.TargetTable AS t
WHERE
(@UserId IS NULL OR t.UserId = @UserId)
AND (@Keyword IS NULL OR @Keyword = N'' OR t.Title LIKE N'%' + @Keyword + N'%')
AND (@FromDate IS NULL OR t.CreatedAt >= @FromDate)
AND (@ToDate IS NULL OR t.CreatedAt < DATEADD(day, 1, @ToDate));
この書き方はシンプルでデバッグしやすい一方、条件の書き方次第ではインデックスが効きづらくなることがあります。性能がシビアなら、動的SQLを使う/OPTION(RECOMPILE) を検討するなど、要件に合わせて選びます。
動的SQLが必要な場面(判断の目安)
| 動的SQLが向いている | 動的SQLを避けたい |
|---|---|
| 並び替え列やテーブル名など、構造自体が変わる(識別子が変動する) | 単に検索条件の有無が変わるだけ |
| 条件が多く、静的SQLだと最適化・性能が悪化する | デバッグ性・保守性を優先したい(改修頻度が高い) |
| 必要なときだけ条件を追加し、実行プランの無駄を減らしたい | セキュリティ要件が厳しく、SQLインジェクションのリスクを極力排除したい |
「今回のSQLは入力値で文面が変わるタイプでなければ、動的SQLにせず通常のSQLとして書ける」ケースも多いです。まずは“動的SQLが本当に必要か”から見直すと、トラブルが起きにくい設計になります。
動的SQLを使うなら押さえるべき改善ポイント
動的SQL自体が悪いわけではありません。使うなら、事故りやすい部分を最初から潰しておくのが重要です。
SQL文字列が切れていないかを最初に疑う
「単体で動くのにプロシージャでだけ壊れる」「エラーが near ‘xxx’ で意味不明」などの症状が出たら、まず次を疑うと切り分けが速いです。
- @SQL 変数の型と長さ(短い宣言がないか)
- 途中経路に短い型の変数がないか(一時変数、ISNULL、CAST など)
- SQLの全文を SELECT で確認(PRINT 依存をやめる)
パラメーター化を徹底して安全性と性能を確保する
動的SQLで値を文字列連結してしまうと、SQLインジェクションのリスクが上がり、実行プラン再利用も不利になりやすいです。基本は sp_executesql でパラメーターを渡し、値は連結しない設計に寄せます。
| やりがち | 推奨 | 理由 |
|---|---|---|
... WHERE Name = ''' + @Name + ''' | ... WHERE Name = @Name | インジェクション対策/実行プラン再利用 |
EXEC(@SQL) | sp_executesql | パラメーター化しやすい/型が安定 |
| デバッグは PRINT だけ | SELECT + LEN/DATALENGTH | 長文切れに気づける |
識別子(列名・テーブル名)を動的にするなら QUOTENAME を使う
ORDER BY の列名など、どうしても識別子を動的にしたい場面では、値とは別の注意点があります。識別子はパラメーター化できないため、許可リストで制御しつつ QUOTENAME を使って壊れにくくします。
-- 例:並び替え列を限定する(許可リスト)
DECLARE @SortColumn sysname;
SET @SortColumn =
CASE @SortKey
WHEN N'CreatedAt' THEN N'CreatedAt'
WHEN N'Title' THEN N'Title'
ELSE N'CreatedAt'
END;
SET @SQL += N' ORDER BY t.' + QUOTENAME(@SortColumn) + N' DESC';
「値はパラメーター」「識別子は許可リスト + QUOTENAME」という役割分担にすると、動的SQLの安全性が大きく上がります。
再発防止のチェックリスト(実装前に見直す)
最後に、今回のタイプのエラーを繰り返さないためのチェックリストをまとめます。動的SQLを触るたびに、この表を一度見直すだけでも事故率が下がります。
| 項目 | OK の目安 | メモ |
|---|---|---|
| @SQL は nvarchar(max) | 最初から MAX で宣言 | 短い nvarchar(n) を経由しない |
| @ParmDef も nvarchar(max) | パラメーターが増えても切れない | 定義文字列の切れは別エラーを生む |
| SQL全文の確認手段がある | SELECT + LEN/DATALENGTH | PRINT だけに頼らない |
| 値は連結しない | sp_executesql にパラメーターで渡す | 安全性と性能が両立しやすい |
| 識別子は許可リスト化 | QUOTENAME + CASE などで制限 | 任意文字列をそのまま埋め込まない |
まとめ:意味不明な文法エラーほど「SQL文字列が切れていないか」を疑う
An expression of non-boolean type specified... のようなエラーは、表面上は「WHERE句の条件がおかしい」と言っているように見えます。しかし動的SQLの文脈では、SQLの途中切れによって“条件式が存在しない状態”になっているケースが非常に多いです。
特に、動的SQLを格納する変数が NVARCHAR(500) など短い宣言になっていると、条件追加やJOIN追加で簡単に上限を超え、黙って切れたSQLが実行されます。まずは @SQL と @ParmDef を NVARCHAR(MAX) に揃え、SELECT @SQL と LEN/DATALENGTH で“最後まで生成できているか”を確認するところから始めるのが最短ルートです。

コメント