Inserting a single sequence value on multiple rows - sql

I'm trying to insert multiple records into a table, but using the same sequence value for every record.
This is similiar to: How can I insert multiple rows into oracle with a sequence value? however the answer given inserts multiple, different sequence numbers, and I want the same sequence number for multiple recs.
create table test1 (
col_a number,
col_b number
);
commit;
create sequence test_seq increment by 1 start with 2 minvalue 1 nocycle nocache noorder;
commit;
insert into test1 (col_a, col_b)
select a.object_id, test_seq.nextval from (
select object_id from all_objects where rownum < 5
) a;
commit;
The problem with the above is that it retrieves and inserts multiple (different) "test_seq.nextval" values, and I want the same value inserted for every row.
Is this even possible in straight sql without resorting to a trigger (or multiple sql statements)? One of the answers to the related question hinted it may not be, but it wasn't clear to me.
Thanks.
I'm using Oracle 11g if that helps.

use currval instead of nextval.
select test_seq.nextval from dual;
insert into test1 (col_a, col_b)
select a.object_id, test_seq.currval from (
select object_id from all_objects where rownum < 5
) a;
I know of no method to do that without two statements, the first to increment the sequence (and thus make it selectable through currval) and the second to use currval.

Related

How to insert two tables in a single query using oracle?

Here I want to insert data in two tables,
1. COMPONENT_MASTER
2. COMPONENT_TO_ACTION
For this I wrote two queries to insert.But there is a problem with two queries.
Queries are:
INSERT INTO COMPONENT_MASTER(COMPONENT_ID,ROW_ID,COMPONENT_IDENTIFICATION,COMPONENT_NAME,COMPONENT_TYPE,COMPONENT_STATE,FECHA_DE_CREACION,REVISION)
SELECT MAX(COMPONENT_ID) + 1,?,?,?,?,?,CURRENT_TIMESTAMP,? FROM COMPONENT_MASTER
2nd
insert into COMPONENT_TO_ACTION (ORDER_NUMBER,ACTION_ID,COMPONENT_ID,FECHA_DE_CREACION,GRABADO_POR,STATUS,REVISION)
select max(ORDER_NUMBER) +1,?,?,current_timestamp,?,?,? from COMPONENT_TO_ACTION.
Both queries need to insert execute at a time. So both need to insert with same component_id.
Can I write a single Query to insert in two tables.
regarding to your question multi table insert syntax look like:
INSERT ALL
into COMPONENT_MASTER(COMPONENT_ID,ROW_ID,COMPONENT_IDENTIFICATION,COMPONENT_NAME,COMPONENT_TYPE,COMPONENT_STATE,FECHA_DE_CREACION,REVISION)
values (COMPONENT_ID,ROW_ID,COMPONENT_IDENTIFICATION,COMPONENT_NAME,COMPONENT_TYPE,COMPONENT_STATE,FECHA_DE_CREACION,REVISION)
into COMPONENT_TO_ACTION (ORDER_NUMBER,ACTION_ID,COMPONENT_ID,FECHA_DE_CREACION,GRABADO_POR,STATUS,REVISION)
values (ORDER_NUMBER,ACTION_ID,COMPONENT_ID,FECHA_DE_CREACION,GRABADO_POR,STATUS,REVISION)
SELECT (select MAX(COMPONENT_ID) + 1 from COMPONENT_MASTER) as COMPONENT_ID,
(select max(ORDER_NUMBER) + 1 from COMPONENT_TO_ACTION) as ORDER_NUMBER,
? as ACTION_ID,
? as GRABADO_POR,
? as STATUS
? as ROW_ID,
? as COMPONENT_IDENTIFICATION,
? as COMPONENT_NAME,
? as COMPONENT_TYPE,
? as COMPONENT_STATE,
CURRENT_TIMESTAMP as FECHA_DE_CREACION,
? as REVISION
FROM DUAL;
but as guys advise you not to use select max(...) + 1. Oracle has special object to create unique consequent identifiers is sequence. And you cant use sequence in subquery with multitable insert, but you may add it into values(...) clause and use it with nextval and curval. Tom Kyte said that you may rely on insert order . Please change query to use it and in result you should get something like that:
-- for example I create two sequence
CREATE SEQUENCE COMPONENT_MASTER_seq
START WITH 100000
INCREMENT BY 1
NOCACHE
NOCYCLE;
CREATE SEQUENCE COMPONENT_TO_ACTION_seq
START WITH 100000
INCREMENT BY 1
NOCACHE
NOCYCLE;
INSERT ALL
into COMPONENT_MASTER(COMPONENT_ID,ROW_ID,COMPONENT_IDENTIFICATION,COMPONENT_NAME,COMPONENT_TYPE,COMPONENT_STATE,FECHA_DE_CREACION,REVISION)
values (-- /*COMPONENT_ID*/
COMPONENT_MASTER_seq.nextval,ROW_ID,COMPONENT_IDENTIFICATION,COMPONENT_NAME,COMPONENT_TYPE,COMPONENT_STATE,FECHA_DE_CREACION,REVISION)
into COMPONENT_TO_ACTION (ORDER_NUMBER,ACTION_ID,COMPONENT_ID,FECHA_DE_CREACION,GRABADO_POR,STATUS,REVISION)
values (--/*ORDER_NUMBER*/
COMPONENT_TO_ACTION_seq.nextval ,ACTION_ID,
--/*COMPONENT_ID*/
COMPONENT_MASTER_seq.curval,FECHA_DE_CREACION,GRABADO_POR,STATUS,REVISION)
SELECT --COMPONENT_MASTER_seq.nextval as COMPONENT_ID, -- Oracle prohibit to use sequence with multi table insert
--COMPONENT_TO_ACTION_seq.nextval as ORDER_NUMBER, -- Oracle prohibit to use sequence with multi table insert
? as ACTION_ID,
? as GRABADO_POR,
? as STATUS
? as ROW_ID,
? as COMPONENT_IDENTIFICATION,
? as COMPONENT_NAME,
? as COMPONENT_TYPE,
? as COMPONENT_STATE,
CURRENT_TIMESTAMP as FECHA_DE_CREACION,
? as REVISION
FROM DUAL;

