SQL ServerのTRY…CATCHでシーケンス名誤記が捕捉できない理由と対処法【NEXT VALUE FOR】

SQL Server の TRY…CATCH が利かずにバッチが停止する――とくに NEXT VALUE FOR でシーケンス名を誤記したときに起きる現象。これはバグではなく、遅延名前解決とコンパイル時エラーの扱いに起因する“仕様”です。本記事では再現手順から原因の深掘り、現場で実践できる回避策、堅牢な実装テンプレートまでを一気に整理します。

目次

問題の核心:なぜ TRY…CATCH に入らずに止まるのか

同じ TRY…CATCH ブロック内でも、シーケンス名の誤記は捕捉できず、0 で割るエラーは捕捉できます。違いは「エラーが発生するタイミング」です。

  • シーケンス名の誤記:最適化・実行計画生成の段階で「オブジェクトが存在しない」ことが検出されるコンパイル時エラー(名前解決エラー)。同一スコープの TRY…CATCH では捕捉できません。
  • 0 除算:実際に式を評価した瞬間に起こる実行時エラー。TRY…CATCH で捕捉できます。
ケース発生タイミング代表的メッセージTRY…CATCH での扱い備考
シーケンス名の誤記コンパイル時(名前解決)対象のシーケンスが存在しない、または権限がない同一スコープでは捕捉不可遅延名前解決の影響。外側スコープなら捕捉可
0 除算実行時Divide by zero error encountered捕捉可能エラー番号・重大度は実行時の文脈による

まずは再現:最小コードで挙動を確認

以下のスクリプトは、同じスコープでは捕捉できないこと、外側スコープなら捕捉できることを示します。

-- 準備:正しいシーケンス(動作確認用)
IF OBJECT_ID(N'dbo.Seq_Demo', 'SO') IS NOT NULL
    DROP SEQUENCE dbo.Seq_Demo;
GO
CREATE SEQUENCE dbo.Seq_Demo AS bigint START WITH 1 INCREMENT BY 1;
GO

-- 同一スコープでの TRY…CATCH:誤記は捕捉できない
BEGIN TRY
SELECT NEXT VALUE FOR dbo.Seq_Typo;  -- 存在しない
SELECT 1/0;                          -- ここに到達すれば 0 除算は捕捉可能
END TRY
BEGIN CATCH
PRINT N'CATCH に入りました(同一スコープ)';
PRINT ERROR_MESSAGE();
END CATCH;
GO

-- 内部 SP で誤記 → 外側 SP で捕捉
IF OBJECT_ID(N'dbo.inner_sp', 'P') IS NOT NULL DROP PROCEDURE dbo.inner_sp;
IF OBJECT_ID(N'dbo.outer_sp', 'P') IS NOT NULL DROP PROCEDURE dbo.outer_sp;
GO
CREATE PROCEDURE dbo.inner_sp
AS
BEGIN
BEGIN TRY
SELECT NEXT VALUE FOR dbo.Seq_Typo; -- 誤記
END TRY
BEGIN CATCH
-- ここには来ない
PRINT N'inner_sp の CATCH(到達しない)';
END CATCH
END
GO

CREATE PROCEDURE dbo.outer_sp
AS
BEGIN
BEGIN TRY
EXEC dbo.inner_sp;
END TRY
BEGIN CATCH
PRINT N'外側で捕捉:' + ERROR_MESSAGE();
END CATCH
END
GO

EXEC dbo.outer_sp;

技術的背景:遅延名前解決(deferred name resolution)

SQL Server はストアドプロシージャやバッチを即時に完全解決しない設計を採用しており、テーブル・ビュー・シーケンスなどの存在有無は、実際に当該ステートメントが最適化・実行される段階でチェックされます。これが遅延名前解決です。

このとき発生する「オブジェクトが見つからない」類のエラーはコンパイル時エラーとして扱われ、同一スコープの TRY…CATCH では捕捉できません。一方で、外側のスコープに対してはエラーが伝播するため、呼び出し元に TRY…CATCH を置けば捕捉できます。

