如何在一组行之后或有条件地在没有 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 - 将一组行从表移动到具有相同结构的历史表

如何相对于 spark 中的 col 值在一组行上设置增量 id

如何更新除该组中最新项目之外的一组行

PL/SQL 块需要大量时间来执行

Oracle PL/SQL - 如果不满足条件,则退出开始结束块