tsql について
SQL Server で T-SQL を記述する開発者向けのリファレンススキルです。ストアドプロシージャ、関数、制御フロー、エラー処理、トリガー、トランザクション、CTE と、保守性や安全性に配慮した実践方法を扱います。
主な機能
- 01ストアドプロシージャと関数の作成
- 02制御フローと CTE の利用
- 03TRY...CATCH によるエラー処理
- 04トリガーとトランザクションの管理
- 05動的 SQL とクエリの実践方法
活用シーン
- 01SQL Server の業務ロジックを実装
- 02プロシージャ内のエラーとトランザクションを処理
- 03T-SQL の一般的な問題を確認
インストール
コマンドをコピーしてターミナルで実行します。
$
npx skills add https:/ / github.com/ claude-dev-suite/ claude-dev-suite --skill tsqlインストール後、Claude Code で/tsqlを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
advanced.mdquick-ref/error-handling.mdfunctions.mdprocedures.md詳細
T-SQL の基本知識
完全なリファレンス:複数ステートメントの TVF、カスタムエラーメッセージ、INSTEAD OF トリガー、トランザクション分離レベル、カーソル、動的 SQL、再帰 CTE については、advanced.md を参照してください。
詳細な知識:包括的なドキュメントについては、technology に
sqlserverを指定してmcp__documentation__fetch_docsを使用してください。
基本構造
-- Anonymous block
BEGIN
DECLARE @count INT = 0;
SET @count = @count + 1;
PRINT 'Count: ' + CAST(@count AS VARCHAR);
END;
GO
変数
DECLARE @name VARCHAR(100) = 'John';
DECLARE @age INT;
DECLARE @salary DECIMAL(10,2), @bonus DECIMAL(10,2);
SET @age = 25;
SELECT @salary = salary FROM employees WHERE id = 1;
-- Multiple assignments
SELECT @salary = salary, @bonus = bonus
FROM employees WHERE id = 1;
-- Table variable
DECLARE @employees TABLE (
id INT,
name VARCHAR(100),
salary DECIMAL(10,2)
);
INSERT INTO @employees SELECT id, name, salary FROM employees;
ストアドプロシージャ
基本的なプロシージャ
CREATE OR ALTER PROCEDURE usp_GetEmployee
@EmployeeId INT
AS
BEGIN
SET NOCOUNT ON;
SELECT employee_id, first_name, last_name, salary
FROM employees
WHERE employee_id = @EmployeeId;
END;
GO
-- Execute
EXEC usp_GetEmployee @EmployeeId = 100;
-- or
EXEC usp_GetEmployee 100;
OUTPUT パラメーターを使用するプロシージャ
CREATE OR ALTER PROCEDURE usp_GetEmployeeStats
@DeptId INT,
@EmployeeCount INT OUTPUT,
@TotalSalary DECIMAL(15,2) OUTPUT,
@AvgSalary DECIMAL(15,2) OUTPUT
AS
BEGIN
SET NOCOUNT ON;
SELECT
@EmployeeCount = COUNT(*),
@TotalSalary = SUM(salary),
@AvgSalary = AVG(salary)
FROM employees
WHERE department_id = @DeptId;
END;
GO
-- Call with OUTPUT
DECLARE @Count INT, @Total DECIMAL(15,2), @Avg DECIMAL(15,2);
EXEC usp_GetEmployeeStats
@DeptId = 10,
@EmployeeCount = @Count OUTPUT,
@TotalSalary = @Total OUTPUT,
@AvgSalary = @Avg OUTPUT;
PRINT 'Count: ' + CAST(@Count AS VARCHAR);
戻り値を返すプロシージャ
CREATE OR ALTER PROCEDURE usp_ValidateEmployee
@EmployeeId INT
AS
BEGIN
SET NOCOUNT ON;
IF NOT EXISTS (SELECT 1 FROM employees WHERE employee_id = @EmployeeId)
RETURN -1; -- Not found
IF EXISTS (SELECT 1 FROM employees WHERE employee_id = @EmployeeId AND status = 'INACTIVE')
RETURN -2; -- Inactive
RETURN 0; -- Success
END;
GO
-- Check return value
DECLARE @result INT;
EXEC @result = usp_ValidateEmployee @EmployeeId = 100;
IF @result = 0
PRINT 'Valid';
ELSE IF @result = -1
PRINT 'Employee not found';
関数
スカラー関数
CREATE OR ALTER FUNCTION dbo.fn_CalculateBonus(
@Salary DECIMAL(10,2),
@YearsOfService INT
)
RETURNS DECIMAL(10,2)
AS
BEGIN
DECLARE @Bonus DECIMAL(10,2);
SET @Bonus = CASE
WHEN @YearsOfService >= 10 THEN @Salary * 0.15
WHEN @YearsOfService >= 5 THEN @Salary * 0.10
ELSE @Salary * 0.05
END;
RETURN @Bonus;
END;
GO
-- Usage
SELECT employee_id, salary,
dbo.fn_CalculateBonus(salary, years_of_service) AS bonus
FROM employees;
インラインテーブル値関数
CREATE OR ALTER FUNCTION dbo.fn_GetDeptEmployees(
@DeptId INT
)
RETURNS TABLE
AS
RETURN (
SELECT employee_id, first_name, last_name, salary
FROM employees
WHERE department_id = @DeptId
);
GO
-- Usage
SELECT * FROM dbo.fn_GetDeptEmployees(10);
制御フロー
IF...ELSE
DECLARE @status VARCHAR(20);
IF @status = 'ACTIVE'
BEGIN
PRINT 'User is active';
-- Multiple statements in BEGIN...END
END
ELSE IF @status = 'PENDING'
PRINT 'User is pending'; -- Single statement, no BEGIN needed
ELSE
BEGIN
PRINT 'User is inactive';
END;
CASE 式
SELECT
employee_id,
salary,
CASE
WHEN salary >= 100000 THEN 'Executive'
WHEN salary >= 50000 THEN 'Senior'
WHEN salary >= 30000 THEN 'Mid'
ELSE 'Junior'
END AS level
FROM employees;
-- Simple CASE
SELECT
employee_id,
CASE status
WHEN 'A' THEN 'Active'
WHEN 'I' THEN 'Inactive'
ELSE 'Unknown'
END AS status_name
FROM employees;
WHILE ループ
DECLARE @counter INT = 1;
WHILE @counter <= 10
BEGIN
PRINT 'Counter: ' + CAST(@counter AS VARCHAR);
SET @counter = @counter + 1;
IF @counter = 5
CONTINUE; -- Skip to next iteration
IF @counter = 8
BREAK; -- Exit loop
END;
エラー処理
TRY...CATCH
BEGIN TRY
BEGIN TRANSACTION;
UPDATE accounts SET balance = balance - 100 WHERE id = 1;
UPDATE accounts SET balance = balance + 100 WHERE id = 2;
COMMIT TRANSACTION;
END TRY
BEGIN CATCH
IF @@TRANCOUNT > 0
ROLLBACK TRANSACTION;
-- Error information
DECLARE @ErrorMessage NVARCHAR(4000) = ERROR_MESSAGE();
DECLARE @ErrorSeverity INT = ERROR_SEVERITY();
DECLARE @ErrorState INT = ERROR_STATE();
-- Re-throw or log
RAISERROR(@ErrorMessage, @ErrorSeverity, @ErrorState);
END CATCH;
エラー関数
| 関数 | 説明 |
|---|---|
ERROR_NUMBER() | エラー番号 |
ERROR_MESSAGE() | エラーメッセージ |
ERROR_SEVERITY() | エラー重大度(0~25) |
ERROR_STATE() | エラー状態 |
ERROR_LINE() | エラーが発生した行番号 |
ERROR_PROCEDURE() | ストアドプロシージャ名 |
THROW と RAISERROR の比較
-- THROW (SQL Server 2012+, preferred)
THROW 50001, 'Custom error message', 1;
-- THROW without parameters re-throws current error
BEGIN CATCH
INSERT INTO error_log (message, error_time)
VALUES (ERROR_MESSAGE(), GETDATE());
THROW; -- Re-throw original error
END CATCH;
-- RAISERROR (legacy)
RAISERROR('Error: %s', 16, 1, @ErrorMessage);
トリガー
DML トリガー
CREATE OR ALTER TRIGGER tr_employees_audit
ON employees
AFTER INSERT, UPDATE, DELETE
AS
BEGIN
SET NOCOUNT ON;
-- Handle INSERT
IF EXISTS (SELECT 1 FROM inserted) AND NOT EXISTS (SELECT 1 FROM deleted)
BEGIN
INSERT INTO employees_audit (action, employee_id, new_salary, changed_by, changed_at)
SELECT 'INSERT', employee_id, salary, SYSTEM_USER, GETDATE()
FROM inserted;
END
-- Handle UPDATE
IF EXISTS (SELECT 1 FROM inserted) AND EXISTS (SELECT 1 FROM deleted)
BEGIN
INSERT INTO employees_audit (action, employee_id, old_salary, new_salary, changed_by, changed_at)
SELECT 'UPDATE', i.employee_id, d.salary, i.salary, SYSTEM_USER, GETDATE()
FROM inserted i
INNER JOIN deleted d ON i.employee_id = d.employee_id;
END
-- Handle DELETE
IF NOT EXISTS (SELECT 1 FROM inserted) AND EXISTS (SELECT 1 FROM deleted)
BEGIN
INSERT INTO employees_audit (action, employee_id, old_salary, changed_by, changed_at)
SELECT 'DELETE', employee_id, salary, SYSTEM_USER, GETDATE()
FROM deleted;
END
END;
GO
トランザクション
BEGIN TRANSACTION;
-- or
BEGIN TRAN;
SAVE TRANSACTION SavePoint1;
-- Rollback to savepoint
ROLLBACK TRANSACTION SavePoint1;
COMMIT TRANSACTION;
-- or
COMMIT;
-- Check transaction count
SELECT @@TRANCOUNT;
-- Named transaction
BEGIN TRANSACTION MyTransaction;
COMMIT TRANSACTION MyTransaction;
共通テーブル式(CTE)
;WITH dept_stats AS (
SELECT
department_id,
COUNT(*) AS emp_count,
AVG(salary) AS avg_salary
FROM employees
GROUP BY department_id
)
SELECT d.department_name, ds.emp_count, ds.avg_salary
FROM departments d
INNER JOIN dept_stats ds ON d.department_id = ds.department_id;
ベストプラクティス
DO
- プロシージャで SET NOCOUNT ON を使用する
- エラー処理に TRY...CATCH を使用する
- 動的 SQL には sp_executesql を使用する
- 可能な場合は FAST_FORWARD カーソルを使用する
- 一括操作にはテーブル値パラメーターを使用する
- オブジェクト名には必ずスキーマを指定する
DON'T
- 本番コードで SELECT * を使用しない
- 文字列連結で動的 SQL を構築しない
- セットベースの操作で対応できる場合はカーソルを使用しない
- エラー処理を無視しない
- 非推奨の機能(GROUP BY ALL など)を使用しない
このスキルを使用しない場合
- 基本的な SQL Server SQL - データ型、インデックス、テンポラルテーブルには
sqlserverスキルを使用してください - PL/pgSQL(PostgreSQL) - PostgreSQL のプロシージャには
plpgsqlスキルを使用してください - PL/SQL(Oracle) - Oracle のプロシージャには
plsqlスキルを使用してください - 基本的な SQL - ANSI SQL の基本については
sql-fundamentalsを使用してください
アンチパターン
| アンチパターン | 問題 | 解決策 |
|---|---|---|
| TRY...CATCH を使用しない | エラーが気づかれない | エラー処理ブロックを追加する |
| RAISERROR を使用する | 非推奨のパターン | THROW を使用する(SQL 2012 以降) |
| SET NOCOUNT ON がない | パフォーマンスのオーバーヘッド | すべてのプロシージャに追加する |
| 動的 SQL の文字列連結 | SQL インジェクション | パラメーターを指定して sp_executesql を使用する |
| TOP に ORDER BY を指定せず SELECT を使用する | 非決定的な結果 | 常に ORDER BY を指定する |
| セットベースで対応できるのにカーソルを使用する | パフォーマンスの低下 | セット演算に書き換える |
すばやいトラブルシューティング
| 問題 | 診断 | 修正 |
|---|---|---|
| トランザクションが未コミット | SELECT @@TRANCOUNT | COMMIT または ROLLBACK を追加する |
| プロシージャの実行が遅い | SSMS の実行プラン | インデックスを追加し、クエリを書き換える |
| エラーが握りつぶされる | CATCH ブロックを確認する | THROW またはログ記録が行われるようにする |
| デッドロックの被害者 | 拡張イベント | 一貫したアクセス順序にする |
| パラメータースニッフィング | プランを比較する | OPTION (RECOMPILE) またはローカル変数を使用する |
リファレンスドキュメント
コメント
Markdown 対応