現場で使える解決策

捕捉したい/落ちずに制御を取り戻したいケースに対して、現実解を優先順に整理します。

外側スコープでラップする(最小の実装負荷)

モジュールを分け、呼び出し側に TRY…CATCH を置きます。上の inner_sp / outer_sp の例が典型です。

  • 長所:既存ロジックを変えずに適用できる。構造化しやすい。
  • 短所:すべての呼び出し点でのラップを徹底する必要がある。

事前に存在を検査して明示的に例外化する

呼び出し直前にカタログから存在をチェックし、みずから THROW または RAISERROR で例外化します。THROW のほうが新しく推奨です。

DECLARE @schema sysname = N'dbo', @seq sysname = N'sqc_LogReplic_tmp';
IF OBJECT_ID(QUOTENAME(@schema) + N'.' + QUOTENAME(@seq), 'SO') IS NULL
    THROW 50000, N'シーケンスが存在しません: ' + @schema + N'.' + @seq, 1;

-- 以降は安全に参照できる
SELECT NEXT VALUE FOR dbo.sqc_LogReplic_tmp;

この方法は開発時の見落とし、デプロイ漏れ、スキーマ名の勘違いを早期に検知できます。ただし存在確認は「権限の有無」までは保証しません。存在はするが権限がないケースでは、最終的に実行時に権限エラーが発生します(この点は外側スコープで捕捉して復旧してください)。

動的 SQL(sp_executesql)で名前解決を分離する

シーケンス名の解決を別バッチに逃がすと、外側の TRY…CATCH で捕捉しやすくなります。読みやすさとセキュリティ(SQL インジェクション対策)を両立するため、QUOTENAME で厳格に限定します。

CREATE OR ALTER PROCEDURE dbo.TryGetNextSeqValue
  @schema sysname,
  @seq    sysname,
  @value  sql_variant OUTPUT
AS
BEGIN
  SET NOCOUNT ON;

-- 事前検査(任意だが推奨)
IF OBJECT_ID(QUOTENAME(@schema) + N'.' + QUOTENAME(@seq), 'SO') IS NULL
THROW 50001, N'シーケンスが存在しません: ' + @schema + N'.' + @seq, 1;

DECLARE @sql nvarchar(4000) =
N'SELECT @v = NEXT VALUE FOR ' + QUOTENAME(@schema) + N'.' + QUOTENAME(@seq) + N';';

BEGIN TRY
EXEC sp_executesql @sql, N'@v sql_variant OUTPUT', @v = @value OUTPUT;
END TRY
BEGIN CATCH
-- ここで捕捉できる/しやすい
DECLARE @msg nvarchar(4000) =
N'TryGetNextSeqValue 失敗: ' + ERROR_MESSAGE();
THROW 50002, @msg, 1;
END CATCH
END
GO

-- 呼び出し側
DECLARE @v sql_variant;
BEGIN TRY
EXEC dbo.TryGetNextSeqValue @schema = N'dbo', @seq = N'Seq_Typo', @value = @v OUTPUT;
SELECT @v AS NextValue;
END TRY
BEGIN CATCH
PRINT N'呼び出し側で捕捉:' + ERROR_MESSAGE();
END CATCH;

動的 SQL は最小限にとどめ、QUOTENAME を徹底し、呼び出し側でも例外を握りつぶさないことが重要です。

アプリケーション層での防御(推奨)

DB 層での回避に加え、アプリケーション層でも例外を捕捉し、必要ならリトライ・通知・フォールバック(代替の採番方式など)を実装します。ADO.NET / EF / JDBC いずれでも、オブジェクト未存在や権限不足を示すエラーを例外クラスとメッセージで判別できます。

CI/CD での静的解析(未解決参照の検出)

デプロイ前に「未解決の参照」を洗い出します。プロシージャやスクリプトの依存関係は sys.sql_expression_dependencies から抽出できます。referenced_id IS NULL は未解決参照のサインです(すべてのケースを網羅できるわけではありませんが、早期警告として有効)。

