指定したデータのセットを複製するために、これを実行してワーキング セットを取得しました。
CREATE TABLE cc (id_category INT, tree INT);
INSERT INTO cc VALUES (3,2),(3,3),(4,2),(4,3),(4,4),(5,2),(5,3),
(5,5),(6,2),(6,3),(6,6),(7,2),(7,3),(7,7);
SELECT cc.* FROM cc ORDER BY 1,2;
SQL フィドルはこちら: http://sqlfiddle.com/#!2/16249/3
これが私が問題にアプローチする方法です。まず、個別のid_category
値のリストを取得します。それは簡単です。(GROUP BY ではなく DISTINCT キーワードを使用できます。)
SELECT id_category
FROM cc
GROUP BY id_category
ORDER BY id_category
次に、それらの各行から 4 つの行を生成します。そこで、前のクエリをインライン ビューとしてラップします (一連の括弧で囲み、エイリアスを付けて、混乱全体を単なるテーブル名のように参照します。このようなもの:
SELECT c.id_category, j_.j
FROM (SELECT 1 AS j UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4) j_
JOIN (
SELECT cc.id_category
FROM cc cc
GROUP BY cc.id_category
ORDER BY cc.id_category
) c
ORDER BY c.id_category, j_.j
別のインライン ビューを使用して 1 ~ 4 の整数を返し、CROSS JOIN を実行して、個別の id_category ごとに 4 つの行を取得しています。それは基本的に私が返したい結果セットの概要を取得します...しかし、ツリー列の値(NULL以外)はありません。
ここで、バックアップを取り、別の行セットの作業を開始する必要があります。基本的には cc テーブルの順序付きセットですが、今回はツリー列の値を含みます。ここでは、正確に 4 つの行を取得することには関心がありません。ツリー列に値がある行だけです。繰り返しますが、非常に簡単です。
SELECT s.id_category_id, s.tree
FROM cc s
ORDER BY s.id_category, s.tree
しかしここで、各 id_category 内で、これらの各行に相対的な行番号を割り当てたいと考えています。次のように、そのクエリを一連の括弧で囲み、エイリアスを付けて、テーブルのように扱うことで実行できます。
SELECT @i := IF(r.id_category = @prev_idcat,@i + 1,1) AS i
, @prev_idcat := r.id_category AS id_category
, r.tree
FROM (SELECT @i := 0, @prev_idcat := NULL) i_
JOIN (
SELECT s.id_category, s.tree
FROM cc s
ORDER BY s.id_category, s.tree
) r
ユーザー変数で MySQL のトリックを使用して、個別の id_category ごとに 1 から始まる昇順の整数値を割り当てています。ここでの秘訣は、MySQL に行を順序付けさせることです (インライン ビューでは、r としてエイリアス化され、前の行の id_category をユーザー変数に「保存」して、次の行と比較できるようにします。
そして今、MySQL で Common Table Expressions を利用できるようになれば、本当に素晴らしいことになるところまで来ています。しかし、そうではないので、先に進み、インライン ビューをネストします。
そこで、これらの「行番号付け」クエリのそれぞれにエイリアスを与え、それらをテーブルのように参照します。クエリは次の形式になります...
SELECT b.*, q.*
FROM ( ) b
LEFT
JOIN ( ) q
ON q.id_category = b.id_category AND q.i = b.j
(ステートメントが実際に行っていることの概要を把握するために、これらのインライン ビューの内容は省略します。)
これは醜く見え始めますが、ここで魔法が起こります。b から id_category ごとに 4 つの行を取得し、それを q に結合して、id_category と「行番号」を照合します。これは LEFT OUTER 結合なので、b からすべての行を取得し、q から「一致する」行を選択します。
SELECT b.id_category, q.tree
FROM (SELECT c.id_category, j_.j
FROM (SELECT 1 AS j UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4
) j_
JOIN (
SELECT cc.id_category
FROM cc cc
GROUP BY cc.id_category
ORDER BY cc.id_category
) c
ORDER BY c.id_category, j_.j
) b
LEFT
JOIN (SELECT @i := IF(r.id_category = @prev_idcat,@i + 1,1) AS i
, @prev_idcat := r.id_category AS id_category
, r.tree
FROM (SELECT @i := 0, @prev_idcat := NULL) i_
JOIN (
SELECT s.id_category, s.tree
FROM cc s
ORDER BY s.id_category, s.tree
) r
) q
ON q.id_category = b.id_category AND q.i = b.j
ORDER BY b.id_category, b.j
仕様に残っているのは、id
列の値の生成だけです。テーブルに挿入する場合は、AUTO_INCREMENT 列を使用してそれを行うことができます。しかし、それがなければ、値を生成するのに最も便利な場所id
は、エイリアスとしてインラインビューにありますb
。少し微調整すると、最終的に、指定された結果セットを返すこの巨大なクエリが得られます。
SELECT b.k AS id, b.id_category, q.tree
FROM (SELECT @k := @k + 1 AS k
, c.id_category
, j_.j
FROM (SELECT @k := 0) k_
JOIN (SELECT 1 AS j UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4
) j_
JOIN (
SELECT cc.id_category
FROM cc cc
GROUP BY cc.id_category
ORDER BY cc.id_category
) c
ORDER BY c.id_category, j_.j
) b
LEFT
JOIN (SELECT @i := IF(r.id_category = @prev_idcat,@i + 1,1) AS i
, @prev_idcat := r.id_category AS id_category
, r.tree
FROM (SELECT @i := 0, @prev_idcat := NULL) i_
JOIN (
SELECT s.id_category, s.tree
FROM cc s
ORDER BY s.id_category, s.tree
) r
) q
ON q.id_category = b.id_category AND q.i = b.j
ORDER BY b.id_category, b.j
これを行セットで機能させるには、cc
テーブルへの各参照を括弧で囲まれたクエリに置き換えます。または、私が行ったように cc という名前のテーブルを作成し、クエリの結果をそこに挿入することもできます。
同じ結果セットを確実に生成する、より単純な SQL ステートメントを使用する人もいるかもしれません。もっと簡単な方法を学ぶことに非常に興味があります。