How to create a sequence in oracle from top to bottom

So I have two tables in Oracle. Table A is the master table and Table B is data retrieved from a contractor. They both have the same general structure. In the end, I would like to INSERT INTO TABLE A(SELECT * FROM TABLE B). However the primary key column in Table B does not exist. How do you suggest creating a primary key that at the same time generates a sequence from 4328 and on for every row in Table B?
I proceeded to do the following:
create sequence myseq
increment by 1
start with 4328
MAXVALUE 99999
NOCACHE
NOCYCLE;
Then created a PK column to finally implemented the following:
INSERT INTO TABLE B (PK) VALUES(MYSEQ.nextVal);
But no results yielded except putting in one row at the very end. I want every row to be populated starting at 4328 and ending 291 rows later.
Sorry, but I don't know if I undestand your problem.
Do you want to insert one row in Table A and Table B with equal PK value?
You can do it by procedure put the sequence value in a variable before insert the rows, for example:
BEGIN
SELECT MYSEQ.nextval INTO v_SEQUENCE FROM dual;
insert into table_A values (v_SEQUENCE,1,2,3);
insert into table_B values (v_SEQUENCE,1,2,3);
END;
If you can get all rows from Table_B and insert into table_A with a PK you can do for example:
INSERT INTO TABLE_A (SELECT MYSEQ.nextval, B.* FROM TABLE_B B)
Is it?
Your approach only calls the sequence once. What you want to do is perform a loop in PL/SQL to call the sequence as many times as needed:
BEGIN
FOR x IN 1 .. 291 LOOP
INSERT INTO TABLE B (PK) VALUES(MYSEQ.nextVal);
END LOOP;
END;
Make sure you drop and recreate your sequence to ensure it starts at the right value.

Oracle SQL: How to generate per-order sequence numbers

