Manually forward a sequence - oracle sql - sql

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.

Related

How to store multiple rows in a variable in pl/sql function?

I'm writing a pl/sql function. I need to select multiple rows from select statement:
SELECT pel.ceid
FROM pa_exception_list pel
WHERE trunc(pel.creation_date) >= trunc(SYSDATE-7)
if i use:
SELECT pel.ceid
INTO v_ceid
it only stores one value, but i need to store all values that this select returns. Given that this is a function i can't just use simple select because i get error, "INTO - is expected."
You can use a record type to do that. The below example should work for you
DECLARE
TYPE v_array_type IS VARRAY (10) OF NUMBER;
var v_array_type;
BEGIN
SELECT x
BULK COLLECT INTO
var
FROM (
SELECT 1 x
FROM dual
UNION
SELECT 2 x
FROM dual
UNION
SELECT 3 x
FROM dual
);
FOR I IN 1..3 LOOP
dbms_output.put_line(var(I));
END LOOP;
END;
So in your case, it would be something like
select pel.ceid
BULK COLLECT INTO <variable which you create>
from pa_exception_list
where trunc(pel.creation_Date) >= trunc(sysdate-7);
If you really need to store multiple rows, check BULK COLLECT INTO statement and examples. But maybe FOR cursor LOOP and row-by-row processing would be better decision.
You may store all in a rowtype parameter and show whichever column you want to show( assuming ceid is your primary key column, col1 & 2 are some other columns of your table ) :
SQL> set serveroutput on;
SQL> declare
l_exp pa_exception_list%rowtype;
begin
for c in ( select *
from pa_exception_list pel
where trunc(pel.creation_date) >= trunc(SYSDATE-7)
) -- to select multiple rows
loop
select *
into l_exp
from pa_exception_list
where ceid = c.ceid; -- to render only one row( ceid is primary key )
dbms_output.put_line(l_exp.ceid||' - '||l_exp.col1||' - '||l_exp.col2); -- to show the results
end loop;
end;
/
SET SERVEROUTPUT ON
BEGIN
FOR rec IN (
--an implicit cursor is created here
SELECT pel.ceid AS ceid
FROM pa_exception_list pel
WHERE trunc(pel.creation_date) >= trunc(SYSDATE-7)
)
LOOP
dbms_output.put_line(rec.ceid);
END LOOP;
END;
/
Notes from here:
In this case, the cursor FOR LOOP declares, opens, fetches from, and
closes an implicit cursor. However, the implicit cursor is internal;
therefore, you cannot reference it.
Note that Oracle Database automatically optimizes a cursor FOR LOOP to
work similarly to a BULK COLLECT query. Although your code looks as if
it fetched one row at a time, Oracle Database fetches multiple rows at
a time and allows you to process each row individually.

SQL CREATE SEQUENCE based on a combination of date and counter

I am using Oracle SQL:
Oracle Database 12c Enterprise Edition Release 12.1.0.2.0 - 64bit Production 0
PL/SQL Release 12.1.0.2.0 - Production 0
CORE 12.1.0.2.0 Production 0
TNS for Linux: Version 12.1.0.2.0 - Production 0
NLSRTL Version 12.1.0.2.0 - Production
I need help figuring out how to create a sequence for a key field below:
I have a table with a field named MY_ID.
MY_ID needs to be automatically generated using sequence when a record is inserted.
The rule for the sequence is a combination of a string prefix, an counter increment by 1 per day and reset at midnight, and the date.
for example:
on Sept 10, we inserted 2 records, then the MY_ID should be:
PREFIX_01_20170910
PREFIX_02_20170910
on Sept 11 :
PREFIX_01_20170911
PREFIX_02_20170911
PREFIX_03_20170911
on Sept 12, the whole table might look like this
PREFIX_01_20170910
PREFIX_02_20170910
PREFIX_01_20170911
PREFIX_02_20170911
PREFIX_03_20170911
PREFIX_01_20170912
so far, all i can do with the sequence is increment by 1 regardless of the date:
CREATE SEQUENCE SEQ_MY_TABLE INCREMENT BY 1 MAXVALUE 9999999999999999999999999999 MINVALUE 1 NOCACHE;
and the trigger:
create or replace TRIGGER MY_TABLE_TRG
BEFORE INSERT ON MY_TABLE
FOR EACH ROW
BEGIN
<<COLUMN_SEQUENCES>>
BEGIN
IF INSERTING AND :NEW.MY_ID IS NULL THEN
SELECT SEQ_MY_TABLE .NEXTVAL INTO :NEW.MY_ID FROM SYS.DUAL;
END IF;
END COLUMN_SEQUENCES;
END;
Thank you!
You can probably dream up a scheme to generate this PREFIX_nn_date key that will appear to work in your development environment, but I'm going to give you a bit of unwanted advice: don't waste your time on it. Make your primary key a simple NUMBER column, populate it from a sequence, and move on. Once your code hits production, where many more users will be banging on your table simultaneously, your carefully crafted scheme to generate that PREFIX_nn_date key is likely to fail - and the more band-aids you throw at it to fix the problems the worse it's going to become.
Best of luck.
You can modify the TRIGGER as follows. Since you need each digit in the sequence as 01,02,03 attached to your prefix, I have used fm specifier in TO_CHAR with '00'. If the total inserts per day exceeds 99 , you need to use fm000
create or replace TRIGGER MY_TABLE_TRG
BEFORE INSERT ON MY_TABLE
FOR EACH ROW
DECLARE
BEGIN
<<COLUMN_SEQUENCES>>
BEGIN
IF INSERTING AND :NEW.MY_ID IS NULL THEN
SELECT 'PREFIX_'||TO_CHAR(SEQ_MY_TABLE.NEXTVAL,'fm00')||'_'||TO_CHAR(SYSDATE,'YYYYMMDD') INTO :NEW.MY_ID FROM SYS.DUAL;
END IF;
END COLUMN_SEQUENCES;
END;
Note: To suggest a good practice, I do not recommend this to be used as your PRIMARY KEY. It would be better to simply make the sequence PRIMARY KEY in all your application code while populating the records.
Following up on Bob Jarvis's answer, you can always create your string when you need it. Here is a simple example.
with records as (
select 1 id, to_date('20170901', 'yyyymmdd') theDate
from dual
union
select 2 id, to_date('20170901', 'yyyymmdd') theDate
from dual
union
select 3 id, to_date('20170902', 'yyyymmdd') theDate
from dual
)
select 'prefix_' ||
to_char(theDate, 'yyyymmdd') || '_' ||
to_char( rank() over (partition by theDate order by id)) prefix
from records
returns:
prefix_20170901_1
prefix_20170901_2
prefix_20170902_1
I don't do that much oracle work, but if you are going to do this repeatedly, you might want to incorporate this logic into a function or view.
The simpliest way is to recreate sequence every midnight via job. But using sequences is not a good idea. I think this ID is important for you, but sequence can cache some values, some values can be missing. So you'll get:
PREFIX_01_20170910
PREFIX_02_20170910
PREFIX_04_20170910
PREFIX_07_20170910
...
and so on. For example you had "cache 10", you inserted 2 records and quited, or did rollback, or something else.
Use just number for increment field and calc this fake ID.
you can create function like below to get new id and use it in the insert query.
CREATE OR REPLACE FUNCTION F_GETID (P_DT IN VARCHAR2) RETURN VARCHAR2
IS
V_NEW_ID VARCHAR2(50);
BEGIN
SELECT 'PREFIX_' || COUNT(*)+1 ||'_' || P_DT INTO V_NEW_ID FROM MY_TABLE
WHERE MY_ID LIKE 'PREFIX%'||P_DT;
RETURN V_NEW_ID;
END;
then
insert into my_table(my_id , ...) values(F_GETID('20170927'),...);

