将 Oracle 序列重置为现有列中的下一个值的最佳方法?

Posted

技术标签:

【中文标题】将 Oracle 序列重置为现有列中的下一个值的最佳方法?【英文标题】:Best way to reset an Oracle sequence to the next value in an existing column? 【发布时间】:2011-08-31 05:50:59 【问题描述】:

由于某种原因,过去人们插入数据时没有使用 sequence.NEXTVAL。因此,当我使用 sequence.NEXTVAL 来填充表时,我会遇到 PK 违规,因为该数字已在表中使用。

如何更新下一个值以使其可用?现在,我只是一遍又一遍地插入,直到它成功 (INSERT INTO tbl (pk) VALUES (sequence.NEXTVAL)),然后同步 nextval。

【问题讨论】:

见this similar question @rationalSpring 我最终决定放弃并重新创建序列。 删除和重新创建序列的问题是您必须对其重新应用任何授权(更不用说它会暂时使引用它的任何视图或 PL/SQL 无效)。跨度> @JeffreyKemp 没错,所以在我这样做之后,我想出了我在此处发布并接受的自我答案中的例程。 【参考方案1】:

您可以暂时增加缓存大小并进行一次虚拟选择,然后将缓存大小重置为 1。例如

ALTER SEQUENCE mysequence INCREMENT BY 100;

select mysequence.nextval from dual;

ALTER SEQUENCE mysequence INCREMENT BY 1;

【讨论】:

您可能希望确保在更改序列时没有其他人在使用该序列。 如果您只是想将起始值重置为数字,请查看 Leniel Macaferi 的回答。 alter sequence <seq> restart start with <num>; 你的意思是增加increment by。缓存大小完全是另一回事:***.com/a/55576009/346534【参考方案2】:

在我的例子中,我有一个名为PS_LOG_SEQ 的序列,它有一个LAST_NUMBER = 3920

然后我将一些数据从PROD 导入到我的本地计算机并插入到PS_LOG 表中。生产数据有超过 20000 行,最新的 LOG_ID(主键)为 20070。导入后,我尝试在此表中插入新行,但保存时出现如下异常:

ORA-00001: unique constraint (LOG.PS_LOG_PK) violated

这肯定与与PS_LOG 表关联的序列PS_LOG_SEQ 有关。 LAST_NUMBER 与我导入的数据发生冲突,这些数据已经使用了来自 PS_LOG_SEQ 的下一个 ID 值。

为了解决我用这个命令更新序列到最新的\max(LOG_ID)+1:

alter sequence PS_LOG_SEQ restart start with 20071;

此命令重置LAST_NUMBER 值,然后我可以将新行插入表中。没有更多的碰撞。 :)

注意:alter sequence 命令是 Oracle 12c 中的新命令。

注意:blog post 记录了 ALTER SEQUENCE RESTART 选项确实存在,但从 18c 开始,没有记录。它显然是供 Oracle 内部使用的。

【讨论】:

您链接的文档说“要以不同的编号重新启动序列,您必须删除并重新创建它。”,没有提到 RESTART 选项。链接的版本有误? 不适用于 IDENTITY(即 GENERATED)序列。 这应该是公认的答案。简单干净。【参考方案3】:

这两个过程让我重置序列并根据表中的数据重置序列(对此客户端使用的编码约定表示歉意):

CREATE OR REPLACE PROCEDURE SET_SEQ_TO(p_name IN VARCHAR2, p_val IN NUMBER)
AS
   l_num   NUMBER;
BEGIN
   EXECUTE IMMEDIATE 'select ' || p_name || '.nextval from dual' INTO l_num;

   -- Added check for 0 to avoid "ORA-04002: INCREMENT must be a non-zero integer"
   IF (p_val - l_num - 1) != 0
   THEN
      EXECUTE IMMEDIATE 'alter sequence ' || p_name || ' increment by ' || (p_val - l_num - 1) || ' minvalue 0';
   END IF;

   EXECUTE IMMEDIATE 'select ' || p_name || '.nextval from dual' INTO l_num;

   EXECUTE IMMEDIATE 'alter sequence ' || p_name || ' increment by 1 ';

   DBMS_OUTPUT.put_line('Sequence ' || p_name || ' is now at ' || p_val);
END;

CREATE OR REPLACE PROCEDURE SET_SEQ_TO_DATA(seq_name IN VARCHAR2, table_name IN VARCHAR2, col_name IN VARCHAR2)
AS
   nextnum   NUMBER;
BEGIN
   EXECUTE IMMEDIATE 'SELECT MAX(' || col_name || ') + 1 AS n FROM ' || table_name INTO nextnum;

   SET_SEQ_TO(seq_name, nextnum);