I would like to generate unique sequence numbers for each order comment as they are entered.
For example, the segment:
INSERT INTO order_comments(order_number,
seq_num,
comment_text,
remark_timestamp)
VALUES (
'Smith',
(SELECT NVL (MAX (seq_num), 0) + 1
FROM order_comments
WHERE order_number='141592'),
'Shipped updated version 1.1’,
current_timestamp);
can be run multiple times, getting a new sequence number for that user each time.
The bad news is when this is written in Pro*C and we have multiple copies running simultaneously, we seem to get locks if we try to insert two comments for the same user at the same time. We could have an order_comment_sequence and just use nextval in the insert but that seems untidy since the “141592” comments will not have sequential numbers.
For the sake of the current discussion, we will say we have a few million orders, each of which will have five or ten comments. The order_number and seq_num are the primary key.
Is there something wrong with doing it this way? Is there an easier way of doing it?
You can use Oracle Sequence.
First create the sequence, like this:
CREATE SEQUENCE order_comments_seq
MINVALUE 1
MAXVALUE 999999999999999999999999999
START WITH 1
INCREMENT BY 1
CACHE 20;
For more info: http://docs.oracle.com/cd/B28359_01/server.111/b28286/statements_6015.htm#SQLRF01314
then you can use sequence.NEXTVAL like this:
INSERT INTO order_comments(order_number,
seq_num,
comment_text,
remark_timestamp)
VALUES ( 'Smith',
order_comments_seq.NEXTVAL,
'Shipped updated version 1.1’,
current_timestamp);
I don't think you can avoid the "untidy" approach if you want to avoid locks. You can generate a sequence number by ORDER_NUMBER when retrieving data from the database as follows:
SELECT order_number, ROW_NUMBER() OVER ( PARTITION BY order_number ORDER BY remark_timestamp ) AS seq_num
FROM order_comments
The "sequence" approach given by #benji is one approach. Another is to generate a random number using SYS_GUID():
TO_NUMBER( SYS_GUID(), 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' )
You could even use this on the SEQ_NUM column as a default:
ALTER TABLE order_comments MODIFY seq_num DEFAULT TO_NUMBER( SYS_GUID(), 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' );
Hope this helps.

Reference value of serial column in another column during same INSERT

I have a table with a SERIAL primary key, and also an ltree column, whose value I want to be the concatenation of those primary keys. e.g.
id | path
----------
1 1
2 1.2
3 1.2.3
4 1.4
5 1.5
I'm curious if there's a way to do such an insert in one query, e.g.
INSERT INTO foo (id, ltree) VALUES (DEFAULT, THIS.id::text)
I'm probably overreaching here and trying to do in one query what I should be doing in two (grouped in a transaction).
You could use a CTE to retrieve the value from the sequence once and use it repeatedly:
WITH cte AS (
SELECT nextval('foo_id_seq') AS id
)
INSERT INTO foo (id, ltree)
SELECT id, '1.' || id
FROM cte;
The CTE with a data-modifying command requires Postgres 9.1 or later.
If you are not sure about the name of the sequence, use
pg_get_serial_sequence() instead:
WITH i AS (
SELECT nextval(pg_get_serial_sequence('foo', 'id')) AS id
)
INSERT INTO foo (id, ltree)
SELECT id, '1.' || id
FROM i;
If the table name "foo" might not be unique across all schemas in the DB, schema-qualify it. And if the spelling of any name is non-standard, you have to double-quote:
pg_get_serial_sequence('"My_odd_Schema".foo', 'id')
Quick tests indicated #Mark's idea with lastval() might work too:
INSERT INTO foo (ltree) VALUES ('1.' || lastval());
You can just leave id out of the query, the serial column will be assigned automatically. Makes no difference.
There shouldn't be a race condition between rows. I quote the manual:
currval
Return the value most recently obtained by nextval for this sequence in the current session. (An error is reported if nextval has
never been called for this sequence in this session.) Because this is
returning a session-local value, it gives a predictable answer whether
or not other sessions have executed nextval since the current session
did.
This function requires USAGE or SELECT privilege on the sequence.
lastval
Return the value most recently returned by nextval in the current session. This function is identical to currval, except that instead of
taking the sequence name as an argument it refers to whichever
sequence nextval was most recently applied to in the current session.
It is an error to call lastval if nextval has not yet been called in
the current session.
This function requires USAGE or SELECT privilege on the last used sequence.
Bold emphasis mine.
But, as #Bernard commented, it can fail after all: there is no guarantee that the default value is filled (and nextval() called in the process) before lastval() is called to fill the 2nd column ltree. So stick with the first solution and nextval() to be sure.
This worked in my test:
INSERT INTO foo (id, ltree) VALUES (DEFAULT, (SELECT last_value from foo_id_seq));
I think there's a race condition there if two INSERTs are happening at the same time, since this references the last sequence value, instead of the current row. I would personally be more inclined to do this (pseudo-code):
my $id = SELECT nextval('foo_id_seq');
INSERT INTO foo (id, ltree) VALUES ($id, '$id');

Rolling rows in SQL table

I'd like to create an SQL table that has no more than n rows of data. When a new row is inserted, I'd like the oldest row removed to make space for the new one.
Is there a typical way of handling this within SQLite?
Should manage it with some outside (third-party) code?
Expanding on Alex' answer, and assuming you have an incrementing, non-repeating serial column on table t named serial which can be used to determine the relative age of rows:
CREATE TRIGGER ten_rows_only AFTER INSERT ON t
BEGIN
DELETE FROM t WHERE serial <= (SELECT serial FROM t ORDER BY serial DESC LIMIT 10, 1);
END;
This will do nothing when you have fewer than ten rows, and will DELETE the lowest serial when an INSERT would push you to eleven rows.
UPDATE
Here's a slightly more complicated case, where your table records "age" of row in a column which may contain duplicates, as for example a TIMESTAMP column tracking the insert times.
sqlite> .schema t
CREATE TABLE t (id VARCHAR(1) NOT NULL PRIMARY KEY, ts TIMESTAMP NOT NULL);
CREATE TRIGGER ten_rows_only AFTER INSERT ON t
BEGIN
DELETE FROM t WHERE id IN (SELECT id FROM t ORDER BY ts DESC LIMIT 10, -1);
END;
Here we take for granted that we cannot use id to determine relative age, so we delete everything after the first 10 rows ordered by timestamp. (SQLite imposes an arbitrary order on rows sharing the same ts).
Seems SQLite's support for triggers can suffice: http://www.sqlite.org/lang_createtrigger.html
article on fixed queues in sql: http://www.xaprb.com/blog/2007/01/11/how-to-implement-a-queue-in-sql
should be able to use the same technique to implement "rolling rows"
This would be something like how you would do it. This assumes that my_id_column is auto-incrementing and is the ordering column for the table.
-- handle rolls forward
-- deletes the oldest row
create trigger rollfwd after insert on my_table when (select count() from my_table) > max_table_size
begin
delete from my_table where my_id_column = (select min(my_id_column) from my_table);
end;
-- handle rolls back
-- inserts an empty row at the position before oldest entry
-- assumes all columns option or defaulted
create trigger rollbk after delete on my_table when (select count() from my_table) < max_table_size
begin
insert into my_table (my_id_column) values ((select min(my_id_column) from my_table) - 1);
end;