由于某种原因,过去人们插入数据时没有使用sequence.NEXTVAL。因此,当我使用sequence.NEXTVAL来填充表时,我遇到了PK违规,因为该数字已在表中使用。
如何更新下一个值以使其可用?现在,我只是一遍又一遍地插入,直到成功(
INSERT INTO tbl (pk) VALUES (sequence.NEXTVAL)
),然后同步下一个val。
您可以暂时增加缓存大小并执行一次虚拟选择,然后将缓存大小重置回 1。例如
ALTER SEQUENCE mysequence INCREMENT BY 100;
select mysequence.nextval from dual;
ALTER SEQUENCE mysequence INCREMENT BY 1;
就我而言,我有一个名为
PS_LOG_SEQ
的序列,其中有一个 LAST_NUMBER = 3920
。
然后,我将一些数据从
PROD
导入到本地计算机,并插入到 PS_LOG
表中。生产数据有超过 20000
行,最新的 LOG_ID(主键)为 20070。导入后,我尝试在此表中插入新行,但在保存时出现如下异常:
ORA-00001: unique constraint (LOG.PS_LOG_PK) violated
这当然与与
PS_LOG_SEQ
表关联的序列 PS_LOG
有关。 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 内部使用的。
这两个过程让我重置序列并根据表中的数据重置序列(对此客户使用的编码约定表示歉意):
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;
如果您可以指望表在一段时间内处于稳定状态,没有进行新的插入,那么这应该可以做到(未经测试):
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,因此不会执行隐式提交。当然,您将不得不追捕并打那些坚持不使用序列填充列的人......
搭配甲骨文10.2克:
select level, sequence.NEXTVAL
from dual
connect by level <= (select max(pk) from tbl);
将当前序列值设置为表的 max(pk)(即下一次调用 NEXTVAL 将为您提供正确的结果);如果您使用 Toad,请按 F5 运行该语句,而不是按 F9,这会对输出进行分页(因此通常会在 500 行后停止增量)。 好的一面:这个解决方案只有 DML,而不是 DDL。只有 SQL,没有 PL-SQL。 不好的一面:此解决方案打印 max(pk) 行输出,即通常比 ALTER SEQUENCE 解决方案慢。
如今,在 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);
在我的例子中,我使用了一种方法将序列重置为零,然后将目标表从零设置为最大值:
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;
很抱歉没有单行解决方案,因为我的程序在 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}
我想最好的解决方案是一个存储过程来更新序列当前值。 我用这个sp来oracle:
CREATE OR REPLACE PROCEDURE SEQ_UPDATE_NEXTVAL(seq_name IN VARCHAR2, table_name IN VARCHAR2, col_name IN VARCHAR2)
AS
colCurrVal NUMBER;
seqCurrVal NUMBER;
BEGIN
EXECUTE IMMEDIATE 'SELECT MAX(' || col_name || ') AS n FROM ' || table_name INTO colCurrVal;
EXECUTE IMMEDIATE 'SELECT ' || seq_name || '.currval FROM dual' INTO seqCurrVal;
-- Added check for 0 to avoid "ORA-04002: INCREMENT must be a non-zero integer"
IF (colCurrVal - seqCurrVal) <= 0 THEN
DBMS_OUTPUT.put_line('Sequence ' || seq_name || ' does not need increment. currval: ' || seqCurrVal);
return;
END IF;
EXECUTE IMMEDIATE 'ALTER SEQUENCE ' || seq_name || ' increment by ' || (colCurrVal - seqCurrVal);
EXECUTE IMMEDIATE 'SELECT ' || seq_name || '.nextval FROM dual' INTO seqCurrVal;
EXECUTE IMMEDIATE 'SELECT ' || seq_name || '.currval FROM dual' INTO seqCurrVal;
EXECUTE IMMEDIATE 'ALTER SEQUENCE ' || seq_name || ' increment by 1';
DBMS_OUTPUT.put_line('Sequence ' || seq_name || ' currval is now at ' || seqCurrVal);
END;
执行存储过程: 调用 SEQ_UPDATE_NEXTVAL('SEQUENCE_NAME', 'TABLE_NAME', 'COLUMN_NAME');
(抱歉我的英语不好,我同意请修复我) 问候。