如何在Oracle 10g中每年自动将序列值重置为0?

时间:2009-06-03 02:44:55

标签: sql oracle

在问题中,如何在Oracle 10g中每年自动将Oracle序列的值重置为0?

我正在使用序列生成格式为YYYY<sequence value>的标识符,并且序列值必须每年重置为0。

YYYY从java获得,并与Oracle的序列值连接。由于外部第三方要求,无法更改标识符的格式。感谢您提前提供任何帮助。

7 个答案:

答案 0 :(得分:6)

序列实际上并未设计为重置。但是在某些情况下需要重置序列,例如,在设置测试数据或将生产数据合并回测试环境时。此类活动通常在生产中完成。

如果此类操作即将投入生产,则需要进行全面测试。 (引起最大关注的是重置程序在错误的时间意外执行的可能性,例如,在年中。

删除并重新创建序列是一种方法。作为一项操作,就SEQUENCE而言,它是相当简单的:

    DROP SEQUENCE MY_SEQ;
    CREATE SEQUENCE MY_SEQ START WITH 1 INCREMENT BY 1 MINVALUE 0;

[编辑]正如Matthew Watson正确指出的那样,每个DDL语句(例如DROP,CREATE,ALTER)都会导致隐式提交。 [/编辑]

但是,在SEQUENCE上授予的任何权限都将被删除,因此需要重新授予这些权限。引用序列的任何对象都将失效。为了更加通用化,您需要保存权限(在删除序列之前),然后重新授予它们。

第二种方法是改变现有的SEQUENCE,而不是丢弃并重新创建它。重置序列可以通过将INCREMENT值更改为负值(当前值与0之间的差值)来完成,然后只执行一次.NEXTVAL将当前值设置为0,然后将INCREMENT更改回1。我之前(手动地,在测试环境中)使用了相同的方法来将序列设置为更大的值。

当然,要使其正常工作,您需要确保在执行此操作时,没有其他会话引用该序列。在错误的瞬间额外的.NEXTVAL会搞砸重置。 (注意:如果应用程序是作为序列的所有者连接而不是作为单独的用户连接,那么在数据库端实现这一点将很困难。)

要让它每年都发生,你需要安排一份工作。序列重置必须与标识符的YYYY部分重置相协调。

以下是一个例子:

http://www.jaredstill.com/content/reset-sequence.html

[编辑]

UNTESTED 占位符,用于重置序列的PL / SQL块的一种可能设计

    declare
      pragma autonomous_transaction;
      ln_increment       number;
      ln_curr_val        number;
      ln_reset_increment number;
      ln_reset_val       number;
    begin

      -- save the current INCREMENT value for the sequence
      select increment_by
        into ln_increment
        from user_sequences
       where sequence_name = 'MY_SEQ';

      -- determine the increment value required to reset the sequence
      -- from the next fetched value to 0
      select -1 - MY_SEQ.nextval into ln_reset_increment from dual;

      -- fetch the next value (to make it the current value)
      select MY_SEQ.nextval into ln_curr from dual;

      -- change the increment value of the sequence to 
      EXECUTE IMMEDIATE 'alter sequence MY_SEQ increment by '
        || ln_reset_increment ||' minvalue 0';

      -- advance the sequence to set it to 0
      select MY_SEQ.nextval into ln_reset_val from dual;

      -- set increment back to the previous(ly saved) value
      EXECUTE IMMEDIATE 'alter sequence MY_SEQ increment by '
        || ln_increment ;
    end;
    /

注意:

  • 如何最好地保护序列在重置时不被访问,重命名吗?
  • 可以在这里完成几个测试用例。
  • 首先通过,检查正,升,增1序列的规范性案例。
  • 更好的方法是创建新的SEQUENCE,添加权限,重命名现有和新的序列,然后重新编译依赖项?

答案 1 :(得分:5)

把它扔出去作为一个想法:

如果你想要一个不需要持续DDL的解决方案(即没有丢弃和创建或重置序列),甚至任何工作,你可以考虑这样的事情(这只是原则上的,我没有测试过这种方法但是我相信它会起作用):

  1. 创建单个序列。

  2. 创建一个参考表,每年有一行,例如

    年(年份数(4,0)PRIMARY KEY,starting_value NUMBER)

  3. 当您从序列中获得NEXTVAL时,您必须从YEARS表中查询当前年份时减去starting_value。如果未找到年份,则应插入新行(即,在任何给定年份中运行的第一个流程将插入新值)。

  4. e.g。一个函数,例如get_year_starting_value (pn_year IN NUMBER) RETURN NUMBER可以查询此表并返回给定年份的starting_value;如果它得到NO_DATA_FOUND,它可以调用一个过程来使用序列中的NEXTVAL插入它(在自治事务中提交,以便新值立即可用于其他会话,以便函数由于副作用而不会失败)

    可能不是所有案例的解决方案,但我认为这种方法至少在某些情况下可能会有所帮助。

答案 2 :(得分:4)

使用工作来完成这个任务。首先,创建一个存储过程来重置序列(我通常使用DROP / CREATE解决方案,但你可以使用spencer7593的技巧):

CREATE OR REPLACE PROCEDURE my_seq_reset AS
BEGIN
    EXECUTE IMMEDIATE 'DROP SEQUENCE my_seq';
    EXECUTE IMMEDIATE
      'CREATE SEQUENCE my_seq' ||
      '  MINVALUE 1 ' ||
      '  MAXVALUE 999999 ' ||
      '  START WITH 1 ' ||
      '  INCREMENT BY 1 ' ||
      '  NOCACHE';
END;

然后创建作业(see here作为参考):

BEGIN
  dbms_scheduler.create_job(
    job_name        => 'job$my_seq_reset',
    job_type        => 'STORED_PROCEDURE',
    job_action      => 'my_seq_reset',
    start_date      => TO_DATE('01-01-09', 'DD-MM-RR'),
    repeat_interval => 'FREQ=YEARLY;BYDATE=0101',
    enabled         => TRUE,
    auto_drop       => FALSE,
    comments        => 'My sequence yearly reset job.'
  );
END;

你已经完成了。

答案 3 :(得分:1)

我不确定是否有一个好方法,这不是真正的序列设计。它们只是纯粹增加了唯一的数字。

想到了2个想法。

  1. 在第一个上午12点,重置序列,这很难,因为你需要确保你击败任何代码。
  2. 为每年创建一个序列,甚至可以在代码中创建序列,以便能够创建序列,然后动态调用年份的正确序列。
  3. 我倾向于赞成选项2,因为它没有尝试做任何花哨的事情并且总是能够正常工作,任何试图操纵序列的选项都必然会咬你。

答案 4 :(得分:0)

首先,它似乎不是每年自动重启序列的方法。阅读本文以供参考:

http://www.psoug.org/reference/OLD/sequences.html?PHPSESSID=5949da378678fa6d24b6fcc6eaae9888

我的方法是:

  1. 创建一个表格,其中包含该年份的年份和起始顺序(让我们将此表格称为year_seed)

  2. 创建一个接收年份的过程,检查year_seed表,如果是年份的第一次检查,则生成带有起始序列的寄存器。此过程还必须返回序列减去年份的起始序列。

  3. 也许这不是那么简单,但我认为这是最好的解决方案。祝你好运

答案 5 :(得分:0)

我发现最好创建一个触发器和一个表。 该表将包含该年份的年份和顺序。触发器获取当前年份,验证表,如果没有找到注册表,则从1开始插入新的。否则,选择最后一个并递增1,更新相应的表。

create table GDRDOCUMENTOSEQ
(
  noano NUMBER(4),
  noseq NUMBER(6)
)
;
alter table GDRDOCUMENTOSEQ
  add unique (NOANO);

触发器

CREATE OR REPLACE TRIGGER "GDRGUIARESSARCIMENTONODOC_BIR"
BEFORE INSERT ON GDR.GDRGUIARESSARCIMENTO
FOR EACH ROW

DECLARE
  lNoAno number;
  lNoSeq number;
  lQtd   number;
begin

  SELECT EXTRACT(YEAR FROM SYSDATE) into lNoAno FROM DUAL;

  SELECT COUNT(0)
    INTO lQtd
    FROM gdr.gdrdocumentoseq ds
   WHERE ds.noano = lNoAno;

  IF lQtd = 0 then
    lNoSeq := 1;
    INSERT INTO GDR.GDRDOCUMENTOSEQ (NOANO, NOSEQ) VALUES (lNoAno, lNoSeq);
  else
    SELECT nvl(max(ds.noseq), 0) + 1
      INTO lNoSeq
      FROM gdr.gdrdocumentoseq ds
     WHERE ds.noano = lNoAno;

    UPDATE GDR.GDRDOCUMENTOSEQ ds
       SET ds.noseq = lNoSeq
     WHERE ds.noano = lNoAno;
  end if;

  :new.nodocumento := SUBSTR(lNoAno, 3) || lpad(lNoSeq, 6, '0');

end;

我从2016年开始在生产中运行此代码。表的当前状态是:

NOANO   NOSEQ   
2017    1411    
2016    237

答案 6 :(得分:0)

#include <stdio.h>
#include <stdlib.h>

struct node {
    int id;
    struct node *next;
};

struct node *make_node(int id) {
    struct node *n = malloc(sizeof(*n));
    n->id = id;
    n->next = NULL;
    return n;
}

void free_linked_list(struct node *head) {
    while (head) {
        struct node *tmp = head;
        head = head->next;
        free(tmp);
    }
}

int main() {
    struct node *head = make_node(1);
    head->next = make_node(2);
    head->next->next = make_node(3);

    printf("%d, ", head->id);
    printf("%d, ", head->next->id);
    printf("%d\n", head->next->next->id);

    free_linked_list(head);
    return 0;
}