SQLer 生島勘富 のブログ

RDB・SQLの話題を中心に情報発信をしています。

CODE VS 2.1 をSQLでやるとどうなる7

 前回、こんな「落ちゲー」の

 ブロックを一つ一つ加算していって、合計が10になる組合せをSQLで探しました。

 今回は消すお邪魔ブロック(最初から10以上(= 11)のブロック)を探すSQLを書きます。

繋げるか、一旦テーブルに書き出すか。

 前回でも充分な長さのある SQL になりました。
 更に繋げるのが良いか悪いかは微妙で、当初の設計では、Deletedテーブルに一旦保存するつもりでした。
 しかし、繋げた方がパフォーマンスが出そうなので繋げることにします。
 嫌な人は、前回の結果をテーブルに書き出してから続けましょう。

 前回は4方向に加算していきましたが、今回は消えるブロックの周りにあるお邪魔ブロックを探すことになりますので、1回だけですみます(再帰なし)また、お邪魔ブロックからは逆向きに探してこないため、4方向ではなく8方向を調査することになります。

 4方向用のDirection4テーブルと、8方向用のDirection8テーブルの2つ作っているのは、単純にサブクエリーが増えるのを避けたかったからだけで、サブクエリーでも良いと思う人は、Direction8に区分を追加してサブクエリーでやってください。

 ちと長い……。

WITH
	cb (GameID, SimulationID, Turn, Row, Col
		, ERow, ECol, Num, Amount, Cnt, Angle, Memo) -- 4方向に合算された結果
	AS(
	SELECT
		GameID
		, SimulationID
		, Turn
		, Row
		, Col
		, Row AS ERow
		, Col AS ECol
		, Num
		, Num AS amount
		, 1 AS cnt
		, 0 --調べている位置
		, CAST(N'調査' AS nvarchar(8)) AS Memo
	FROM Board
	WHERE GameID =-1 AND Turn = 0	AND Num < 10 
	UNION ALL
	SELECT 
		cb.GameID
		, cb.SimulationID
		, cb.Turn
		, cb.Row			-- 調査元の行
		, cb.Col		-- 調査元の列
		, bd.Row		-- 調査先の行
		, bd.Col		-- 調査先の列
		, bd.Num
		, cb.Amount + bd.Num
		, Cnt + 1 
		, dr.Angle
		, dr.Memo
	FROM 
		Board AS bd
		INNER JOIN (cb
		CROSS JOIN Direction4 AS dr)
			ON bd.Row = cb.Row + (dr.RowOffset * Cnt)
			AND bd.Col = cb.Col + (dr.ColOffset * Cnt)
			AND bd.GameID = cb.GameID
			AND bd.SimulationID = cb.SimulationID
			AND bd.turn = cb.turn
	WHERE
		cb.amount + bd.num <= 10
	)
-- SELECT * FROM cb; -- として、以下をコメントアウトすれば4方向に合算した結果が確認できる
	, eb (GameID, SimulationID, Turn, Row, Col
		, ERow, ECol, Num, Amount, Cnt, Angle, Memo) -- 消すブロック
	AS(
	SELECT * 
	FROM cb AS bd
		WHERE EXISTS (
			SELECT * FROM cb 
			WHERE bd.row = cb.row 
				AND bd.col = cb.col
				AND (bd.angle = 0 OR bd.angle = cb.angle)
				AND cb.amount = 10
			)
	) -- WITHの終わり
-- SELECT * FROM cb; -- として、以下をコメントアウトすれば4方向に合算した結果が確認できる
-- SELECT * FROM eb;  -- として、以下をコメントアウトすれば消すべきブロックを確認できる
	, ob (Row, Col, ERow, ECol, Num, Angle, Memo) -- お邪魔ブロック
	AS(
	SELECT 
		eb.ERow			-- 調査元の行
		, eb.ECol		-- 調査元の列
		, bd.Row		-- 調査先の行
		, bd.Col		-- 調査先の列
		, bd.Num
		, dr.Angle
		, dr.Memo
	FROM
		eb -- 上のSQLで作成した消すべきブロックの一覧
		INNER JOIN (Board AS bd
		CROSS JOIN Direction8 AS dr)
			ON eb.ERow + dr.RowOffset = bd.Row
			AND eb.ECol + dr.ColOffset = bd.Col
	WHERE
		bd.GameID = -1 AND bd.Turn = 0
		AND bd.Num > 10
	)
-- SELECT * FROM cb;  --として、以下をコメントアウトすれば4方向に合計していったときの結果が確認できる
-- SELECT * FROM eb;  --として、以下をコメントアウトすれば消すべきブロックを確認できる
-- SELECT * FROM ob;  --として、以下をコメントアウトすれば消すべきお邪魔ブロックが確認できる
	, curEb (ERow, ECol) -- このチェインで消すブロック(重複あり)
	AS (
		SELECT ERow, ECol FROM eb -- 通常のブロックは重複あり
		UNION ALL 
		SELECT ERow, ECol FROM ob GROUP BY ERow, ECol -- お邪魔ブロックは重複なし
	)
-- SELECT * FROM cb;  --として、以下をコメントアウトすれば4方向に合計していったときの結果が確認できる
-- SELECT * FROM eb;  --として、以下をコメントアウトすれば消すべきブロックが確認できる
-- SELECT * FROM ob;  --として、以下をコメントアウトすれば消すべきお邪魔ブロックが確認できる
SELECT * FROM curEb; --今回のチェインで消すべきブロックの数が確認出来る。

つづく