如何在一组行之后或有条件地在没有 PL/SQL 块的情况下增加 oracle 序列?
Posted
技术标签:
【中文标题】如何在一组行之后或有条件地在没有 PL/SQL 块的情况下增加 oracle 序列?【英文标题】:How to increment an oracle sequence after a set of rows or conditionally without PL/SQL block? 【发布时间】:2013-11-06 13:37:15 【问题描述】:我正在尝试执行代码重写。其中之一就是这个怪物。我有一组普通的 DML,突然之间我在一个脚本中有了这个 PL/SQL 块(请参阅当前的解决方案),这在 SQL DML 中看起来很奇怪。
最初我们决定使用 PL/SQL 块,假设"updating a column with a sequence number without increment within the group of records and incremented for the next group CANNOT BE achieved in a single SQL."
row1 - seq.nextval
row2 - seq.currval
row3 - seq.nextval
row4 - seq.currval
row5 - seq.currval
where group1 row1, row2 and group 2 row3, row4, row5
问题:如何在一组行之后或有条件地增加一个oracle序列?
数据设置:
CREATE TABLE TEMP_GP_SEQ
(
COL1 NUMBER,
COL2 NUMBER,
COL3 NUMBER,
COL4 NUMBER,
COL5 NUMBER,
COL6 NUMBER,
COL7 VARCHAR2 (10)
);
INSERT INTO TEMP_GP_SEQ VALUES(1,10,100,NULL,NULL,NULL,NULL);
INSERT INTO TEMP_GP_SEQ VALUES(1,10,101,NULL,NULL,NULL,NULL);
INSERT INTO TEMP_GP_SEQ VALUES(12,10,100,1,NULL,NULL,NULL);
INSERT INTO TEMP_GP_SEQ VALUES(1,10,100,2,NULL,NULL,NULL);
INSERT INTO TEMP_GP_SEQ VALUES(1,10,100,3,NULL,NULL,NULL);
INSERT INTO TEMP_GP_SEQ VALUES(12,10,100,1,NULL,NULL,NULL);
INSERT INTO TEMP_GP_SEQ VALUES(2,10,100,NULL,NULL,NULL,NULL);
INSERT INTO TEMP_GP_SEQ VALUES(2,10,101,NULL,NULL,NULL,NULL);
INSERT INTO TEMP_GP_SEQ VALUES(2,10,101,1,NULL,NULL,NULL);
INSERT INTO TEMP_GP_SEQ VALUES(2,10,101,1,NULL,NULL,NULL);
COMMIT;
CREATE SEQUENCE SEQ_TEMP_TEST
START WITH 1
INCREMENT BY 1;
所以我关注的记录的选择标准
SELECT
COL1,
COL3,
COL4,
COUNT ( * )
FROM
TEMP_GP_SEQ
GROUP BY
COL1,
COL3,
COL4
HAVING
COUNT ( * ) > 1;
会给我
COL1 COL3 COL4 COUNT(*)
2 101 1 2
12 100 1 2
我需要根据上面提到的分组范围使用创建的序列更新 TEMP_GP_SEQ 表和 COLUMN COL7 表。但是序列不应该为每条记录增加,它应该只随着组的变化而增加。例如
SELECT
COL1,
COL2,
COL3,
COL4,
COL7
FROM
TEMP_GP_SEQ;
期望的输出
COL1 COL2 COL3 COL4 COL7
1 10 100 NULL NULL
1 10 101 NULL NULL
12 10 100 1 M2
1 10 100 2 NULL
1 10 100 3 NULL
12 10 100 1 M2
2 10 100 NULL NULL
2 10 101 NULL NULL
2 10 101 1 M1
2 10 101 1 M1
为这四行更新COL7,以M为前缀词,M之后的数字来自序列。只有在分组标准发生变化并且在整个组中保持不变时,数字才会在此处发生变化(序列递增)。
挑战在于任何列中都可能存在 NULL 值。因此,在分组 NULLS 时应该考虑。因此使用 IS NULL。 (为了确定,NVL 被忽略了)
当前解决方案:
DECLARE
VAL INTEGER;
BEGIN
FOR REC IN ( SELECT
COL1,
COL3,
COL4
FROM
TEMP_GP_SEQ
GROUP BY
COL1,
COL3,
COL4
HAVING
COUNT ( * ) > 1 )
LOOP
SELECT SEQ_TEMP_TEST.NEXTVAL INTO VAL FROM DUAL;
UPDATE
TEMP_GP_SEQ
SET
COL7 = 'M' || VAL
WHERE
( COL1 = REC.COL1 OR ( COL1 IS NULL AND REC.COL1 IS NULL ) )
AND ( COL3 = REC.COL3 OR ( COL3 IS NULL AND REC.COL3 IS NULL ) )
AND ( COL4 = REC.COL4 OR ( COL4 IS NULL AND REC.COL4 IS NULL ) );
END LOOP;
END;
/
真的可以将其重构为普通 SQL 而不是 PL/SQL 块吗? 如果您需要任何澄清,请告诉我。
想尝试的小伙伴可以看看小提琴here
【问题讨论】:
我相信任何pl/sql都可以转换为SQL。我经常使用 NVL 来避免 NULL,这可能会有所帮助吗? 我也相信这一点,因此发布了这个:)。仅当我是1000% sure that the dummy value will never appear on the column
时才能使用 NVL
之间。所以为了避免这种情况,我使用了IS NULL
。
ROWNUM 也是 oracle 中用于转换的有用构造。仍然看着你的 SQL 代码,我的眼睛很刺眼。
顺序有必要吗?或者你可以考虑不使用它?
绝对必要。这就是让它变得棘手的原因
【参考方案1】:
如果我正确理解您的问题,则此查询将执行您想要的操作,那么您只需要使用它来更新您的行。 但是我没有使用 update
来更改行,也没有使用 fiddle,因为(我不知道为什么)它不会让我创建函数。
为了让我的 select 语句运行,我需要创建一个函数来返回序列,因为 oracle 不允许我在我的 sql 语句中使用它(至少不是在我的版本 10.2.x 上)。
所以首先我创建了这个函数:
create or replace function retSeq return number
as
n number;
begin
select SEQ_TEMP_TEST.nextval into n from dual;
return n;
end;
然后我做了选择语句。我认为将您的代码更改为此会变得更加难以理解。但问题是用一个查询来解决问题,我几乎做到了(必须创建函数)。所以,不要害怕:
SELECT s1.col1, s1.col2, s1.col3, s1.col4, s1.col5, s1.col6,
decode(s1.id,null,'','M')
|| (SELECT retseq seq
FROM (SELECT col1, col3, col4,
ROW_NUMBER () OVER (ORDER BY col1, col3, col4) ID
FROM temp_gp_seq
GROUP BY col1, col3, col4
HAVING COUNT (*) > 1)
WHERE ID = s1.ID)
as col7
FROM (SELECT a.*, b.ID
FROM temp_gp_seq a,
(SELECT col1, col3, col4,
ROW_NUMBER () OVER (ORDER BY col1, col3, col4) ID
FROM (SELECT col1, col3, col4, COUNT (*) ct
FROM temp_gp_seq
GROUP BY col1, col3, col4
HAVING COUNT (*) > 1)) b
WHERE a.col1 = b.col1(+)
AND a.col3 = b.col3(+)
AND a.col4 = b.col4(+)) s1
结果将是(在第一次运行时,由于顺序)
COL1 COL2 COL3 COL4 COL5 COL6 COL7
2 10 101 1 M1
2 10 101 1 M1
12 10 100 1 M2
12 10 100 1 M2
1 10 100 3
1 10 100 2
1 10 100
2 10 101
1 10 101
2 10 100
由 OP 跟进:
SELECT
A.COL1,
A.COL2,
A.COL3,
A.COL4,
A.COL5,
A.COL6,
DECODE ( B.ID, NULL, '', 'M' )
|| SEQ
AS COL7
FROM
TEMP_GP_SEQ A,
(SELECT
COL1,
COL3,
COL4,
RETSEQ SEQ,
ROW_NUMBER ( )
OVER ( ORDER BY
COL1,
COL3,
COL4 )
ID
FROM
TEMP_GP_SEQ
GROUP BY
COL1,
COL3,
COL4
HAVING
COUNT ( * ) > 1) B
WHERE
A.COL1 = B.COL1(+)
AND A.COL3 = B.COL3(+)
AND A.COL4 = B.COL4(+);
PS:删除了不必要的子查询并将窗口功能合二为一。
【讨论】:
我确实检查并简化了您的查询,但完全归功于逻辑。 :) 看后续 @realspirituals 是的,那太好了,我逐步解决了问题,并没有考虑尽可能做到最好。感谢您的跟进:)以上是关于如何在一组行之后或有条件地在没有 PL/SQL 块的情况下增加 oracle 序列?的主要内容,如果未能解决你的问题,请参考以下文章
在一组行之后显示分隔线,而不是 DataTable 中的每一行
Oracle pl sql 10g - 将一组行从表移动到具有相同结构的历史表