Obtaining an inserted recordid on Oracle db - oracle

I'm using Oracle on database server, from an XP client, using VB6 and ADO. In one transaction, I'm inserting one record into a parent table, which has a trigger and sequence to create a unique recordid, then that recordid is used for the relationship to a child table for a variable number of inserts to the child table. For performance, this is being sent in one execute command from my client app. For instance (simplified example):
declare Recordid int;
begin
insert into ParentTable (_field list_) Values (_data list_);
Select ParentTableSequence.currVal into Recordid from dual;
insert into ChildTable (RecordID, _field list_) Values (Recordid, _data list_);
insert into ChildTable (RecordID, _field list_) Values (Recordid, _data list_);
... multiple, variable number of additional ChildTable inserts
commit;
end;
This is working fine. My question is: I also need to return to the client the Recordid that was created for the inserts. On SQL Server, I can add something like a select to Scope_Identity() after the commit to return a recordset to the client with the unique id.
But how can I do something similar for Oracle (doesn't have to be a recordset, I just need that long integer value)? I've tried a number of things based on results from searching the 'net, but have failed in finding a solution.

These two lines can be compressed into a single statement:
-- insert into ParentTable (field list) Values (data list);
-- Select ParentTableSequence.currVal into Recordid from dual;
insert into ParentTable (field list) Values (data list)
returning ParentTable.ID into Recordid;
If you want to pass the ID back to the calling program you will need to define your program as a stored procedure or function, returning Recordid as an OUT parameter or a RETURN value respectively.
Edit
MarkL commented:
This is more of an Oracle PL/SQL
question than anything else, I
believe.
I confess that I no nothing about ADO, so I don't know whether the following example will work in your case. It involves building some infrastructure which allows us to pass an array of values into a procedure. The following example creates a new department, promotes an existing employee to manage it and assigns two new hires.
SQL> create or replace type new_emp_t as object
2 (ename varchar2(10)
3 , sal number (7,2)
4 , job varchar2(10));
5 /
Type created.
SQL>
SQL> create or replace type new_emp_nt as table of new_emp_t;
2 /
Type created.
SQL>
SQL> create or replace procedure pop_new_dept
2 (p_dname in dept.dname%type
3 , p_loc in dept.loc%type
4 , p_mgr in emp.empno%type
5 , p_staff in new_emp_nt
6 , p_deptno out dept.deptno%type)
7 is
8 l_deptno dept.deptno%type;
9 begin
10 insert into dept
11 (dname, loc)
12 values
13 (p_dname, p_loc)
14 returning deptno into l_deptno;
15 update emp
16 set deptno = l_deptno
17 , job = 'MANAGER'
18 , mgr = 7839
19 where empno = p_mgr;
20 forall i in p_staff.first()..p_staff.last()
21 insert into emp
22 (ename
23 , sal
24 , job
25 , hiredate
26 , mgr
27 , deptno)
28 values
29 (p_staff(i).ename
30 , p_staff(i).sal
31 , p_staff(i).job
32 , sysdate
33 , p_mgr
34 , l_deptno);
35 p_deptno := l_deptno;
36 end pop_new_dept;
37 /
Procedure created.
SQL>
SQL> set serveroutput on
SQL>
SQL> declare
2 dept_staff new_emp_nt;
3 new_dept dept.deptno%type;
4 begin
5 dept_staff := new_emp_nt(new_emp_t('MARKL', 4200, 'DEVELOPER')
6 , new_emp_t('APC', 2300, 'DEVELOPER'));
7 pop_new_dept('IT', 'BRNO', 7844, dept_staff, new_dept);
8 dbms_output.put_line('New DEPTNO = '||new_dept);
9 end;
10 /
New DEPTNO = 70
PL/SQL procedure successfully completed.
SQL>
The primary keys for both DEPT and EMP are assigned through triggers. The FORALL syntax is a very efficient way of inserting records (it also works for UPDATE and DELETE). This could be written as a FUNCTION to return the new DEPTNO instead, but it is generally considered better practice to use a PROCEDURE when inserting, updating or deleting.
That would be my preferred approach but I admit it's not to everybody's taste.
Edit 2
With regards to performance, bulk operations using FORALL will definitely perform better than a handful of individual inserts. In SQL, set operations are always preferable to record-by-record. However, if we are dealing with only a handful of records each time it can be hard to notice the difference.
Building a PL/SQL collection (what you think of as a temporary table in SQL Server) can be expensive in terms of memory. This is especially true if there are many users running the code, because it comes out of the session level allocation of memory, not the Shared Global Area. When we're dealing with a large number of records it is better to populate an array in chunks, perhaps using the BULK COLLECT syntax with a LIMIT clause.
The Oracle online documentation set is pretty good. The PL/SQL Developer's Guide has a whole chapter on Collections. Find out more.

Related

Find tables with specific criteria - Oracle 12c

How to find tables in a schema that start with NUM_ whose data is more than 0 records.
Eg:
Consider I'm a couple of schemas
Schema1
Schema2
Schema3
Schema1 has tables:
NUM_table11 (has 0 records)
NUM_table12 (has 20 records)
DummyTable1 (has 10 records)
Schema2 has tables:
NUM_table21 (has 0 records)
NUM_table22 (has 20 records)
DummyTable2 (has 10 records)
Schema3 has tables:
NUM_table31 (has 0 records)
NUM_table32 (has 20 records)
DummyTable3 (has 10 records)
I want to get only Schema2 tables, whose name starts with NUM_ and has more than 0 records.
i.e, in this case NUM_table22
Can anyone help on achieving this?
I don't have your tables (and don't feel like creating ones either), so I'll show it on another example - looking for the EMP tables throughout my database.
Connect as a privileged user, the one that has access to DBA_TABLES (as you want to search all users, right?). One of those is SYS, if you don't have any other.
Then write a PL/SQL block which looks into the DBA_TABLES, searches for tables whose name begins with EMP. Dynamic SQL (i.e. execute immediate) counts rows in those tables and - if it is a positive number - returns that table as an output.
SQL> show user
USER is "SYS"
SQL> set serveroutput on
SQL>
SQL> declare
2 l_cnt number;
3 begin
4 for cur_r in (select owner, table_name
5 from dba_tables
6 where table_name like 'EMP%'
7 )
8 loop
9 execute immediate 'select count(*) from ' || cur_r.owner ||'.'||
10 cur_r.table_name into l_cnt;
11 if l_cnt > 0 then
12 dbms_output.put_line(cur_r.owner ||'.'|| cur_r.table_name ||' = ' || l_cnt);
13 end if;
14 end loop;
15 end;
16 /
HR.EMPLOYEES = 107
SCOTT.EMP = 14
PL/SQL procedure successfully completed.
SQL>
In my databases, there are two such tables:
one is owned by HR, its name is EMPLOYEES and contains 107 rows
another one is owned by SCOTT, its name is EMP and contains 14 rows
I believe that you shouldn't have any problems in adjusting that code to your needs.
I'm not sure why would you want to search only through schema2; in that case, it is simpler to connect as schema2 and do the same job using user_tables (or all_tables) (so you don't have to connect as a privileged user).
Or, if you want to pick the user within that code, you'd add yet another condition into the where clause (line #6):
and owner = 'SCHEMA2'
Littlefoot's answer would work but may take longer. Assuming stats has been collected the following SQL should give you want you want and much faster. If stats are not fresh then it can give wrong result.
select * from DBA_TABLES where TABLE_NAME like 'NUM_%' and NUM_ROWS > 0

