advanced-patterns について
複雑な SQL Server クエリやデータベースパターンを扱う開発者向けの T-SQL リファレンススキルです。CTE、データ変更、テンポラルテーブル、OLTP などを例と選択指針で説明します。
主な機能
- 01階層・順位クエリ向けの CTE
- 02CROSS APPLY と OUTER APPLY のパターン
- 03MERGE による更新挿入と OUTPUT 捕捉
- 04ROLLUP、CUBE、GROUPING SETS
- 05テンポラルテーブルと In-Memory OLTP
- 06テーブル値コンストラクターとシーケンス
活用シーン
- 01再帰的な階層クエリの作成
- 02グループごとの上位行の抽出
- 03更新時の変更行の捕捉
- 04テーブルの履歴バージョンの検索
インストール
コマンドをコピーしてターミナルで実行します。
$
npx skills add https:/ / github.com/ josiahsiegel/ claude-plugin-marketplace --skill advanced-patternsインストール後、Claude Code で/advanced-patternsを入力して読み込まれたか確認します。
詳細
高度な T-SQL パターン
複雑な SQL Server のシナリオに対応する高度なテクニックです。
クイックリファレンス
パターン選択ガイド
| タスク | パターン |
|---|---|
| 階層データ | 再帰 CTE |
| グループごとの上位 N 件 | ROW_NUMBER + CTE |
| 相関サブクエリの代替 | CROSS/OUTER APPLY |
| アップサート(挿入または更新) | MERGE |
| 変更された行の取得 | OUTPUT 句 |
| 履歴データの追跡 | テンポラルテーブル |
| 高スループット OLTP | In-Memory OLTP |
| 複数の集計レベル | ROLLUP/CUBE/GROUPING SETS |
共通テーブル式(CTE)
基本的な CTE
WITH RecentOrders AS (
SELECT CustomerID, OrderDate, Amount
FROM Orders
WHERE OrderDate >= DATEADD(month, -3, GETDATE())
)
SELECT c.CustomerName, r.Amount
FROM Customers c
JOIN RecentOrders r ON c.CustomerID = r.CustomerID;
複数の CTE
WITH
Sales AS (
SELECT ProductID, SUM(Amount) AS TotalSales FROM Orders GROUP BY ProductID
),
Inventory AS (
SELECT ProductID, SUM(Quantity) AS TotalInventory FROM Stock GROUP BY ProductID
)
SELECT p.ProductName, s.TotalSales, i.TotalInventory
FROM Products p
LEFT JOIN Sales s ON p.ProductID = s.ProductID
LEFT JOIN Inventory i ON p.ProductID = i.ProductID;
再帰 CTE(階層)
WITH OrgChart AS (
-- Anchor: Top-level (no manager)
SELECT EmployeeID, Name, ManagerID, 0 AS Level,
CAST(Name AS VARCHAR(1000)) AS Path
FROM Employees
WHERE ManagerID IS NULL
UNION ALL
-- Recursive: Subordinates
SELECT e.EmployeeID, e.Name, e.ManagerID, oc.Level + 1,
CAST(oc.Path + ' > ' + e.Name AS VARCHAR(1000))
FROM Employees e
JOIN OrgChart oc ON e.ManagerID = oc.EmployeeID
)
SELECT * FROM OrgChart
OPTION (MAXRECURSION 100); -- Default is 100, max is 32767
重複行を削除する CTE
WITH Duplicates AS (
SELECT *,
ROW_NUMBER() OVER (
PARTITION BY Email
ORDER BY CreatedDate DESC
) AS RowNum
FROM Users
)
DELETE FROM Duplicates WHERE RowNum > 1;
APPLY 演算子
CROSS APPLY(内部結合の動作)
-- Top 3 orders per customer
SELECT c.CustomerID, c.Name, o.OrderID, o.Amount
FROM Customers c
CROSS APPLY (
SELECT TOP 3 OrderID, Amount
FROM Orders
WHERE CustomerID = c.CustomerID
ORDER BY OrderDate DESC
) o;
OUTER APPLY(左外部結合の動作)
-- Last order per customer (including customers with no orders)
SELECT c.CustomerID, c.Name, o.LastOrderDate, o.LastOrderAmount
FROM Customers c
OUTER APPLY (
SELECT TOP 1 OrderDate AS LastOrderDate, Amount AS LastOrderAmount
FROM Orders
WHERE CustomerID = c.CustomerID
ORDER BY OrderDate DESC
) o;
テーブル値関数での APPLY
-- Call function for each row
SELECT c.CustomerID, f.MonthlyTotal, f.OrderCount
FROM Customers c
CROSS APPLY dbo.GetCustomerMonthlyStats(c.CustomerID) f;
列のピボット解除に使用する APPLY
-- Transform columns to rows
SELECT ID, AttributeName, AttributeValue
FROM Products
CROSS APPLY (
VALUES
('Color', Color),
('Size', Size),
('Weight', CAST(Weight AS VARCHAR))
) AS Unpivoted(AttributeName, AttributeValue)
WHERE AttributeValue IS NOT NULL;
MERGE ステートメント
基本的なアップサート
MERGE INTO TargetTable AS t
USING SourceTable AS s
ON t.ID = s.ID
WHEN MATCHED THEN
UPDATE SET t.Name = s.Name, t.Value = s.Value, t.UpdatedAt = GETDATE()
WHEN NOT MATCHED BY TARGET THEN
INSERT (ID, Name, Value, CreatedAt)
VALUES (s.ID, s.Name, s.Value, GETDATE())
WHEN NOT MATCHED BY SOURCE THEN
DELETE
OUTPUT $action, inserted.*, deleted.*;
条件付き MERGE
MERGE INTO Products AS t
USING StagingProducts AS s
ON t.ProductID = s.ProductID
WHEN MATCHED AND s.Price <> t.Price THEN
UPDATE SET t.Price = s.Price, t.LastModified = GETDATE()
WHEN MATCHED AND s.Discontinued = 1 THEN
DELETE
WHEN NOT MATCHED THEN
INSERT (ProductID, Name, Price) VALUES (s.ProductID, s.Name, s.Price);
OUTPUT 句
挿入された行を取得
DECLARE @InsertedRows TABLE (ID INT, Name VARCHAR(100));
INSERT INTO Customers (Name, Email)
OUTPUT inserted.CustomerID, inserted.Name INTO @InsertedRows
VALUES ('John', '[email protected]'), ('Jane', '[email protected]');
SELECT * FROM @InsertedRows;
更新された行を取得(更新前と更新後)
UPDATE Products
SET Price = Price * 1.1
OUTPUT deleted.ProductID, deleted.Price AS OldPrice, inserted.Price AS NewPrice
WHERE Category = 'Electronics';
削除された行を取得
DELETE FROM ExpiredOrders
OUTPUT deleted.*
INTO OrderArchive
WHERE ExpiryDate < DATEADD(year, -1, GETDATE());
高度なグループ化
ROLLUP(階層的な小計)
SELECT
COALESCE(Region, 'Total') AS Region,
COALESCE(Product, 'All Products') AS Product,
SUM(Sales) AS TotalSales
FROM SalesData
GROUP BY ROLLUP (Region, Product);
-- Groups: (Region, Product), (Region), ()
CUBE(すべての組み合わせ)
SELECT Region, Product, SUM(Sales) AS TotalSales
FROM SalesData
GROUP BY CUBE (Region, Product);
-- Groups: (Region, Product), (Region), (Product), ()
GROUPING SETS(カスタムの組み合わせ)
SELECT Region, Product, Year, SUM(Sales)
FROM SalesData
GROUP BY GROUPING SETS (
(Region, Product),
(Region, Year),
(Product),
()
);
グループ化レベルの特定
SELECT
CASE WHEN GROUPING(Region) = 1 THEN 'All' ELSE Region END AS Region,
CASE WHEN GROUPING(Product) = 1 THEN 'All' ELSE Product END AS Product,
SUM(Sales) AS TotalSales,
GROUPING_ID(Region, Product) AS GroupLevel
-- GroupLevel: 0 = both, 1 = Product rolled up, 2 = Region rolled up, 3 = both
FROM SalesData
GROUP BY ROLLUP (Region, Product);
テンポラルテーブル(SQL 2016 以降)
システムバージョン管理テーブルの作成
CREATE TABLE Products (
ProductID INT PRIMARY KEY,
Name NVARCHAR(100),
Price DECIMAL(18,2),
ValidFrom DATETIME2 GENERATED ALWAYS AS ROW START,
ValidTo DATETIME2 GENERATED ALWAYS AS ROW END,
PERIOD FOR SYSTEM_TIME (ValidFrom, ValidTo)
)
WITH (SYSTEM_VERSIONING = ON (HISTORY_TABLE = dbo.ProductsHistory));
履歴データのクエリ
-- Point in time
SELECT * FROM Products
FOR SYSTEM_TIME AS OF '2024-01-01 12:00:00';
-- Time range
SELECT * FROM Products
FOR SYSTEM_TIME BETWEEN '2024-01-01' AND '2024-06-30';
-- All history
SELECT * FROM Products
FOR SYSTEM_TIME ALL;
In-Memory OLTP
メモリ最適化テーブルの作成
-- First add filegroup
ALTER DATABASE YourDB
ADD FILEGROUP MemOptFG CONTAINS MEMORY_OPTIMIZED_DATA;
ALTER DATABASE YourDB
ADD FILE (NAME = 'MemOptFile', FILENAME = 'C:\Data\MemOpt') TO FILEGROUP MemOptFG;
-- Create table
CREATE TABLE OrdersMemOpt (
OrderID INT NOT NULL PRIMARY KEY NONCLUSTERED HASH WITH (BUCKET_COUNT = 1000000),
CustomerID INT NOT NULL INDEX IX_Customer NONCLUSTERED HASH WITH (BUCKET_COUNT = 100000),
OrderDate DATETIME2 NOT NULL,
Amount DECIMAL(18,2) NOT NULL,
INDEX IX_Date NONCLUSTERED (OrderDate)
) WITH (MEMORY_OPTIMIZED = ON, DURABILITY = SCHEMA_AND_DATA);
ネイティブコンパイルされたプロシージャ
CREATE PROCEDURE InsertOrderFast
@CustomerID INT,
@Amount DECIMAL(18,2)
WITH NATIVE_COMPILATION, SCHEMABINDING
AS
BEGIN ATOMIC WITH (TRANSACTION ISOLATION LEVEL = SNAPSHOT, LANGUAGE = N'English')
INSERT INTO dbo.OrdersMemOpt (OrderID, CustomerID, OrderDate, Amount)
VALUES (NEXT VALUE FOR dbo.OrderSeq, @CustomerID, SYSDATETIME(), @Amount);
END;
テーブル値コンストラクター
テーブルとしての VALUES
SELECT * FROM (
VALUES
(1, 'Apple', 1.50),
(2, 'Banana', 0.75),
(3, 'Orange', 2.00)
) AS Products(ID, Name, Price);
MERGE での使用
MERGE INTO Products AS t
USING (VALUES
(1, 'Apple', 1.60),
(2, 'Banana', 0.80)
) AS s(ID, Name, Price)
ON t.ID = s.ID
WHEN MATCHED THEN UPDATE SET Price = s.Price
WHEN NOT MATCHED THEN INSERT VALUES (s.ID, s.Name, s.Price);
シーケンス
シーケンスの作成と使用
CREATE SEQUENCE OrderSeq
AS INT START WITH 1 INCREMENT BY 1;
-- Get next value
SELECT NEXT VALUE FOR OrderSeq;
-- Use in INSERT
INSERT INTO Orders (OrderID, CustomerID)
VALUES (NEXT VALUE FOR OrderSeq, @CustomerID);
-- Use as default
ALTER TABLE Orders
ADD CONSTRAINT DF_OrderID DEFAULT NEXT VALUE FOR OrderSeq FOR OrderID;
コメント
Markdown 対応