Oracle SQL: How to generate per-order sequence numbers - sql

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.

Related

Get identity of row inserted in Snowflake Datawarehouse

If I have a table with an auto-incrementing ID column, I'd like to be able to insert a row into that table, and get the ID of the row I just created. I know that generally, StackOverflow questions need some sort of code that was attempted or research effort, but I'm not sure where to begin with Snowflake. I've dug through their documentation and I've found nothing for this.
The best I could do so far is try result_scan() and last_query_id(), but these don't give me any relevant information about the row that was inserted, just confirmation that a row was inserted.
I believe what I'm asking for is along the lines of MS SQL Server's SCOPE_IDENTITY() function.
Is there a Snowflake equivalent function for MS SQL Server's SCOPE_IDENTITY()?
EDIT: for the sake of having code in here:
CREATE TABLE my_db..my_table
(
ROWID INT IDENTITY(1,1),
some_number INT,
a_time TIMESTAMP_LTZ(9),
b_time TIMESTAMP_LTZ(9),
more_data VARCHAR(10)
);
INSERT INTO my_db..my_table
(
some_number,
a_time,
more_data
)
VALUES
(1, my_time_value, some_data);
I want to get to that auto-increment ROWID for this row I just inserted.
NOTE: The answer below can be not 100% correct in some very rare cases, see the UPDATE section below
Original answer
Snowflake does not provide the equivalent of SCOPE_IDENTITY today.
However, you can exploit Snowflake's time travel to retrieve the maximum value of a column right after a given statement is executed.
Here's an example:
create or replace table x(rid int identity, num int);
insert into x(num) values(7);
insert into x(num) values(9);
-- you can insert rows in a separate transaction now to test it
select max(rid) from x AT(statement=>last_query_id());
----------+
MAX(RID) |
----------+
2 |
----------+
You can also save the last_query_id() into a variable if you want to access it later, e.g.
insert into x(num) values(5);
set qid = last_query_id();
...
select max(rid) from x AT(statement=>$qid);
Note - it will be usually correct, but if the user e.g. inserts a large value into rid manually, it might influence the result of this query.
UPDATE
Note, I realized the code above might rarely generate incorrect answer.
Since the execution order of various phases of a query in a distributed system like Snowflake can be non-deterministic, and Snowflake allows concurrent INSERT statements, the following might happen
Two queries, Q1 and Q2, do a simple single row INSERT, start at roughly the same time
Q1 starts, is a bit ahead
Q2 starts
Q1 creates a row with value 1 from the IDENTITY column
Q2 creates a row with value 2 from the IDENTITY column
Q2 gets ahead of Q1 - this is the key part
Q2 commits, is marked as finished at time T2
Q1 commits, is marked as finished at time T1
Note that T1 is later than T2. Now, when we try to do SELECT ... AT(statement=>Q1), we will see the state as-of T1, including all changes from statements before, hence including the value 2 from Q2. Which is not what we want.
The way around it could be to add a unique identifier to each INSERT (e.g. from a separate SEQUENCE object), and then use a MAX.
Sorry. Distributed transactions are hard :)
If I have a table with an auto-incrementing ID column, I'd like to be
able to insert a row into that table, and get the ID of the row I just
created.
FWIW, here's a slight variation of the current accepted answer (using Snowflake's 'Time Travel' feature) that gives any column values "of the row I just created." It applies to auto-incrementing sequences and more generally to any column configured with a default (e.g. CURRENT_TIMESTAMP() or UUID_STRING()). Further, I believe it avoids any inconsistencies associated with a second query utilizing MAX().
Assuming this table setup:
CREATE TABLE my_db.my_table
(
ROWID INT IDENTITY(1,1),
some_number INT,
a_time TIMESTAMP_LTZ(9),
b_time TIMESTAMP_LTZ(9),
more_data VARCHAR(10)
);
Make sure the 'Time Travel' feature (change_tracking) is enabled for this table with:
ALTER TABLE my_db.my_table SET change_tracking = true;
Perform the INSERT per usual:
INSERT INTO my_db.my_table
(
some_number,
a_time,
more_data
)
VALUES
(1, my_time_value, some_data);
Use the CHANGES clause with BEFORE(statement... and END(statement... specified as LAST_QUERY_ID() to SELECT the row(s) added to my_table which are the precise result of the previous INSERT statement (with column values that existed the moment the row(s) was(were) added, including any defaults):
SET insertQueryId=LAST_QUERY_ID();
SELECT
ROWID,
some_number,
a_time,
b_time,
more_data
FROM my_db.my_table
CHANGES(information => default)
BEFORE(statement => $insertQueryId)
END(statement => $insertQueryId);
For more information on the CHANGES, BEFORE, END clauses see the Snowflake documentation here.

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;

inserting id into table with random number

I need to write a view that does a merge insert/update.
When inserting, I need to insert id.
This id is also being inserted by random number generator in another program ( which I can't change ).
I wanted to do max(id) + 1, but not sure if that is a good idea. Could you suggest any better solutions for this problem?
or
How about using with id as ( dbms_random .... ) do a
select * from table where id = ?
if row is not found, I will insert this id otherwise, I will generate another random and do a select.
if this is for a Primary Key - then how about generating negative numbers for your part of the app (using a sequence) and leaving the random number wizardry in the positives...
You can use rand() function for your request with random number !
Enjoy,
remontees

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');

Inserting a single sequence value on multiple rows

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.