SELECT
    OBJECT_SCHEMA_NAME(d.referencing_id) AS referencing_schema,
    OBJECT_NAME(d.referencing_id)        AS referencing_object,
    d.referenced_schema_name,
    d.referenced_entity_name,
    d.is_ambiguous
FROM sys.sql_expression_dependencies AS d
WHERE d.referenced_id IS NULL
ORDER BY referencing_schema, referencing_object;

実戦テンプレート:トランザクション + ロギング + リカバリ

コンパイル時/実行時の双方に備えた“型”を示します。TRY…CATCH 内外の責任分担、XACT_STATE() によるロールバック判断、THROW の再送出を標準化します。

CREATE OR ALTER PROCEDURE dbo.Process_With_Sequence
AS
BEGIN
  SET NOCOUNT ON;
  SET XACT_ABORT ON;  -- ステートメント失敗でトランザクションを確実に中断

BEGIN TRAN;

BEGIN TRY
DECLARE @next sql_variant;


  -- 事前検査 + ベストエフォートの取得
  IF OBJECT_ID(N'[dbo].[Seq_Demo]', 'SO') IS NULL
      THROW 50010, N'必須のシーケンスが存在しません: dbo.Seq_Demo', 1;

  EXEC dbo.TryGetNextSeqValue @schema = N'dbo', @seq = N'Seq_Demo', @value = @next OUTPUT;

  -- ここから業務処理
  INSERT INTO dbo.SomeTable(KeyCol, ...)
  VALUES (CONVERT(bigint, @next), ...);

  COMMIT;


END TRY
BEGIN CATCH
IF XACT_STATE() <> 0 ROLLBACK;
DECLARE
@n int = ERROR_NUMBER(),
@s int = ERROR_SEVERITY(),
@st int = ERROR_STATE(),
@p nvarchar(128) = ISNULL(ERROR_PROCEDURE(), N'(ad-hoc)'),
@l int = ERROR_LINE(),
@m nvarchar(2048) = ERROR_MESSAGE();


  -- 監査テーブルなどにログ(例)
  INSERT INTO dbo.ErrorLog(OccuredAt, Number, Severity, State, ProcedureName, Line, Message)
  VALUES (SYSDATETIMEOFFSET(), @n, @s, @st, @p, @l, @m);

  -- アプリ層に明確なメッセージで再送出
  THROW 50100, N'Process_With_Sequence 失敗: ' + @m, 1;


END CATCH
END
GO

よくある落とし穴と対策

  • スキーマ未指定:NEXT VALUE FOR は必ず二部名(schema.object)で書きます。デフォルトスキーマの違いで誤参照・未参照が起こりやすい。
  • 権限不足と未存在の区別:どちらも似たエラーメッセージになりがち。カタログで存在チェックしても、最終的な実行は権限で失敗し得ます。外側スコープの TRY…CATCH と監査ログで診断可能に。
  • テスト環境との差分:シーケンスの作成忘れ・スキーマ名の差異が多発。CREATE SEQUENCE IF NOT EXISTS がないため、デプロイスクリプトに冪等な作成分岐を入れておくと安全です。
  • キャッシュ設定:CACHE を有効にすると性能は向上しますが、サービス停止時に欠番が生じ得ます。業務要件に応じて NOCACHE かアプリ側の期待値を調整。
  • リコンパイル:OPTION (RECOMPILE) は最適化再実行を促しますが、名前解決の性質は変わりません。未存在は未存在のままです。
  • 関数・ビューでの使用:NEXT VALUE FOR の使用可否には制約があります。可能でも非決定的であるため、安易に組み込まず専用の取得ルーチンで一元化するのが無難です。

“捕捉できないものはある”を前提に設計する