END;

【讨论】:

这适用于 11g 吗?我得到“未知的设置选项~~~”【参考方案4】:

如果您可以指望有一段时间表处于稳定状态而没有新的插入,那么应该这样做(未经测试):

DECLARE
  last_used  NUMBER;
  curr_seq   NUMBER;
BEGIN
  SELECT MAX(pk_val) INTO last_used FROM your_table;

  LOOP
    SELECT your_seq.NEXTVAL INTO curr_seq FROM dual;
    IF curr_seq >= last_used THEN EXIT;
    END IF;
  END LOOP;
END;

这使您可以使序列与表重新同步,而无需删除/重新创建/重新授予序列。它也不使用 DDL,因此不执行隐式提交。当然,你将不得不追捕并打那些坚持不使用序列来填充列的人......

【讨论】:

这行得通,它只需要在 IF 行后面加上一个“END IF”【参考方案5】:

使用 oracle 10.2g:

select  level, sequence.NEXTVAL
from  dual 
connect by level <= (select max(pk) from tbl);

将当前序列值设置为表的最大值(pk)(即下一次调用 NEXTVAL 会给你正确的结果);如果您使用 Toad,请按 F5 运行语句,而不是 F9,它分页输出(因此通常在 500 行之后停止增量)。 好的方面:这个解决方案只有 DML,而不是 DDL。只有 SQL,没有 PL-SQL。 不好的一面:此解决方案打印 max(pk) 行输出,即通常比 ALTER SEQUENCE 解决方案慢。

【讨论】:

不是max(pk) +1 ? 否:运行select后,序列的当前值为max(pk),所以,当你需要序列的下一个值时,调用sequence.NEXTVAL会给你max的值(pk)+1。无论如何,试试吧:它很漂亮。 这将始终以表格中的行数递增序列。如果序列不为0,则会过冲。【参考方案6】:

今天,在 Oracle 12c 或更高版本中,您可能将列定义为 GENERATED ... AS IDENTITY,而 Oracle 会自行处理序列。

您可以使用 ALTER TABLE 语句来修改身份的“START WITH”。

ALTER TABLE tbl MODIFY ("ID" NUMBER(13,0) GENERATED BY DEFAULT ON NULL AS IDENTITY MINVALUE 1 MAXVALUE 9999999999999999999999999999 INCREMENT BY 1 START WITH 3580 NOT NULL ENABLE);

【讨论】:

【参考方案7】:

在我的例子中,我使用了一种方法将序列重置为零,而不是将目标表的从零设置为最大值:

DECLARE
    last_val NUMBER;
    next_val NUMBER;
BEGIN
    SELECT MAX(id_field) INTO next_val FROM some_table;
    IF next_val > 0 THEN
        SELECT some_table_seq.nextval INTO last_val FROM DUAL;
        EXECUTE IMMEDIATE 'ALTER SEQUENCE some_table_seq INCREMENT BY -' || last_val || ' MINVALUE 0';
        SELECT some_table_seq.nextval INTO last_val FROM DUAL;
        EXECUTE IMMEDIATE 'ALTER SEQUENCE some_table_seq INCREMENT BY ' || next_val;
        SELECT some_table_seq.nextval INTO last_val FROM DUAL;
        EXECUTE IMMEDIATE 'ALTER SEQUENCE some_table_seq INCREMENT BY 1 MINVALUE 1';
    END IF;
END;

【讨论】:

【参考方案8】:

很抱歉没有单线解决方案,因为我的程序在 Typeorm 中使用 node-oracle 运行。但是,我认为以下 SQL 命令将有助于解决此问题。

从您的序列中获取LAST_NUMBER

SELECT SEQUENCE_NAME, LAST_NUMBER FROM ALL_SEQUENCES WHERE SEQUENCE_NAME = '$sequenceName'

从最后一行获取您的 PK 值(在这种情况下,ID 是 PK)。

SELECT ID FROM $tableName ORDER BY ID DESC FETCH NEXT 1 ROWS ONLY

最后将LAST_NUMBER 更新为值 + 1:

ALTER SEQUENCE $sequenceName RESTART START WITH $value + 1

【讨论】:

以上是关于将 Oracle 序列重置为现有列中的下一个值的最佳方法?的主要内容,如果未能解决你的问题,请参考以下文章

通过使用php将表单中的值添加到表列中的现有值来更新数据库中的值的问题

oracle 11g 中的重置序列

将序列重置为特定值

通过存储过程检索 Oracle 序列的下一个值

循环遍历范围,如果单元格包含值,则复制到列中的下一个空单元格

Oracle - 返回一组行中的最短字符串值