how can you select the same sequence twice in one query?
I have googled this and just cant get an answer.
Just to be clear, this is what I need, example :
select seq.nextval as v1, seq.nextval as v2 from dual (I know that doesn't work)
I have tried UNION as well, Just cant get my head around it.
If you always need exactly two values, you could change the sequence to increment by 2:
alter sequence seq increment by 2;
and then select the current value and its successor:
select seq.nextval,
seq.nextval + 1
from dual;
Not pretty, but it should do the job.
UPDATE
Just to clarify: the ALTER SEQUENCE should be issued only once in a lifetime, not once per session!
The Answer by Frank has a downside:
You cannot use it in transactional system, because the ALTER SEQUENCE commits any pending DML.
The Answer of Sean only pulls the sequence once.
As I understand, you want to pull two values.
As an alternative to Seans solution, you could also select two times from .nextval, due ORACLE gives you the same value twice.
I'd prefer wrapping up the sequence in a procedure.
This tricks oracle to pulling the sequence twice.
CREATE or replace FUNCTION GetSeq return number as
nSeq NUMBER;
begin
select seq.nextval into nSeq from dual;
return nSeq;
end;
/
If you need this generically, maybe you'd like:
CREATE or replace FUNCTION GetSeq(spSeq in VARCHAR2) return number as
nSeq NUMBER;
v_sql long;
begin
v_sql:='select '||upper(spSeq)||'.nextval from dual';
execute immediate v_sql into nSeq;
return nSeq;
end;
/
There are some limitations on how NEXTVAL can be used. There's a list in the Oracle docs. More to the point, the link includes a list of conditions where NEXTVAL will be called more than once.
The only scenario named on the page where a straight SELECT will call NEXTVAL more than once is in "A top-level SELECT statement". A crude query that will call NEXTVAL twice is:
SELECT seq.NEXTVAL FROM (
SELECT * FROM DUAL
CONNECT BY LEVEL <= 2) -- Change the "2" to get more sequences
Unfortunately, if you try to push this into a subquery to flatten out the values to one row with columns v1 and v2 (like in your question), you'll get the ORA-02287: sequence number not allowed here.
This is as close as I could get. If the query above won't help you get where you want, then check out the other answers that have been posted here. As I've been typing this, a couple of excellent answers have been posted.
Telling you to just call sequence.nextval multiple times would be boring, so here's what you can try:
create type t_num_tab as table of number;
CREATE SEQUENCE SEQ_TEST
START WITH 1
MAXVALUE 999999999999999999999999999
MINVALUE 1
NOCYCLE
CACHE 100
NOORDER;
create or replace function get_seq_vals(i_num in number) return t_num_tab is
seq_tab t_num_tab;
begin
select SEQ_TEST.nextval
bulk collect into seq_tab
from all_objects
where rownum <= i_num;
return seq_tab;
end;
And you can use it as follows. This example is pulling 7 numbers at once from the sequence:
declare
numbers t_num_tab;
idx number;
begin
-- this grabs x numbers at a time
numbers := get_seq_vals(7);
-- this just loops through the returned numbers
-- to show the values
idx := numbers.first;
loop
dbms_output.put_line(numbers(idx));
idx := numbers.next(idx);
exit when idx is null;
end loop;
end;
Also note that I use "next" instead of first..last as its possible you may want to remove numbers from the list before iterating through (or, sometimes a sequence cache can results in numbers incrementing by more than 1).
These are all great answers, unfortunately it was just not enough. Will definitely be able to return to this page when struggling in the future.
I have gone with a different method. Asked a new question and got my answer.
You can go check it out here.
Related
as I described on the title, I want to write a trigger that defines to add a new staff by all giving attributes except ID, I want to trigger generate and insert it automatically. How can I do that?
I've written a code like below in PL/SQL, but it's including the sequence and I couldn't find how can I get the current max ID of my staff with using the sequence, so could you please help me, with or without using the sequence?
CREATE SEQUENCE BEFORE_INSERTING START WITH 1000 INCREMENT BY 1;
CREATE OR REPLACE TRIGGER NEW_ID_BEFORE_INSERTING
BEFORE INSERT ON STAFF
FOR EACH ROW
BEGIN
:NEW.STAFF_ID := BEFORE_INSERTING.nextval;
END;
/
By the way, this code works fine but as you see it's starting from 1000.
Perhaps you can use something like the following to find the maximum value for STAFF_ID and then redefine the sequence based on that value:
DECLARE
nMax_staff_id NUMBER;
BEGIN
SELECT MAX(STAFF_ID)
INTO nMax_staff_id
FROM STAFF;
EXECUTE IMMEDIATE 'DROP SEQUENCE BEFORE_INSERTING';
EXECUTE IMMEDIATE 'CREATE SEQUENCE BEFORE_INSERTING START WITH ' ||
nMax_staff_id + 1 || ' INCREMENT BY 1';
END;
You only need to run the above once, just to get the sequence reset. After that the trigger will use the sequence to obtain each STAFF_ID value. Note that there are other ways to redefine a sequence's value, but here we'll do The Simplest Thing That Could Possibly Work, which is to drop the sequence and then recreate it with the new starting value.
Best of luck.
in order to find the max STAFF_ID you need the below select:
select max(STAFF_ID) from STAFF;
Once you have the highest STAFF_ID you can re-create the sequence as desired.
In any case you can increment a sequence like so:
ALTER SEQUENCE seq_name INCREMENT BY 1;
I hope that helps!
Please don't hesitate to leave a comment for any further clarifications.
Ted.
Using the sequence guarantees uniqueness of STAFF_ID but does not guarantee no gaps in assigning STAFF_ID. You might end up with STAFF_ID like 100, 101, 103, 106..
First, get the max(STAFF_ID) while the system is not running. Something like
select max(staff_id) from staff;
Then, create the sequence to start from the max staff_id. Something like
create sequence staff_sequence start with <max_id> + 1 increment by 1 nocache;
"NOCACHE" minimizes the chance of having gaps in the staff_id assigned
After, use the trigger that you created to get the nextval from the seuqnece
Note the following:
- Once a sequence is invoked for nextval, that number dispatched cannot be returned to the sequnece
- Any cached sequence values will be lost if oracle database was shutdown
If your requirement is not to have gaps between staff_ids, then sequence might not be used.
i have table which have id when iwnna to insert new record its by default generate new # increment by 1 how can i do it please
the number is begin with year and serial for ex; 20130001,20130002,20130003 and so on , when the year is end then will start 20140001,20140002,20140003
Putting aside the question why you would want to do this.
The most straightforward approach is to create an Oracle SEQUENCE object, starting at the value you want to start with, increment of 1. As an example:
CREATE SEQUENCE myseq START WITH 20130001 INCREMENT BY 1 NOCACHE ;
To make use of the sequence object to supply a value for a table on an INSERT, create a BEFORE INSERT trigger
CREATE TRIGGER mytable_trg_bi
BEFORE INSERT ON mytable
FOR EACH ROW
BEGIN
IF NEW.mycol IS NULL THEN
SELECT myseq.NEXTVAL FROM DUAL INTO NEW.mycol;
END IF;
END;
/
(It's been a while since I've worked with the Oracle syntax; some of the keywords might not be in the right order. But this is the normal pattern for assigning unique, system generated values to a column on an INSERT.
That part is easy.
The trickier part is getting the sequence to "jump" to a specific value.
There's a couple of approaches to doing that. One would be drop the sequence object and re-create a new object, with the same name, with a new starting value. But that's not very elegant, and fairly disruptive.
Another approach is to modify the increment value, select nextval to make it jump, and then set the increment back to 1.
As a rough outline of what that might look like:
DECLARE
ln_val NUMBER;
BEGIN
-- retrieve next value from sequence
SELECT myseq.NEXTVAL FROM DUAL INTO ln_val;
-- set increment so next call to nextval will "jump"
EXECUTE IMMEDIATE
'ALTER SEQUENCE myseq INCREMENT BY '|| 20140001 - 2 - ln_val ||' NOCACHE';
-- this should return us 20140000
SELECT myseq.NEXTVAL FROM DUAL INTO ln_val;
-- reset increment back to 1
EXECUTE IMMEDIATE
'ALTER SEQUENCE myseq INCREMENT BY 1';
END;
/
Note that this approach of setting/resetting the current value of the sequence is subject to a race condition, if another session is pulling values from the SEQUENCE at the same time.
Does this SQL make sense?
CREATE SEQUENCE order_id_sequence
START WITH 310;
INSERT INTO order_id_sequence VALUES (150);
The way you'd usually use a sequence is to select the next sequential value from it:
SELECT ORDER_ID_SEQUENCE.NEXTVAL FROM DUAL;
Or in PL/SQL you can just assign the next value to a variable:
DECLARE
nSeq_value NUMBER;
BEGIN
nSeq_value := ORDER_ID_SEQUENCE.NEXTVAL;
-- ...etc etc etc...
END;
It is possible to alter a sequence to change the minimum value:
ALTER SEQUENCE ORDER_ID_SEQUENCE MINVALUE 150;
However, if you want to change the 'next' value returned by the sequence the only way to do it is to drop the sequence and recreate it.
Share and enjoy.
Not a validate one. You can try it in a testing env.
In fact, test it by yourself is a good way before asking question here.
I have 2 procedures inside a package. I am calling one procedure to get a comma separated list of user ids.
I am storing the result in a VARCHAR variable. Now when I am using this comma separated list to put inside an IN clause in it is throwing "ORA-01722:INVALID NUMBER" exception.
This is how my variable looks like
l_userIds VARCHAR2(4000) := null;
This is where i am assigning the value
l_userIds := getUserIds(deptId); -- this returns a comma separated list
And my second query is like -
select * from users_Table where user_id in (l_userIds);
If I run this query I get INVALID NUMBER error.
Can someone help here.
Do you really need to return a comma-separated list? It would generally be much better to declare a collection type
CREATE TYPE num_table
AS TABLE OF NUMBER;
Declare a function that returns an instance of this collection
CREATE OR REPLACE FUNCTION get_nums
RETURN num_table
IS
l_nums num_table := num_table();
BEGIN
for i in 1 .. 10
loop
l_nums.extend;
l_nums(i) := i*2;
end loop;
END;
and then use that collection in your query
SELECT *
FROM users_table
WHERE user_id IN (SELECT * FROM TABLE( l_nums ));
It is possible to use dynamic SQL as well (which #Sebas demonstrates). The downside to that, however, is that every call to the procedure will generate a new SQL statement that needs to be parsed again before it is executed. It also puts pressure on the library cache which can cause Oracle to purge lots of other reusable SQL statements which can create lots of other performance problems.
You can search the list using like instead of in:
select *
from users_Table
where ','||l_userIds||',' like '%,'||cast(user_id as varchar2(255))||',%';
This has the virtue of simplicity (no additional functions or dynamic SQL). However, it does preclude the use of indexes on user_id. For a smallish table this shouldn't be a problem.
The problem is that oracle does not interprete the VARCHAR2 string you're passing as a sequence of numbers, it is just a string.
A solution is to make the whole query a string (VARCHAR2) and then execute it so the engine knows he has to translate the content:
DECLARE
TYPE T_UT IS TABLE OF users_Table%ROWTYPE;
aVar T_UT;
BEGIN
EXECUTE IMMEDIATE 'select * from users_Table where user_id in (' || l_userIds || ')' INTO aVar;
...
END;
A more complex but also elegant solution would be to split the string into a table TYPE and use it casted directly into the query. See what Tom thinks about it.
DO NOT USE THIS SOLUTION!
Firstly, I wanted to delete it, but I think, it might be informative for someone to see such a bad solution. Using dynamic SQL like this causes multiple execution plans creation - 1 execution plan per 1 set of data in IN clause, because there is no binding used and for the DB, every query is a different one (SGA gets filled with lots of very similar execution plans, every time the query is run with a different parameter, more memory is needlessly used in SGA).
Wanted to write another answer using Dynamic SQL more properly (with binding variables), but Justin Cave's answer is the best, anyway.
You might also wanna try REF CURSOR (haven't tried that exact code myself, might need some little tweaks):
DECLARE
deptId NUMBER := 2;
l_userIds VARCHAR2(2000) := getUserIds(deptId);
TYPE t_my_ref_cursor IS REF CURSOR;
c_cursor t_my_ref_cursor;
l_row users_Table%ROWTYPE;
l_query VARCHAR2(5000);
BEGIN
l_query := 'SELECT * FROM users_Table WHERE user_id IN ('|| l_userIds ||')';
OPEN c_cursor FOR l_query;
FETCH c_cursor INTO l_row;
WHILE c_cursor%FOUND
LOOP
-- do something with your row
FETCH c_cursor INTO l_row;
END LOOP;
END;
/
I need to forward a set of sequences with only DML access. Due to a bug in a piece of code several values were grabbed without a sequence but instead manually, so now the sequence is duplicating those values. So, I would like to push the sequence to the max value so that the next time nextval is called, it gives a value higher than the maximum. I've got about 50 sequences that each have to go a few thousand forward.
Is this possible with only DML access? If so, how should I go about it?
You should determine the difference between the next value of the sequence and the required value. The required value is typically the max value of a primary key column (let's name it ID).
DECLARE
maxid NUMBER;
maxseq NUMBER;
temp NUMBER; -- without this variable Oracle would skip to query the sequence
BEGIN
SELECT MAX(ID) INTO maxid FROM MYTABLE;
SELECT MYSEQ.NEXTVAL INTO maxseq FROM DUAL;
FOR i IN maxseq .. maxid LOOP
SELECT MYSEQ.NEXTVAL INTO temp FROM DUAL;
END LOOP;
END;
/
You can use dynamic SQL to do this. For example, this bit of code will select the next 10,000 values from each of a list of sequences.
DECLARE
l_num INTEGER;
BEGIN
FOR seq IN (select *
from all_sequences
where sequence_name in (<<list of 50 sequences>>)
and sequence_owner = <<owner of sequences>>)
LOOP
FOR i IN 1 .. 10000
LOOP
execute immediate
'select ' || seq.sequence_owner || '.' || seq.sequence_name || '.nextval from dual'
into l_num;
END LOOP;
END LOOP;
END;
If you had the ability to issue DDL against the sequence, you could use a similar approach to set the INCREMENT to 10,000, select one value from the sequence, and set the INCREMENT back down to 1 (or whatever it is now).
you can just
select seq.nextval from dual
until it is big enough...
If you have a table with at least as many rows as the amount you want to add to your sequences, the following will work. This increments each sequence by the same amount, which may not suit you, but it's quick and easy without requiring PL/SQL or the need to drop/re-create the sequence. I use it all the time when I want to get development server sequences ahead of production.
SELECT seq1.nextval, seq2.nextval, ..., seqN.nextval
FROM very_large_table
WHERE ROWNUM <= number_of_rows_to_add
To restart the sequence at a different value you need to drop and recreate it.
See the Oracle docs for ALTER SEQUENCE here.
And for CREATE SEQUENCE here
So, no I don't think it's possible with DML access, unless you just increment repeatedly like Randy suggests.
you can just;
declare
l_MaxVal pls_integer;
l_Currval pls_integer default - 1;
begin
select max(id)
into l_MaxVal
from people;
while l_Currval < l_Maxval
loop
select my_seq.nextval
into l_Currval
from dual;
end loop;
end;
There's a trick you can use to select a sequence of numbers, one per row. For example:
SELECT ROWNUM FROM DUAL CONNECT BY ROWNUM <= 100
Replace ROWNUM with <sequence>.NEXTVAL to fast-forward the sequence in large steps, for instance:
SELECT <sequence>.NEXTVAL FROM DUAL CONNECT BY ROWNUM <= 100
I wouldn't use this for making changes to a production database, but for a dev database, it may be sufficient.