简体   繁体   中英

Auto Increment for non primary key column in Oracle

I want to insert the records for a particular column that would be increment by 1 whenever the new row gets inserted into table based on the following condition For current year the first row with value :1 For current year the column value should be increment by 1 meaning 1 for 1st record of that current year and next available number for the matching year

Year  Value
2016    1
2016    2
2016    3
2017    1
2017    2
...

My approach will be somewhat like this:

INSERT INTO ABC(ANALYSIS_YEAR,ANALYSIS_NUMBER)
values (EXTRACT(YEAR FROM sysdate),
        case when ANALYSIS_YEAR=EXTRACT(YEAR FROM sysdate) then AutoIcreamt with starting value 1 else 1;
    )

Any solution that looks at current table values will not work in a 'real' environment with multiple users and multiple sessions and parallel transactions.

I think you need to separate out the two requirements:

  1. Have the ability to sequence records based on when they were created
  2. Have the ability to report on record sequencing within a year.

The first is handled using a sequence as these are designed exactly for this and handle concurrency (multiple users, multiple transactions, ...).

The second is a reporting requirement and has a number of options depending on performance requirements.

First of all create a sequence:

create sequence seq_analysis_id start with 1 increment by 1 nocache nocycle;

Not let's create a base table and a trigger to handle the auto-increment:

create table analysis_data (
    analysis_id integer not null,
    analysis_date date not null
    );

alter table analysis_data add constraint pk_analysis_data primary key (analysis_id);

create or replace trigger trg_analysis_data
before insert on analysis_data
for each row
begin
    :new.analysis_id := seq_analysis_id.nextval();
end trg_analysis_data;
/

insert into analysis_data (analysis_date) values (to_date('2015-12-28', 'YYYY-MM-DD'));

insert into analysis_data (analysis_date) values (to_date('2015-12-29', 'YYYY-MM-DD'));

insert into analysis_data (analysis_date) values (to_date('2015-12-30', 'YYYY-MM-DD'));

insert into analysis_data (analysis_date) values (to_date('2015-12-31', 'YYYY-MM-DD'));

insert into analysis_data (analysis_date) values (to_date('2016-01-01', 'YYYY-MM-DD'));

insert into analysis_data (analysis_date) values (to_date('2016-01-02', 'YYYY-MM-DD'));

insert into analysis_data (analysis_date) values (to_date('2016-01-03', 'YYYY-MM-DD'));

commit;

select * from analysis_data;

ANALYSIS_ID    ANALYSIS_DATE
    1           28/12/2015
    2           29/12/2015
    3           30/12/2015
    4           31/12/2015
    5           01/01/2016
    6           02/01/2016
    7           03/01/2016

Ok - so that all works fine but doesn't give you what you asked for :)

This is the second part - the reporting requirement:

The first option is just to get the numbers you need dynamically:

select
    analysis_id,
    analysis_date,
    extract(year from analysis_date) analysis_year,
    row_number()
        over (partition by trunc(analysis_date, 'YYYY')
        order by analysis_date, analysis_id) analysis_number
from
    analysis_data;

Using Analytic Functions ( row_number in this case) is a great way to handle this sort of thing.

ANALYSIS_ID ANALYSIS_DATE   ANALYSIS_YEAR    ANALYSIS_NUMBER
    1         28/12/2015      2015              1
    2         29/12/2015      2015              2
    3         30/12/2015      2015              3
    4         31/12/2015      2015              4
    5         01/01/2016      2016              1
    6         02/01/2016      2016              2
    7         03/01/2016      2016              3

I've ordered by analysis_date, analysis_id in the row_number function. This probably isn't necessary but would be needed if you had to handle updates to the analysis_date (in which case the sequence no longer works for in-year ordering on its own).

You could make this a little more straightforward for reporting by wrapping it in a view:

create or replace view analysis_data_v as
select
    analysis_id,
    analysis_date,
    extract(year from analysis_date) analysis_year,
    row_number()
        over (partition by trunc(analysis_date, 'YYYY')
        order by analysis_date, analysis_id) analysis_number
from
    analysis_data;

This may be all you need, but if you have large data sets then you may need to pre-calculate some of these values. You have virtual columns in 11g, but these don't work for analytic functions. My option here would be to use a materialized view - lots of ways to handle materialized view refreshes and the simplest would be:

create materialized view analysis_data_mv 
    build immediate
    refresh complete on demand
as 
    select
        analysis_id,
        analysis_date,
        analysis_year,
        analysis_number
    from
        analysis_data_v;

select * from analysis_data_mv order by analysis_year, analysis_number;

ANALYSIS_ID ANALYSIS_DATE   ANALYSIS_YEAR    ANALYSIS_NUMBER
    1         28/12/2015      2015              1
    2         29/12/2015      2015              2
    3         30/12/2015      2015              3
    4         31/12/2015      2015              4
    5         01/01/2016      2016              1
    6         02/01/2016      2016              2
    7         03/01/2016      2016              3

In this case the materialized view would be manually refreshed:

exec dbms_mview.refresh('analysis_data_mv');

Hope this helps.

You will probably need a trigger on insert that looks like this (I guess you can't change the value of Year, in that case it will get more complicated). Like @ibre5041 This won't work on a multi-user enviroment since you could have duplicates if a couple of transactions are executed at the same time on the same year:

CREATE OR REPLACE TRIGGER trg_bi_table1
before insert
ON table_1
Begin
  select nvl(max(value),0)+1 as value
    into :new.value
    from table_1
   where Year = :new.Year;
end;
/

In a multi-user enviroment you should use select for update

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM