Oracle11g variable declaration - sql

I'm trying to increment a sequence by the grater value from an id column (numeric type but not int). For this I've tried to declare an int variable and selecting into it the max value from the table like this:
declare
last_id int;
begin
select max(id) into last_id from my_table;
alter sequence my_table_seq increment by last_id;
end;
However, I'm getting this error
ORA-06550: line 2, column 19:
PLS-00103: Encountered the symbol "end-of-file" when expecting one of the following:
:= . ( # % ; not null range default character
I'm using Oracle 11g. Here's a screenshot

declare
last_id number;
begin
select max(id) into last_id from my_table;
execute immediate 'alter sequence my_table_seq increment by ' || to_char(last_id);
end;
Use any of oracle Numeric Data Types instead of int
Use execute immediate for any DDN in anonymous block

Created similar objects and it works as expected.
Suggest you to try the below:
1.Change your column to from ID to something_id.
2.Remove double quotes from schema and table name (just give schema.table_name)
3.Ensure you do not miss a semi colon at the end of statement and all variables declared properly.
If still doesn't work then go for trial and error method.
Just take simple block and try printing the ID value alone.
Then keep adding your logic (assign value to last_id and try print last_id).
Then finally add your execute immediate statement and see if it works.
Create table general_discount_type
(id number);
-- Table created.
Begin
insert into general_discount_type values(101);
insert into general_discount_type values(102);
insert into general_discount_type values(103);
insert into general_discount_type values(104);
End;
-- Statement processed.
Create sequence general_discount_type_seq
start with 1
increment by 1
NOCACHE
NOCYCLE;
-- Sequence created.
Declare
last_id number;
Begin
select max(id) into last_id from MAHI_SCHEMA.general_discount_type;
execute immediate 'alter sequence MAHI_SCHEMA.general_discount_type_seq increment by '
|| to_char(last_id);
dbms_output.put_line('Value in last_id is : ' || last_id);
End;
-- Value in last_id is : 104
Output

Related

Write an insert statement with select clause returning id in oracle

I am trying to write a query as follows:
Eg:
DECLARE
V_Output varchar(20):='';
BEGIN
INSERT INTO T(ID, MY_PK, NAME, VALUE)
(SELECT ID, NEXT_TRAN_VALUE('T'), NAME, VALUE FROM T WHERE MY_PK = 'NO0000000000013');
RETURNING MY_PK INTO V_Output;
DBMS_OUTPUT.PUT(V_Output);
END;
using below function
create or replace FUNCTION NEXT_TRAN_VALUE (field IN VARCHAR2) RETURN
VARCHAR2
IS
n_value VARCHAR2 (20);
P_APR VARCHAR2(3);
CURSOR rec_exists IS SELECT * FROM TRANSACTION_SEQ where SEQ_NAME = field ;
jk_seq_rec TRANSACTION_SEQ%ROWTYPE;
PRAGMA AUTONOMOUS_TRANSACTION;
BEGIN
SELECT LC_CODE into P_APR FROM LOCATION_CONFIG;
OPEN rec_exists;
FETCH rec_exists INTO jk_seq_rec;
IF rec_exists%FOUND THEN
UPDATE TRANSACTION_SEQ SET curr_value = curr_value + 1 WHERE SEQ_NAME = field;
COMMIT;
END IF;
--SELECT curr_value INTO n_value FROM TRANSACTION_SEQ WHERE SEQ_NAME = field;
END;
But, it shows error.
the select statement shall always return single statement to be inserted, so in my opinion it should return the my_pk of inserted row.
That won't work. RETURNING clause can't be used the way you're doing it, i.e.
insert into t (id, my_pk)
select some_id, your_function from ...
returning into v_output
but would work if you inserted VALUES, as
insert into t
values (id, your_function)
returning my_pk into v_output
It means that you'll either have to rewrite that code, or look at a workaround described in returning with insert..select article (written by Adrian Billington).
BTW, wouldn't an ordinary Oracle sequence suit your purpose? Won't be gapless, but would be simple & effective. Mind the performance when inserting huge amount of data, using your solution.
BTW #2, what's the purpose of the last line in your function? You never use N_VALUE.
I use Cursor to resolve the issue.

Using Cursor in oracle SQL

I want to perform the delete operation in 2 tables with a given id here is sudo code
declare
cursor del_id is
select person_id from table_1 where termination is true
begin
for id_x in del_id
delete from table_X where id=id_x
delete from tabele_Y where id=id_x
How to do that ? i can't directly use my cursor please help.
I just try to print my id
begin
for id in del_id
LOOP
dbms_output.put_line(id);
END LOOP;
end;
Getting this error
Error report -
ORA-06550: line 11, column 3:
PLS-00306: wrong number or types of arguments in call to 'PUT_LINE'
To print the values from a cursor, you need to explicitly write the columns you want; dbms_output.put_line can not handle a row that may contain many columns with different types, so you need to pass it a string.
SQL> declare
2 cursor del_id is select 1 as one, 2 as two from dual;
3 begin
4 FOR id IN del_id
5 LOOP
6 dbms_output.put_line(id.one || ' - ' || id.two);
7 END LOOP;
8 end;
9 /
1 - 2
PL/SQL procedure successfully completed.
If you need to use the values from a cursor in some statement, a DELETE in your question, you need to do the same, by explicitly writing the column name; for example:
declare
cursor del_id is select 1 as one, 2 as two from dual;
begin
FOR id IN del_id
LOOP
delete from someTable where someColumn = id.one;
END LOOP;
end;
You do not need a cursor:
BEGIN
DELETE FROM table_X
WHERE id IN ( select person_id from table_1 where termination is true );
DELETE FROM table_Y
WHERE id IN ( select person_id from table_1 where termination is true );
END;
/
You could also use a collection:
CREATE TYPE id_type IS TABLE OF INTEGER;
/
DECLARE
ids id_type;
BEGIN
SELECT person_id
BULK COLLECT INTO ids
FROM table_1
WHERE termination is true;
DELETE FROM table_X
WHERE id MEMBER OF ids;
DELETE FROM table_Y
WHERE id MEMBER OF ids;
END;
/

Column values as factorial in Oracle SQL

I have created a trigger, that will autoincrement the id, according to the sequence, every time a new record is inserted. Like this:
create sequence test_seq
start with 1
increment by 1
nomaxvalue;
--drop trigger test_trigger;
create or replace trigger test_trigger
before insert on myTable
for each row
begin
select test_seq.nextval into :new.tab_id from dual;
end;
However, I'd like to insert a factorial of the row index instead. How could I achieve this?
Edit:
create or replace trigger test_trigger
after insert on myT
for each row
begin
select fac(test_seq.nextval) into :new.tab_id from dual;
end;
Added fac function which works fine:
create or replace function fac(n in number)
return number
is
v number :=1;
begin
for i in 1..n
loop
v :=v * i;
end loop;
return v;
end;
But I still only see 1,2,3,4 in the table instead of 1,2,6,24...
From Oracle's documentation. You want to use a BEFORE trigger in this instance, an AFTER trigger won't actually change the table's data just from setting it in NEW:
Because the trigger uses the BEFORE keyword, it can access the new
values before they go into the table, and can change the values if
there is an easily-corrected error by assigning to :NEW.column_name.
My guess is that you are still seeing the same old values from the sequence because your BEFORE trigger still exists; the AFTER trigger won't change those values.
So what you want is the following:
CREATE OR REPLACE TRIGGER test_trigger
BEFORE INSERT ON myt
FOR EACH ROW
BEGIN
SELECT FAC(test_seq.nextval) INTO :new.tab_id FROM dual;
END;
/
I think as of Oracle 11g (or maybe it's 10g; can't remember) you can also do the following:
CREATE OR REPLACE TRIGGER test_trigger
BEFORE INSERT ON myt
FOR EACH ROW
BEGIN
:new.tab_id := FAC(test_seq.nextval);
END;
/
Do something like
create function factorial (n integer) return integer as
...
create or replace trigger test_trigger
after insert on mytable
-- don't do this for each row
begin
update mytable set
tab_id = factorial((select count(*) from mytable))
where tab_id is null;
end;
/

Best way to reset an Oracle sequence to the next value in an existing column?

For some reason, people in the past have inserted data without using sequence.NEXTVAL. So when I go to use sequence.NEXTVAL in order to populate a table, I get a PK violation, since that number is already in use in the table.
How can I update the next value so that it is usable? Right now, I'm just inserting over and over until it's successful (INSERT INTO tbl (pk) VALUES (sequence.NEXTVAL)), and that syncs up the nextval.
You can temporarily increase the cache size and do one dummy select and then reset the cache size back to 1. So for example
ALTER SEQUENCE mysequence INCREMENT BY 100;
select mysequence.nextval from dual;
ALTER SEQUENCE mysequence INCREMENT BY 1;
In my case I have a sequence called PS_LOG_SEQ which had a LAST_NUMBER = 3920.
I then imported some data from PROD to my local machine and inserted into the PS_LOG table. Production data had more than 20000 rows with the latest LOG_ID (primary key) being 20070. After importing I tried to insert new rows in this table but when saving I got an exception like this one:
ORA-00001: unique constraint (LOG.PS_LOG_PK) violated
Surely this has to do with the Sequence PS_LOG_SEQ associated with the PS_LOG table. The LAST_NUMBER was colliding with data I imported which had already used the next ID value from the PS_LOG_SEQ.
To solve that I used this command to update the sequence to the latest \ max(LOG_ID) + 1:
alter sequence PS_LOG_SEQ restart start with 20071;
This command reset the LAST_NUMBER value and I could then insert new rows into the table. No more collision. :)
Note: this alter sequence command is new in Oracle 12c.
Note: this blog post documents the ALTER SEQUENCE RESTART option does exist, but as of 18c, is not documented; it is apparently intended for internal Oracle use.
These two procedures let me reset the sequence and reset the sequence based on data in a table (apologies for the coding conventions used by this client):
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;
If you can count on having a period of time where the table is in a stable state with no new inserts going on, this should do it (untested):
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;
This enables you to get the sequence back in sync with the table, without dropping/recreating/re-granting the sequence. It also uses no DDL, so no implicit commits are performed. Of course, you're going to have to hunt down and slap the folks who insist on not using the sequence to populate the column...
With oracle 10.2g:
select level, sequence.NEXTVAL
from dual
connect by level <= (select max(pk) from tbl);
will set the current sequence value to the max(pk) of your table (i.e. the next call to NEXTVAL will give you the right result); if you use Toad, press F5 to run the statement, not F9, which pages the output (thus stopping the increment after, usually, 500 rows).
Good side: this solution is only DML, not DDL. Only SQL and no PL-SQL.
Bad side : this solution prints max(pk) rows of output, i.e. is usually slower than the ALTER SEQUENCE solution.
Today, in Oracle 12c or newer, you probably have the column defined as GENERATED ... AS IDENTITY, and Oracle takes care of the sequence itself.
You can use an ALTER TABLE Statement to modify "START WITH" of the identity.
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);
In my case I used an approach to reset sequence to zero and than setting from zero to max of target table:
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;
Apologies for not having a one-liner solution, since my program runs in Typeorm with node-oracle. However, I think the following SQL commands would help with this problem.
Get the LAST_NUMBER from your sequence.
SELECT SEQUENCE_NAME, LAST_NUMBER FROM ALL_SEQUENCES WHERE SEQUENCE_NAME = '${sequenceName}'
Get the value of your PK from the last row (in this case ID is the PK).
SELECT ID FROM ${tableName} ORDER BY ID DESC FETCH NEXT 1 ROWS ONLY
Lastly update LAST_NUMBER to the value + 1:
ALTER SEQUENCE ${sequenceName} RESTART START WITH ${value + 1}