SQL Server のエラーハンドリングは歴史的経緯から一貫しない点が残っています。すべてのエラーを同一スコープで拾うのは不可能、という前提で設計してください。以下の多層防御をおすすめします。

  1. モジュール分割 + 外側 TRY…CATCH:コンパイル系エラーは上位に伝播させる。
  2. 事前検査:OBJECT_ID(..., 'SO') または sys.sequences で存在をチェック。
  3. 動的 SQL の最小利用:必要時のみ。QUOTENAME 徹底。
  4. アプリ層の例外処理:ユーザー向けメッセージ、監査、アラート。
  5. CI の静的解析:未解決参照を検出してビルドを失敗させる。

解決策の選定表

解決策効果実装コスト副作用・注意点推奨度
外側スコープでラップ高い(誤記も捕捉可能)低呼び出し点の統一が必要◎
事前存在チェック高い(早期検知)低権限不足は別途対処◎
動的 SQL + sp_executesql中(ケースにより有効)中保守性・セキュリティ配慮必須◯
アプリ層で吸収高い(UI/UX を守れる)中DB ロジックとの役割分担が鍵◎
CI の静的解析中(早期に芽を摘む)中完全ではないが強力◯

スキーマをまたぐ・複数 DB をまたぐ場合の指針

  • 常に完全修飾名:Database.Schema.Object まで書く運用に統一すると、接続先の既定 DB や既定スキーマに依存しません。
  • 権限モデル:EXECUTE AS OWNER やモジュール サイニングで、呼び出し元に権限を委譲する設計も検討。
  • 依存関係の可視化:シーケンスは sys.objects(type=SO)にも出るため、参照関係の棚卸しに活用できます。

テスト戦略:壊しながら確認する

運用前に以下の観点でテストすると安心です。

  • シーケンス名を誤記させるテスト(CATCH できること/できないことの境界を確認)。
  • シーケンスのスキーマだけを変えるテスト(dbo 以外)。
  • 権限を剥がしたユーザーでの実行(存在はするが使えない)。
  • トランザクション中に取得 → 例外 → ロールバックの整合性確認(XACT_STATE())。
  • 同時実行(大量の NEXT VALUE FOR)での性能と欠番の扱い。

ミニ FAQ

Q. 事前に存在チェックしても、本番で CATCH に入らずに止まることがあるのはなぜ?
A. チェックから実行までのわずかな間に、オブジェクトが削除・リネーム・権限変更される“TOCTOU”問題がありえます。完全には防げないため、外側スコープでの捕捉とリカバリを併用してください。

Q. バージョン依存はある?
A. シーケンスが導入された SQL Server 2012 以降で同様の傾向です。互換性レベルを変えても、名前解決とコンパイル時エラーの扱いという根本は同じです。

Q. RAISERROR と THROW のどちらを使うべき?
A. 新規開発は THROW を推奨します。RAISERROR は互換のために残っていますが、THROW の方がスタックや番号の再送出などで扱いやすいです。

チェックリスト:採番に NEXT VALUE FOR を使う前に

  • 二部名(できれば三部名)での完全修飾を徹底しているか。
  • モジュールを分割し、外側スコープに TRY…CATCH を置いているか。
  • 事前存在チェックと、わかりやすい THROW メッセージを実装しているか。
  • 例外をアプリ層で捕捉し、ユーザー/オペレーターに伝える仕組みがあるか。
  • CI で未解決参照(referenced_id IS NULL)を検出しているか。
  • ロギング(ERROR_* 関数)とアラート運用が整っているか。

まとめ

  • シーケンス名誤記などの「オブジェクト未存在」系は、同一スコープの TRY…CATCH では捕捉できない――遅延名前解決とコンパイル時エラーの性質による仕様です。
  • 捕捉したい/サービスを落としたくないなら、外側スコープでラップし、事前存在チェックを入れ、必要に応じて動的 SQLで名前解決を分離します。
  • トランザクション制御(XACT_ABORT・XACT_STATE())とアプリ層の例外処理、CI の静的解析を組み合わせた多層防御が実運用の鍵です。
  • divide-by-zero などの通常の実行時エラーは TRY…CATCH で捕捉可能。性質の異なるエラーを混同しない設計が、障害時の“止まり方”を決めます。

この記事を書いた人

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

コメント

コメントする

目次