PLSQL Trigger - Update another table before inserting a new record

I have 3 tables that are related to each other:
ACCOUNTS
CARDS
TRANSACTIONS
I want to change the money amount from account every time I execute a new transaction. I want to decrease the account value with each new move.
I tried writing this trigger:
create or replace trigger ceva_trig1
before insert on miscari
for each row
declare
new_val micari.valoare%tipe := new.valoare;
begin
update conturi
set sold = sold - new_val
where nrcont = (select nrcont
from conturi
join carti_de_credit on conturi.nrcont = carti_de_credit.nrcont
join miscari on carti_de_credit.nr_card = miscari.nrcard)
and sold >= new_val;
end;
May anyone help me correct the syntax that crashes here?
I've created those tables with minimal number of columns, just to make trigger compile.
SQL> create table conturi
2 (sold number,
3 nrcont number
4 );
Table created.
SQL> create table miscari
2 (valoare number,
3 nrcard number
4 );
Table created.
SQL> create table carti_de_credit
2 (nrcont number,
3 nr_card number
4 );
Table created.
Trigger:
SQL> create or replace trigger ceva_trig1
2 before insert on miscari
3 for each row
4 begin
5 update conturi c
6 set c.sold = c.sold - :new.valoare
7 where c.nrcont = (select r.nrcont
8 from carti_de_credit r
9 where r.nrcont = c.nrcont
10 and r.nr_card = :new.nrcard
11 )
12 and c.sold >= :new.valoare;
13 end;
14 /
Trigger created.
SQL>
How does it differ from your code? Like this:
SQL> create or replace trigger ceva_trig1
2 before insert on miscari
3 for each row
4 declare
5 new_val micari.valoare%tipe := new.valoare;
6 begin
7 update conturi
8 set sold = sold - new_val
9 where nrcont = (select nrcont
10 from conturi
11 join carti_de_credit on conturi.nrcont = carti_de_credit.nrcont
12 join miscari on carti_de_credit.nr_card = miscari.nrcard)
13 and sold >= new_val;
14 end;
15 /
Warning: Trigger created with compilation errors.
SQL> show err
Errors for TRIGGER CEVA_TRIG1:
LINE/COL ERROR
-------- -----------------------------------------------------------------
2/11 PL/SQL: Item ignored
2/26 PLS-00208: identifier 'TIPE' is not a legal cursor attribute
4/3 PL/SQL: SQL Statement ignored
10/15 PL/SQL: ORA-00904: "NEW_VAL": invalid identifier
10/15 PLS-00320: the declaration of the type of this expression is incomplete or malformed
SQL>
Explained:
it isn't tipe but type
new column values are referenced with a colon, i.e. :new.valoare
you shouldn't make typos regarding table & column names; it is miscari, not micari
it is bad practice to write query which references the same table (miscari, line #12) trigger is created for. As it is being changed, you can't select values from it as it is mutating
lucky you, you don't have to do that at all. How? Have a look at my code.
Attempting to maintain an ongoing for transactions in one table in another table is always a bad idea. Admittedly in an extremely few cases it's necessary, but should be the design of last resort not an initial one; even when necessary it's still a bad idea and therefore requires much more processing and complexity.
In this instance after you correct all the errors #Littlefoot points out then your real problems begin. What do you do when: (Using Littlefoot's table definitions)
I delete a row from miscari?
I update a row in miscari?
The subselect for nrcont returns 0 rows?
The condition sold >= new_val is False?
If any of conditions occur the value for sold in conturi is incorrect and may not be correctable from values in the source table - miscari. An that list may be just the beginning of the issues you face.
Suggestion: Abandon the idea of keeping an running account of transaction values. Instead derive it when needed. You can create a view that does that and select from the view.
So maybe instead of "create table conturi ..."

Oracle SEQUENCE.Currval problem in CodeIgniter

I have a sequence named WCOMP_SEQ in oracle to generate auto increment column ON WCOMP table. When I insert a row to WCOMP table in SQLPlus, the row inserted and I can get the auto increment value using
SELECT WCOMP_SEQ.currval FROM dual
But when I ran insert a row using Database Class in CodeIgniter, the row inserted but when I ran the query above to get auto increment value I got Exception:
Exception: Undefined Index currval in E:...
How to fix this?
There is a way to get the value automatically assigned to a column: it is the RETURNING clause.
So, here is my sequence:
SQL> select emp_seq.currval from dual
2 /
CURRVAL
----------
8140
SQL>
I'm going to use it in an INSERT statement:
SQL> var seqval number
SQL> insert into emp
2 (empno, ename, deptno, sal, job)
3 values
4 (emp_seq.nextval, 'JELLEMA', 50, 4575, 'PAINTER')
5 returning empno into :seqval
6 /
1 row created.
SQL>
I returned the EMPNO into a SQL*Plus variable which I can print, and it has the same value as CURRVAL:
SQL> print :seqval
SEQVAL
----------
8141
SQL> select emp_seq.currval from dual
2 /
CURRVAL
----------
8141
SQL>
Your next question is, "does CodeIgniter support the RETURNING sysntax?" I have no idea, but I suspect it does not. Most non-Oracle frameworks don't.
There is always the option to wrap the INSERT statement in a stored procedure, but that's an architectural decision whoch many people dislike.
You can not fetch the SEQUENCE current value without issuing NEXTVAL (see here). So, if you do not want to increment the sequence value (by using NEXTVAL), you should instead query USER_SEQUENCES.
Something like this:
select Sequence_Name
, Last_Number
from user_sequences
where sequence_name = 'WCOMP_SEQ'
/
SEQUENCE_NAME LAST_NUMBER
------------- -----------
WCOMP_SEQ 20
Hope this helps.
In order to get currval on the sequence you will need to have at least one reference to the corresponding nextval for the sequence in the current user session. This is what causes it to set the currval value which would belong to the session.
If you are using it outside, it defeats the purpose which value could it return if there were other sessions active.

Using Oracle temp table for multiple async HTTP calls

I have a Silverlight app that makes multiple (often concurrent) asynchronous calls to an Oracle database. The largest database table stores around 5 million records. Below is a summary of how the Silverlight app works, followed by my question.
The user sets query criteria to select a particular group of records, usually 500 to 5000 records in a group.
An asynchronous WCF call is made to the database to retrieve the values in four fields (latitude, longitude, heading, and time offset) over the selected group of records (meaning the call returns anywhere from 2k to 20k floating point numbers. These values are used to plot points on a map in the browser.
From here, the user can choose to graph the values in one or more of an additional twenty or so fields associated with the initial group of records. The user clicks on a field name, and another async WCF call is made to retrieve the field values.
My question is this: does it make sense in this case to store the records selected in step one in a temp table (or materialized view) in order to speed up and simplify the data access in step three?
If so, can anyone give me a hint regarding a good way to maintain the browser-to-temp-table link for a user's session?
Right now, I am just re-querying the 5 million points each time the user selects a new field to graph--which works until the user selects three or more fields at once. This causes the async calls to timeout before they can return.
We can do this using a CONTEXT. This is a namespace in session memory which we can use to store values. Oracle comes with a default namespace, 'USERENV', but we can define our own. The context has to be created by a user with the CREATE ANY CONTEXT privilege; this is usually a DBA. The statement references a PACKAGE which sets and gets values in the namespace, but this package does not have to exist in order for the statement to succeed:
SQL> create context user_ctx using apc.ctx_pkg
2 /
Context created.
SQL>
Now let's create the package:
SQL> create or replace package ctx_pkg
2 as
3 procedure set_user_id(p_userid in varchar2);
4 function get_user_id return varchar2;
5 procedure clear_user_id;
6 end ctx_pkg;
7 /
Package created.
SQL>
There are three methods, to set, get and unset a value in the namespace. Note that we can use one namespace to hold different valiables. I am just using this package to set one variable (USER_ID) in the USER_CTX namespace.
SQL> create or replace package body ctx_pkg
2 as
3 procedure set_user_id(p_userid in varchar2)
4 is
5 begin
6 DBMS_SESSION.SET_CONTEXT(
7 namespace => 'USER_CTX',
8 attribute => 'USER_ID',
9 value => p_userid);
10 end set_user_id;
11
12 function get_user_id return varchar2
13 is
14 begin
15 return sys_context('USER_CTX', 'USER_ID');
16 end get_user_id;
17
18 procedure clear_user_id
19 is
20 begin
21 DBMS_SESSION.CLEAR_CONTEXT(
22 namespace => 'USER_CTX',
23 attribute => 'USER_ID');
24 end clear_user_id;
25
26 end ctx_pkg;
27 /
Package body created.
SQL>
So, how does this solve anything? Here is a table for the temporary storage of data. I'm going to add a column which will hold a token to identify the user. When we populate the table the value for this column will be provided by CTX_PKG.GET_USER_ID():
SQL> create table temp_23 as select * from big_table
2 where 1=0
3 /
Table created.
SQL> alter table temp_23 add (user_id varchar2(30))
2 /
Table altered.
SQL> create unique index t23_pk on temp_23(user_id, id)
2 /
Index created.
SQL>
... and over that table I create a view:...
create or replace view v_23 as
select
id
, col1
, col2
, col3
, col4
from temp_23
where user_id = ctx_pkg.get_user_id
/
Now, when I want to store some data in the table I need to set the context with a value with uniquely identifies my user.
SQL> exec ctx_pkg.set_user_id('APC')
PL/SQL procedure successfully completed.
SQL>
This statement populates the temporary table with twenty random rows:
SQL> insert into temp_23
2 select * from
3 ( select b.*, ctx_pkg.get_user_id
4 from big_table b
5 order by dbms_random.random )
6 where rownum <= 20
7 /
20 rows created.
SQL>
I can retrieve those rows by querying the view. But when I change my USER_ID and run the same query I cannot see them any more:
SQL> select * from v_23
2 /
ID COL1 COL2 COL3 COL4
---------- ---------- ------------------------------ --------- ----------
277834 1880 GV_$MAP_EXT_ELEMENT 15-OCT-07 4081
304540 36227 /375c3e3_TCPChannelReaper 15-OCT-07 36
1111897 17944 /8334094a_CGCast 15-OCT-07 17
1364675 42323 java/security/PublicKey 15-OCT-07 42
1555115 3379 ALL_TYPE_VERSIONS 15-OCT-07 3
2073178 3355 ALL_TYPE_METHODS 15-OCT-07 3
2286361 68816 NV 15-OCT-07 68
2513770 59414 /5c3965c8_DicomUidDoc 15-OCT-07 59
2560277 66973 MGMT_MNTR_CA 15-OCT-07 66
2700309 45890 /6cc68a64_TrustManagerSSLSocke 15-OCT-07 45
2749978 1852 V_$SQLSTATS 15-OCT-07 6395
2829080 24832 /6bcb6225_TypesTypePair 15-OCT-07 24
3205157 55063 SYS_NTsxSe84BlRX2HiXujasKy/w== 15-OCT-07 55
3236186 23830 /de0b4d45_BaseExecutableMember 15-OCT-07 23
3276764 31296 /a729f2c6_SunJCE_n 15-OCT-07 31
3447961 60129 HHGROUP 15-OCT-07 60
3517106 38204 java/awt/im/spi/InputMethod 15-OCT-07 38
3723931 30332 /32a30e8e_EventRequestManagerI 15-OCT-07 30
3877332 53700 EXF$XPVARCLST 15-OCT-07 53
4630976 21193 oracle/net/nl/NetStrings 15-OCT-07 21
20 rows selected.
SQL> exec ctx_pkg.set_user_id('FOX_IN_SOCKS')
PL/SQL procedure successfully completed.
SQL> select * from v_23
2 /
no rows selected
SQL>
So, the challenges are:
to establish a token which you can use automatically to uniquely identify a user
to find a hook in your connecting code which can set the context each time the user gets a session
just as importantly, to find a hook in your dis-connecting code which can unset the context each time the user leaves a session
Also, remember to clear out the table once the user has finished with it.
when I first read this I thought 'global temporary table' (gtt) and realized that would not help you in the slightest! This is because the data in a GTT is visible only in a session, and with a stateless web app, probably using connection pooling, there is no guaranteed relationship between application user and database session (one user might be handed different sessions on successive connections, one session will be handed to several different users). Now a temp table should do the trick.
It seems that on each iterative hit, the person (via silverlight) is polling the same data (and a large amount to boot).
I do believe that a temp table would suffice. Here is an asktom that shows how to do this in a web environment. Keep in mind the moment the data is stored it is aging and possibly stale and there will need to be a cleanup job.
http://asktom.oracle.com/pls/asktom/f?p=100:11:0::::P11_QUESTION_ID:76812348057
Now to tie it back to the user, not 100% sure how to do this in Silverlight (assuming via asp.net?) is the user authenticated prior to proceding? if so, you ought to be able to take their credentials and utilize that as the source to query against (utilize their user name and/or SID as their primary key and foreign key it against the data table as described in the asktom link).
http://www.codeproject.com/KB/silverlight/SL3WindowsIdentityName.aspx
this links appears to show how to get the current silverlight user in a window's authenticated scheme.
hth

How to generate alphanumeric id in Oracle

In my vb application I want an autogenerated id of alphanumeric characters, like prd100. How can I increment it using Oracle as backend?
Any particular reason it needs to be alphanumeric? If it can just be a number, you can use an Oracle sequence.
But if you want just a random string, you could use the dbms_random function.
select dbms_random.string('U', 20) str from dual;
So you could probably combine these 2 ideas (in the code below, the sequence is called oid_seq):
SELECT dbms_random.string('U', 20) || '_' || to_char(oid_seq.nextval) FROM dual
There are two parts to your question. The first is how to create an alphanumeric key. The second is how to get the generated value.
So the first step is to determine the source of the alpha and the numeric components. In the following example I use the USER function and an Oracle sequence, but you will have your own rules. I put the code to assemble the key in a trigger which is called whenever a row is inserted.
SQL> create table t1 (pk_col varchar2(10) not null, create_date date)
2 /
Table created.
SQL> create or replace trigger t1_bir before insert on t1 for each row
2 declare
3 n pls_integer;
4 begin
5 select my_seq.nextval
6 into n
7 from dual;
8 :new.pk_col := user||trim(to_char(n));
9 end;
10 /
Trigger created.
SQL>
The second step requires using the RETURNING INTO clause to retrieve the generated key. I am using SQL*PLus for this example. I confess to having no idea how to wire this syntax into VB. Sorry.
SQL> var new_pk varchar2(10)
SQL> insert into t1 (create_date)
2 values (sysdate)
3 returning pk_col into :new_pk
4 /
1 row created.
SQL> print new_pk
NEW_PK
--------------------------------
APC61
SQL>
Finally, a word of warning.
Alphanumeric keys are a suspicious construct. They reek of "smart keys" which are, in fact, dumb. A smart key is a value which contains multiple parts. At somepoint you will find yourself wanting to retrieving all rows where the key starts with 'PRD', which means using SUBSTR() or LIKE. Even worse someday the definition of the smart key will change and you will have to cascade a complicated update to your table and its referencing foreign keys. A better ides is to use a surrogate key (number) and have the alphanumeric "key" defined as separate columns with a UNIQUE composite constraint to enforce the business rule.
SQL> create table t1 (id number not null
2 , alpha_bit varchar2(3) not null
3 , numeric_bit number not null
4 , create_date date
5 , constraint t1_pk primary key (id)
6 , constraint t1_uk unique (alpha_bit, numeric_bit)
7 )
8 /
Table created.
SQL>

Resources