番号付けと「ロックされた行の番号を変更しない」という目標が、1..COUNT(*)
解決できない競合につながる場合があります。例えば:
NAME SEQ_NO LOCKED
Foo 1 N
Bar 13 Y
Abc 14 Y
Baz 5 N
Cde 7 N
このシナリオに必要な出力は次のとおりであると仮定します。
NAME SEQ_NO LOCKED
Foo 1 N
Baz 2 N
Cde 3 N
Bar 13 Y
Abc 14 Y
あなたの例は、ロックされていないデータが元のシーケンス番号の順序で保持されていることを示しており、ロックされたデータは明らかに新しい番号を取得していません。
元のデータに重複したシーケンス番号はないと仮定します。
クイック サマリー
面白くてトリッキーな問題です。データを並べ替えるための鍵は、ロックされていない行を配置する場所を知ることです。サンプルデータでは:
NAME OLD_SEQ LOCKED NEW_SEQ
Foo 1 N 1
Bar 3 Y 3
Abc 4 Y 4
Baz 5 N 2
Cde 7 N 5
ロックされていない行に 1 から 3 までのシーケンス番号を与えることができるので、最終的にはord:oldシーケンスA { 1:1, 2:5, 3:7 }のペアになります。結果セット 1..5 のスロットのリストを生成できます。そのスロットのリストから、ロックされた行によって保持されているスロットを削除し、並べ替えられたリスト内のロックされていない行によって占有されるスロットのリストとして { 1, 2, 5 } を残します。次に、それらにも順番に番号を付けて、ペアord:new B { 1:1, 2:2, 3:5 }を残します。次に、最初のフィールドでこれら 2 つのリスト A と B を結合し、シーケンスを射影して、新しい: 古いスロット番号のペアC { 1:1, 2:5, 5:7 }を残すことができます。ロックされた行は、new = oldの一連のnew:old値を生成します。いずれの場合も、D { 3:3, 4:4 }です。最終結果は C と D の結合であるため、結果セットには次のものが含まれます。
- 新しいシーケンス番号 1 の古いシーケンス番号 1。
- 古い 5 を新しい 2 に。
- (新しい 3 の古い 3);
- (新しい 4 の古い 4); と
- 古い 7 を新しい 5 に。
これは、ロックされた行のシーケンス番号が 13 と 14 の場合にも機能します。ロックされていない行には新しいシーケンス番号 1、2、3 が割り当てられ、ロックされている行は変更されません。質問へのコメントの 1 つは、「1 ロック、5 ロック解除、10 ロック」について質問しています。これにより、「1 ロック、2 ロック解除、10 ロック」が生成されます。
SQL でこれを行うには、かなりの量の SQL が必要です。OLAP 機能を使いこなせる人なら、私のコードよりも早くそこにたどり着けるかもしれません。また、SELECT の結果を UPDATE ステートメントに変換するのもトリッキーです (完全には解決できません)。しかし、正しい結果の順序で提示されたデータを取得できることは非常に重要であり、それを解決するための鍵は、リスト A と B で表される順序付け手順です。
TDQD — テスト駆動クエリ設計
複雑な SQL クエリ操作と同様に、秘訣はクエリを段階的に構築することです。前述のように、ロックされた行とロックされていない行を別々に扱う必要があります。この場合、ターゲットは最終的に UPDATE ステートメントですが、UPDATE のデータを生成する方法を知る必要があるため、最初に SELECT を実行します。
再番号付け可能な行
-- Query 1
SELECT Name, Seq_No
FROM My_Table
WHERE Locked = 'N'
ORDER BY Seq_No;
NAME SEQ_NO
Foo 1
Baz 5
Cde 7
必要に応じて、これらを ORDER BY 句で並べ替えることができますが、通常、サブクエリでは ORDER BY 句を使用できないため、番号を生成する必要があります。OLAP 関数を使用すると、おそらくこれをよりコンパクトに行うことができます。Oracle では、ROWNUM を使用して行番号を生成できる場合があります。特に高速ではありませんが、どの DBMS でも機能するトリックがあります。
ロックされた行からの干渉がないことを前提として、行番号を付け直しました
-- Query 2
SELECT m1.Name, m1.Seq_No AS Old_Seq, COUNT(*) AS New_Seq
FROM My_Table m1
JOIN My_Table m2
ON m1.Seq_No >= m2.Seq_No
WHERE m1.Locked = 'N' AND m2.Locked = 'N'
GROUP BY m1.Name, m1.Seq_No
ORDER BY New_Seq;
NAME Old_Seq New_Seq
Foo 1 1
Baz 5 2
Cde 7 3
これは非等結合であり、これが特に高速な操作ではない理由です。
再番号付け不可能な行
-- Query 3
SELECT Name, Seq_No
FROM My_Table
WHERE Locked = 'Y'
ORDER BY Seq_No;
NAME Seq_No
Bar 3
Abc 4
新しいシーケンス番号
数値のリスト 1..N (サンプル データでは N = 5) を取得できたとします。そのリストからロックされたエントリ (3, 4) を削除し、(1, 2, 5) を残します。それらがランク付けされると (1 = 1、2 = 2、3 = 5)、アンロックされたレコードの新しいシーケンスでランキングに参加できますが、レコードの最終シーケンス番号として別の番号を使用します。解決しなければならない小さな問題がいくつか残っています。まず、数値 1..N のそれぞれを生成します。これらの恐ろしい小さな非等結合トリックの 1 つを実行できますが、もっと良い方法があるはずです。
-- Query 4
SELECT COUNT(*) AS Ordinal
FROM My_Table AS t1
JOIN My_Table AS t2
ON t1.Seq_No >= t2.Seq_No
GROUP BY t1.Seq_No
ORDER BY Ordinal;
Ordinal
1
2
3
4
5
次に、ロックされたシーケンス番号をこのリストから削除できます。
-- Query 5
SELECT Ordinal
FROM (SELECT COUNT(*) AS ordinal
FROM My_Table t1
JOIN My_Table t2
ON t1.Seq_No <= t2.Seq_No
GROUP BY t1.Seq_No
) O
WHERE O.Ordinal NOT IN (SELECT Seq_No FROM My_Table WHERE Locked = 'Y')
ORDER BY Ordinal;
Ordinal
1
2
5
次に、それらをランク付けする必要があります。これは、別の自己結合を意味しますが、今回はその式についてです。「共通テーブル式」または「WITH 句」とも呼ばれる CTE を使用する時が来ました。
-- Query 6
WITH HoleyList AS
(SELECT ordinal
FROM (SELECT COUNT(*) ordinal
FROM My_Table t1
JOIN My_Table t2
ON t1.seq_no <= t2.seq_no
GROUP BY t1.seq_no
) O
WHERE O.Ordinal NOT IN (SELECT Seq_No FROM My_Table WHERE Locked = 'Y')
)
SELECT H1.Ordinal, COUNT(*) AS New_Seq
FROM HoleyList H1
JOIN HoleyList H2
ON H1.Ordinal >= H2.Ordinal
GROUP BY H1.Ordinal
ORDER BY New_Seq;
Ordinal New_Seq
1 1
2 2
5 3
仕上げ
そのため、その結果をクエリ 2 と結合して、ロックされていない行の最終的な数値を取得し、それをクエリ 3 と結合して、必要な出力を取得する必要があります。もちろん、出力でも Locked の正しい値を取得する必要があります。まだ段階的に進んでいます:
-- Query 7
WITH
Query2 AS
(SELECT m1.Name, m1.Seq_No AS Old_Seq, COUNT(*) AS New_Seq
FROM My_Table m1
JOIN My_Table m2 ON m1.Seq_No <= m2.Seq_No
WHERE m1.Locked = 'N' AND m2.Locked = 'N'
GROUP BY m1.Name, m1.Seq_No
),
HoleyList AS
(SELECT ordinal
FROM (SELECT COUNT(*) AS ordinal
FROM My_Table t1
JOIN My_Table t2
ON t1.seq_no <= t2.seq_no
GROUP BY t1.seq_no
) O
WHERE O.Ordinal NOT IN (SELECT Seq_No FROM My_Table WHERE Locked = 'Y')
),
Reranking AS
(SELECT H1.Ordinal, COUNT(*) AS New_Seq
FROM HoleyList H1
JOIN HoleyList H2
ON H1.Ordinal >= H2.Ordinal
GROUP BY H1.Ordinal
)
SELECT r.Ordinal, r.New_Seq, q.Name, q.Old_Seq, 'N' Locked
FROM Reranking r
JOIN Query2 q
ON r.New_Seq = q.New_Seq
ORDER BY r.New_Seq;
Ordinal New_Seq Name Old_Seq Locked
1 1 Cde 7 N
2 2 Baz 5 N
5 3 Foo 1 N
これは、クエリ 3 のバリアントと組み合わせる必要があります。
-- Query 3a
SELECT Seq_No Ordinal, Seq_No New_Seq, Name, Seq_No Old_Seq, Locked
FROM My_Table
WHERE Locked = 'Y'
ORDER BY New_Seq;
Ordinal New_Seq Name Old_Seq Locked
3 3 Bar 3 Y
4 4 Abc 4 Y
結果セット
これらの収量を組み合わせると、次のようになります。
-- Query 8
WITH
Query2 AS
(SELECT m1.Name, m1.Seq_No AS Old_Seq, COUNT(*) AS New_Seq
FROM My_Table m1
JOIN My_Table m2 ON m1.Seq_No <= m2.Seq_No
WHERE m1.Locked = 'N' AND m2.Locked = 'N'
GROUP BY m1.Name, m1.Seq_No
),
HoleyList AS
(SELECT ordinal
FROM (SELECT COUNT(*) AS ordinal
FROM My_Table t1
JOIN My_Table t2
ON t1.seq_no <= t2.seq_no
GROUP BY t1.seq_no
) O
WHERE O.Ordinal NOT IN (SELECT Seq_No FROM My_Table WHERE Locked = 'Y')
),
Reranking AS
(SELECT H1.Ordinal, COUNT(*) AS New_Seq
FROM HoleyList H1
JOIN HoleyList H2
ON H1.Ordinal >= H2.Ordinal
GROUP BY H1.Ordinal
),
Query7 AS
(SELECT r.Ordinal, r.New_Seq, q.Name, q.Old_Seq, 'N' Locked
FROM Reranking r
JOIN Query2 q
ON r.New_Seq = q.New_Seq
),
Query3a AS
(SELECT Seq_No Ordinal, Seq_No New_Seq, Name, Seq_No Old_Seq, Locked
FROM My_Table
WHERE Locked = 'Y'
)
SELECT Ordinal, New_Seq, Name, Old_Seq, Locked
FROM Query7
UNION
SELECT Ordinal, New_Seq, Name, Old_Seq, Locked
FROM Query3a
ORDER BY New_Seq;
これにより、次の結果が得られます。
Ordinal New_Seq Name Old_Seq Locked
1 1 Cde 7 N
2 2 Baz 5 N
3 3 Bar 3 Y
4 4 Abc 4 Y
5 3 Foo 1 N
そのため、データを正しく順序付けする SELECT ステートメントを作成することは (簡単ではありませんが) 可能です。
UPDATE 操作への変換
次に、その怪物を UPDATE ステートメントに入れる方法を見つける必要があります。私自身のデバイスに任せて、クエリ 8 の結果を一時テーブルに選択し、ソース テーブル ( My_Table
) からすべてのレコードを削除し、クエリ 8 の結果の適切なプロジェクトを元のテーブルを作成してからコミットします。
Oracle は、動的に作成された「セッションごと」の一時テーブルをサポートしていないようです。グローバル一時テーブルのみ。そして、それらを使用しない正当な理由があります。それらはすべて SQL 標準です。それにもかかわらず、他に何が機能するかわからない場合は、ここでトリックを実行します。
この作品とは別に:
CREATE GLOBAL TEMPORARY TABLE ReSequenceTable
(
Name CHAR(3) NOT NULL,
Seq_No INTEGER NOT NULL,
Locked CHAR(1) NOT NULL
)
ON COMMIT DELETE ROWS;
それで:
-- Query 8a
BEGIN; -- May be unnecessary and/or unsupported in Oracle
INSERT INTO ReSequenceTable(Name, Seq_No, Locked)
WITH
Query2 AS
(SELECT m1.Name, m1.Seq_No AS Old_Seq, COUNT(*) AS New_Seq
FROM My_Table m1
JOIN My_Table m2 ON m1.Seq_No <= m2.Seq_No
WHERE m1.Locked = 'N' AND m2.Locked = 'N'
GROUP BY m1.Name, m1.Seq_No
),
HoleyList AS
(SELECT ordinal
FROM (SELECT COUNT(*) AS ordinal
FROM My_Table t1
JOIN My_Table t2
ON t1.seq_no <= t2.seq_no
GROUP BY t1.seq_no
) O
WHERE O.Ordinal NOT IN (SELECT Seq_No FROM My_Table WHERE Locked = 'Y')
),
Reranking AS
(SELECT H1.Ordinal, COUNT(*) AS New_Seq
FROM HoleyList H1
JOIN HoleyList H2
ON H1.Ordinal >= H2.Ordinal
GROUP BY H1.Ordinal
),
Query7 AS
(SELECT r.Ordinal, r.New_Seq, q.Name, q.Old_Seq, 'N' Locked
FROM Reranking r
JOIN Query2 q
ON r.New_Seq = q.New_Seq
),
Query3a AS
(SELECT Seq_No Ordinal, Seq_No New_Seq, Name, Seq_No Old_Seq, Locked
FROM My_Table
WHERE Locked = 'Y'
)
SELECT Name, Ordinal, Locked
FROM Query7
UNION
SELECT Name, Ordinal, Locked
FROM Query3a;
DELETE FROM My_Table;
INSERT INTO My_Table(Name, Seq_No, Locked) FROM ReSequenceTable;
COMMIT;
おそらく適切な UPDATE でそれを行うことができます。あなたはいくつかの考えをする必要があります。
概要
簡単ではありませんが、それは可能です。
重要なステップ(少なくとも私にとって) は、更新された結果セット内のロックされていない行の新しい位置を計算するクエリ 6の結果セットでした。これはすぐにはわかりませんが、答えを出すためには重要です。
残りは、その重要なステップにラップされたサポート コードです。
前述のように、一部のクエリを改善するには多くの方法がある可能性があります。たとえば1..N
、テーブルからシーケンスを生成することは、クエリを圧縮するのと同じくらい簡単かもしれませんSELECT ROWNUM FROM My_Table
(非常に有益です — 冗長です)。OLAP 関数があります。それらの 1 つまたは複数は、ランキング操作に役立つ可能性があります (おそらく、より簡潔に言えば、パフォーマンスも向上します)。
したがって、これは洗練された最終的な回答ではありません。しかし、それは正しい一般的な方向への強力なプッシュです。
PoC テスト
コードは Informix に対してテストされています。Informix は (まだ) CTE をサポートしていないため、多少異なる表記法を使用する必要がありました。非常に便利で非常にシンプルなセッションごとの動的一時テーブルが導入されINTO TEMP <temp-table-name>
ており、そうでない場合は ORDER BY 句が表示される可能性があります。したがって、クエリ 8a を次のようにシミュレートしました。
+ BEGIN;
+ SELECT O.Ordinal
FROM (SELECT COUNT(*) AS ordinal
FROM My_Table AS t1
JOIN My_Table AS t2
ON t1.Seq_No <= t2.Seq_No
GROUP BY t1.Seq_No
) AS O
WHERE O.Ordinal NOT IN (SELECT Seq_No FROM My_Table WHERE Locked = 'Y')
INTO TEMP HoleyList;
+ SELECT * FROM HoleyList ORDER BY Ordinal;
1
2
5
+ SELECT H1.Ordinal, COUNT(*) AS New_Seq
FROM HoleyList AS H1
JOIN HoleyList AS H2
ON H1.Ordinal >= H2.Ordinal
GROUP BY H1.Ordinal
INTO TEMP ReRanking;
+ SELECT * FROM ReRanking ORDER BY Ordinal;
1|1
2|2
5|3
+ SELECT m1.Name, m1.Seq_No AS Old_Seq, COUNT(*) AS New_Seq
FROM My_Table m1
JOIN My_Table m2
ON m1.Seq_No >= m2.Seq_No
WHERE m1.Locked = 'N' AND m2.Locked = 'N'
GROUP BY m1.Name, m1.Seq_No
INTO TEMP Query2;
+ SELECT * FROM Query2 ORDER BY New_Seq;
Foo|1|1
Baz|5|2
Cde|7|3
+ SELECT r.Ordinal, r.New_Seq, q.Name, q.Old_Seq, 'N' Locked
FROM Reranking r
JOIN Query2 q
ON r.New_Seq = q.New_Seq
INTO TEMP Query7;
+ SELECT * FROM Query7 ORDER BY Ordinal;
1|1|Foo|1|N
2|2|Baz|5|N
5|3|Cde|7|N
+ SELECT Seq_NO Ordinal, Seq_No New_Seq, Name, Seq_No Old_Seq, Locked
FROM My_Table
WHERE Locked = 'Y'
INTO TEMP Query3a;
+ SELECT * FROM Query3a ORDER BY Ordinal;
3|3|Bar|3|Y
4|4|Abc|4|Y
+ SELECT Ordinal, New_Seq, Name, Old_Seq, Locked
FROM Query7
UNION
SELECT Ordinal, New_Seq, Name, Old_Seq, Locked
FROM Query3a
INTO TEMP Query8;
+ SELECT * FROM Query8 ORDER BY Ordinal;
1|1|Foo|1|N
2|2|Baz|5|N
3|3|Bar|3|Y
4|4|Abc|4|Y
5|3|Cde|7|N
+ ROLLBACK;