再帰クエリで UPDATE してみる

ネタ元:別テーブルの値で文字列を置換えしてUPDATE - Database Expert会議室


ネタ元で スカラ値関数 を使ったクエリを載せた理由は、再帰で実装したクエリを誰かが載せてくれたらいいなぁ〜という淡い期待からです。
ただ、そのままクローズした様なので、自分で調べてみました。
遠い昔に ORACLE で再帰クエリを書いた記憶はあるんですが、SQL Server だと無かったので初チャレンジ!
ORACLE と書き方が違うんですね〜、CONNECT BY 〜 を使った気がする。


SQL SERVER で 再帰クエリを使うには、ここらへんが参考になりそうです。
共通テーブル式を使用する再帰クエリ
WITH common_table_expression (Transact-SQL)
WITH が追加されたのは知ってたんですが(使った事無いけど)、これで再帰を実現するようです。

CREATE TABLE [dbo].[Table_1](
 [id] [int] IDENTITY(1,1) NOT NULL,
 [val] [nvarchar](max) NOT NULL
) ON [PRIMARY]
GO
SET IDENTITY_INSERT [dbo].[Table_1] ON
INSERT [dbo].[Table_1] ([id], [val]) VALUES (1, N'abcdefghij')
INSERT [dbo].[Table_1] ([id], [val]) VALUES (2, N'agnrfmlmn')
SET IDENTITY_INSERT [dbo].[Table_1] OFF
GO
CREATE TABLE [dbo].[Table_2](
 [mae] [nvarchar](1) NOT NULL,
 [ato] [nvarchar](1) NOT NULL
) ON [PRIMARY]
GO
INSERT [dbo].[Table_2] ([mae], [ato]) VALUES (N'a', N'b')
INSERT [dbo].[Table_2] ([mae], [ato]) VALUES (N'b', N'c')
INSERT [dbo].[Table_2] ([mae], [ato]) VALUES (N'c', N'd')
INSERT [dbo].[Table_2] ([mae], [ato]) VALUES (N'd', N'e')
INSERT [dbo].[Table_2] ([mae], [ato]) VALUES (N'e', N'f')
INSERT [dbo].[Table_2] ([mae], [ato]) VALUES (N'f', N'g')
GO
-- ここまでがテーブルとデータ作成部分
SELECT * FROM [Table_1]

BEGIN TRAN;

WITH [CTE] ([id], [repVal], [currentNum]) AS (
  SELECT 
    [Table_1].[id], 
    REPLACE([Table_1].[val], [一行取得].[mae], [一行取得].[ato]) AS [repVal], 
    [一行取得].[currentNum] 
  FROM [Table_1], (
    SELECT [mae], [ato], [ROWNUM], MIN([ROWNUM]) AS [currentNum] 
    FROM (
      SELECT [mae], [ato], ROW_NUMBER () OVER (ORDER BY [mae]) AS [ROWNUM] 
      FROM [Table_2] 
    ) AS [TMP]
    WHERE [ROWNUM] = 1
    GROUP BY [mae], [ato], [ROWNUM]
  ) AS [一行取得]
  UNION ALL 
  SELECT 
    [CTE].[id], 
    REPLACE([CTE].[repVal], A.[mae], A.[ato]) AS [repVal], 
    [CTE].[currentNum] + 1 AS [currentNum] 
  FROM (
    SELECT [mae], [ato], ROW_NUMBER () OVER (ORDER BY [mae]) AS [ROWNUM] 
    FROM [Table_2] 
  ) AS A INNER JOIN [CTE] ON ( 
    [CTE].[currentNum] + 1 = A.[ROWNUM] 
  )
) 
UPDATE [Table_1] 
SET [val] = ( 
  SELECT TOP 1 [repVal] 
  FROM [CTE] 
  WHERE [CTE].[id] = [Table_1].[id] 
  ORDER BY [currentNum] DESC 
) 

SELECT * FROM [Table_1]
ROLLBACK

GO
DROP TABLE [Table_2]
DROP TABLE [Table_1]
GO

最適かどうかは分かりませんが、ネタ元に貼り付けた スカラ値関数 を使ったクエリと同じ結果になりますね。


しかし、「こうすれば実現出来る!」っていう発想が出てくるのが凄いなぁ。再帰で UPDATE っていう発想が無かった。
再帰SQL のイメージって 階層構造 を取得する*1位しか思いつかない。勉強になりました。

*1:SQL SERVER 2008 から Hierarchyid 型が追加されているので、再帰する必要が無くなったはず。これも使った事無い…