How to create an Oracle sequence starting with max value from a table?

Trying to create a sequence in Oracle that starts with the max value from a specific table. Why does this not work?
CREATE SEQUENCE transaction_sequence
MINVALUE 0
START WITH (SELECT MAX(trans_seq_no)
FROM TRANSACTION_LOG)
INCREMENT BY 1
CACHE 20;
If you can use PL/SQL, try (EDIT: Incorporates Neil's xlnt suggestion to start at next higher value):
SELECT 'CREATE SEQUENCE transaction_sequence MINVALUE 0 START WITH '||MAX(trans_seq_no)+1||' INCREMENT BY 1 CACHE 20'
INTO v_sql
FROM transaction_log;
EXECUTE IMMEDIATE v_sql;
Another point to consider: By setting the CACHE parameter to 20, you run the risk of losing up to 19 values in your sequence if the database goes down. CACHEd values are lost on database restarts. Unless you're hitting the sequence very often, or, you don't care that much about gaps, I'd set it to 1.
One final nit: the values you specified for CACHE and INCREMENT BY are the defaults. You can leave them off and get the same result.
Here I have my example which works just fine:
declare
ex number;
begin
select MAX(MAX_FK_ID) + 1 into ex from TABLE;
If ex > 0 then
begin
execute immediate 'DROP SEQUENCE SQ_NAME';
exception when others then
null;
end;
execute immediate 'CREATE SEQUENCE SQ_NAME INCREMENT BY 1 START WITH ' || ex || ' NOCYCLE CACHE 20 NOORDER';
end if;
end;
you might want to start with max(trans_seq_no) + 1.
watch:
SQL> create table my_numbers(my_number number not null primary key);
Table created.
SQL> insert into my_numbers(select rownum from user_objects);
260 rows created.
SQL> select max(my_number) from my_numbers;
MAX(MY_NUMBER)
--------------
260
SQL> create sequence my_number_sn start with 260;
Sequence created.
SQL> insert into my_numbers(my_number) values (my_number_sn.NEXTVAL);
insert into my_numbers(my_number) values (my_number_sn.NEXTVAL)
*
ERROR at line 1:
ORA-00001: unique constraint (NEIL.SYS_C00102439) violated
When you create a sequence with a number, you have to remember that the first time you select against the sequence, Oracle will return the initial value that you assigned it.
SQL> drop sequence my_number_sn;
Sequence dropped.
SQL> create sequence my_number_sn start with 261;
Sequence created.
SQL> insert into my_numbers(my_number) values (my_number_sn.NEXTVAL);
1 row created.
If you're trying to do the 'gapless' thing, I strongly advise you to
1 not do it, and #2 not use a sequence for it.
You can't use a subselect inside a CREATE SEQUENCE statement. You'll have to select the value beforehand.
Bear in mid, the MAX value will only be the maximum of committed values. It might return 1234, and you may need to consider that someone has already inserted 1235 but not committed.
Based on Ivan Laharnar with less code and simplier:
declare
lastSeq number;
begin
SELECT MAX(ID) + 1 INTO lastSeq FROM <TABLE_NAME>;
if lastSeq IS NULL then lastSeq := 1; end if;
execute immediate 'CREATE SEQUENCE <SEQUENCE_NAME> INCREMENT BY 1 START WITH ' || lastSeq || ' MAXVALUE 999999999 MINVALUE 1 NOCACHE';
end;
DECLARE
v_max NUMBER;
BEGIN
SELECT (NVL (MAX (<COLUMN_NAME>), 0) + 1) INTO v_max FROM <TABLE_NAME>;
EXECUTE IMMEDIATE 'CREATE SEQUENCE <SEQUENCE_NAME> INCREMENT BY 1 START WITH ' || v_max || ' NOCYCLE CACHE 20 NOORDER';
END;
use dynamic sql
BEGIN
DECLARE
maxId NUMBER;
BEGIN
SELECT MAX(id)+1
INTO maxId
FROM table_name;
execute immediate('CREATE SEQUENCE sequane_name MINVALUE '||maxId||' START WITH '||maxId||' INCREMENT BY 1 NOCACHE NOCYCLE');
END;
END;