How to generate SQL Code to update 9k rows with values for one column

I need to update one field for 9000 rows with random information on it.
Keep in mind that the table in question does not have Primary Key.
What's the best option to do it?
Thanks in advance.
Create table to hold 9000 random numbers for demonstration purposes:
CREATE TABLE brianl.random
(
VALUE NUMBER
, id INTEGER
);
Insert 9000 rows into the table
Since this is for demonstration purposes, I decided to use a PL/SQL loop as it is easy to understand even if you are not familiar with PL/SQL. I could just as easily used a Common Table Expression (CTE), but if you don't understand the PL/SQL, you won't understand the CTE
DECLARE
l_cnt INTEGER;
BEGIN
FOR i IN 1 .. 9000
LOOP
INSERT INTO random (id)
VALUES (i);
END LOOP;
COMMIT;
SELECT COUNT (*) c
INTO l_cnt
FROM random;
DBMS_OUTPUT.put_line ('-- '
|| TO_CHAR (SYSDATE, 'YYYY.MM.DD HH24:MI:SS')
|| ' count is: '
|| l_cnt);
END;
-- 2016.11.11 10:28:42 count is: 9000
Update 9000 records with random values with a single update statement
Now that we have a table of 9000 values, we can update 9000 values as requested`.
update random set value = dbms_random.value;
commit;
-- You can see those 9000 random values with a select from the table we
created for this purpose
select * from random;
Alternatives to PL/SQL for creating table records
And finally, some people have commented that the PL/SQL is bad practice for creating the values (I disagree, to me it is straightforward, and because it is procedural, easy to understand for the beginner), Here is a CTE that performs the same function. Don't forget to commit after it has finished executing.
INSERT INTO random (id)
WITH iset (num)
AS (SELECT 1
FROM DUAL
UNION ALL
SELECT num + 1
FROM iset
WHERE num < 9000)
SELECT *
FROM iset;
Another method people sometimes use is to select from all_objects. I personally don't care for this method as I believe that tables should only be used for their stated purpose. This is also defective if you need to go above the number of objects in all_objects, which on the system I am using is 82,009,
INSERT INTO random (id)
SELECT ROWNUM
FROM all_objects
WHERE ROWNUM <= 9000;
So, on the Oracle system I am creating these demonstrations on, the following will only insert 82009 rows into the table, not the requested 100000.
INSERT INTO random (id)
SELECT ROWNUM
FROM all_objects
WHERE ROWNUM <= 100000;

Same seq_name.nextval in one query. ORACLE

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.

Duplicate a row 100 times in SQL DEVELOPER

I want to insert a row 100 times in a table based on count. For example, I have a table with table_id, table_name and want the rows
1,asd
2,asd
3,asd
4,asd
'
'
'
100,asd
I am looking for a solution using SQL Developer.
You can use the connect by level syntax to easily produce your result.
select level, 'asd'
from dual
connect by level <= 100
Or, as FSP notes an anonymous PL/SQL block with a loop, which isn't as good a solution as you should always use SQL over PL/SQL if possible...
begin
for i in 1 .. 100 loop
insert into my_table(table_id, table_name)
values(i, 'asd');
end loop;
end;
/
If you are using oracle it can be done with a single stamtement:
insert into your_table (table_id, table_name) select level, 'asd' from dual connect by level <= 100;