我有一个表(称之为my_table
),可以像这样简化:NAME,SEQ_NO,LOCKED。
项目被删除并添加,我想重新排序它们(修改SEQ_NO),顺序总是从1到COUNT(*),被锁定的项目保留它们的SEQ_NO,没有解锁的项目会得到那个数字。仅使用新的SEQ_NO更新未锁定的项目。
示例:
此
NAME SEQ_NO LOCKED Foo 1 N Bar 3 Y Abc 4 Y Baz 5 N Cde 7 N
会导致:
NAME SEQ_NO LOCKED Foo 1 N Baz 2 N Bar 3 Y Abc 4 Y Cde 5 N
我怎么能这样做?
答案 0 :(得分:8)
有些时候,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} 。然后,我们可以在第一个字段上加入这两个列表A和B,并抛弃排序,留下成对的 new:old 插槽编号 C {1:1,2:5 ,5:7} 。锁定的行会生成一组 new:old 值,其中 new = old ,因此 D {3:3,4:4} 。最终结果是C和D的并集,因此结果集包含:
这适用于锁定行的序号为13和14的情况;解锁的行被分配新的序列号1,2,3,并且锁定的行保持不变。该问题的一个评论是关于锁定,5解锁,10锁定&#39 ;;这将产生锁定,2解锁,10锁定'。
在SQL中实现这一点需要相当多的SQL。掌握OLAP功能的人可能比我的代码更快地到达那里。将SELECT结果转换为UPDATE语句也很棘手(并没有完全解决)。但是能够以正确的结果顺序获取数据是至关重要的,解决这个问题的关键是列表A和B所代表的排序步骤。
与任何复杂的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;我们可以做其中一个可怕的小非等值技巧,但应该有更好的方法:
-- 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
现在我们需要对那些进行排名,这意味着另一个自连接,但这次是在那个表达式上。使用时间&#39;公用表格表达式&#39;或CTE,也称为&#39; WITH子句&#39;:
-- 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语句。留给我自己的设备,我考虑一个选择查询8的结果到临时表的事务,然后删除源表(My_Table
)中的所有记录并插入适当的项目查询8的结果进入原始表然后提交。
Oracle似乎并不支持每个会话动态创建的内容&#39;临时表;只有全局临时表。并且有充分的理由不使用它们,因为它们都是SQL Standard。尽管如此,它还是可以解决这个问题,我不确定还能发挥什么作用:
与此工作分开:
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;
您可以使用适当的更新来完成此操作;你需要做一些思考。
这不容易,但可以做到。
键步骤(至少对我来说)是 Query 6 的结果集,它计算出更新结果集中未锁定行的新位置。这并不是很明显,但产生答案至关重要。
其余的只是围绕关键步骤的支持代码。
如前所述,可能有很多方法可以改进某些查询。例如,从表中生成序列1..N
可能就像SELECT ROWNUM FROM My_Table
一样简单,这会压缩查询(非常有用 - 它是详细的)。有OLAP功能;其中一个或多个可以帮助进行排名操作(可能更简洁;也可以更好地表现)。
所以,这不是一个优秀的最终答案;但这是对正确方向的有力推动。
代码已针对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;
答案 1 :(得分:4)
merge into my_table
using (
select rowid as rid,
row_number() over (order by seq_no) as rn
from my_table
where locked = 'N'
) t on (t.rid = my_table.rowid)
when matched then update
set seq_no = t.rn;
答案 2 :(得分:2)
这可能无法获取所有数据案例,但它适用于示例数据:
update my_table mt
set seq_no =
(with renumber as (select /*+ MATERIALIZE */ rownum rn, name, seq_no, locked
from
(
select * from my_table
where locked = 'N'
order by seq_no
)
)
select rn from renumber rn where rn.seq_no = mt.seq_no
)
where locked = 'N'
;
完全解决了以下示例:
create table my_table as
select 'Foo' name, 1 seq_no, 'N' locked from dual union
select 'Bar' name, 3 seq_no, 'Y' locked from dual union
select 'Baz' name, 5 seq_no, 'N' locked from dual
order by seq_no
;
select * from my_table
order by seq_no
;
update my_table mt
set seq_no =
(with renumber as (select /*+ MATERIALIZE */ rownum rn, name, seq_no, locked
from
(
select * from my_table
where locked = 'N'
order by seq_no
)
)
select rn from renumber rn where rn.seq_no = mt.seq_no
)
where locked = 'N'
;
select * from my_table
order by seq_no
;
答案 3 :(得分:1)
WITH
yourTable_unlocked_resequenced
AS
(
SELECT
yourTable.*,
CASE WHEN
yourTable.locked ='N'
THEN
ROW_NUMBER() OVER (PARTITION BY locked ORDER BY seq_no)
END AS unlocked_seq_no
FROM
yourTable
)
,
master_list
AS
(
SELECT
ROW_NUMBER() OVER (ORDER BY seq_no) AS seq_no
FROM
yourTable
)
,
master_list_unlocked_resequenced
AS
(
SELECT
master_list.seq_no,
ROW_NUMBER() OVER (PARTITION BY yourTable.locked ORDER BY master_list.seq_no) AS unlocked_seq_no
FROM
master_list
LEFT JOIN
yourTable
ON yourTable.seq_no = master_list.seq_no
AND yourTable.locked = 'Y'
WHERE
yourTable.locked IS NULL
)
SELECT
original.*,
modified.*,
COALESCE(modified.seq_no, original_seq_no) AS final_seq_no
FROM
yourTable_unlocked_resequenced AS original
LEFT JOIN
master_list_unlocked_resequenced AS modified
ON original.unlocked_seq_no = modified.unlocked_seq_no
可能有可能将其浓缩,但我会说它有效。
使用您当前的值......
Seq_No 1 3 4 5 7
Locked N Y Y N N
Unlocked_Seq_No 1 2 3
Seq_No 1 2 3 4 5
Unlocked_Seq_No 1 2 3
Original_Seq_No 1 3 4 5 7
Modified_Seq_No 1 3 4 2 5
如果您稍微更改了数据,则会得到以下内容,其中一个值(8
)位于序列之外,因为它已被锁定...
Seq_No 1 3 5 7 8
Locked N Y N N Y
Unlocked_Seq_No 1 2 3
Seq_No 1 2 3 4 5
Unlocked_Seq_No 1 2 3 4
Original_Seq_No 1 3 5 7 8
Modified_Seq_No 1 3 2 4 8