I created a table in sqlplus using
create table employee(id number(1) primary key, name varchar(5));
then I wanted to enter data into table using loop so I implemented
SQL> begin
2 for i in 1..4
3 loop
4 insert into employee
5 values(i, '&name');
6 end loop;
7 end;
8 /
It showed me a prompt to enter name once
Enter value for name: a
Instead of running for 4 times and asking to input name everytime it just ran once.
After checking the output, I found my table as
SQL> select * from employee;
ID NAME
---------- ----
1 a
2 a
3 a
4 a
I want to enter different names for every employee id, not the same name for all the customer.
Related
I have a table with two columns: k (primary key) and value.
I'd like to:
select for update by k, if k is not found, insert a new row with a default value.
with the returned value ( existent or new inserted row value) make some processing.
update the row and commit.
Is it possible to make this "select for update and insert default value if not found"?
If implement (1) as a select/check if found/insert if not found, we have concurrency problems, since two sessions could make the select concurrently on non existent key, both will try to insert and one of the instances will fail.
In this case the desired behavior is to perform atomically the select/insert and one of the instance perform it and the second one keep locked until the first one commits, and then use the value inserted by the first one.
We implement it always doing an "insert ... if not exist.../commit" before the "select for update" but this implies always trying to insert when it is a unlikely needed.
Is there any way to implement it on one sql sentence?
Thanks!!
See if k is available
SELECT * FROM table
WHERE k = value
FOR UPDATE
If no rows returned, then it doesn't exist. Insert it:
INSERT INTO table(k, col1, col2)
VALUES (value, val1, default))
select ... for update is the first step you should make; without it, you can't "reserve" that row for further processing (unless you're willing to lock the whole table in exclusive mode; if that "processing" takes no time, that could also be an option, especially if there are not many users who will be doing it).
If row exists, the rest is simple - process it, update it, commit.
But, if it doesn't exist, you'll have to insert a new row (just as you said), and here's a problem of two (or more) users inserting the same value.
To avoid it, create a function which
will return unique ID value for a new row
is an autonomous transaction
why? Because you're performing DML in it (update or insert), and you can't do that in a function unless it is an autonomous transaction
Users will have to use that function to get the next ID value. Here's an example: you'll need a table (my_id) which holds the last used ID (and every user who accesses it via the function will create a new value).
Table:
SQL> create table my_id (id number);
Table created.
Function:
SQL> create or replace function f_id
2 return number
3 is
4 pragma autonomous_transaction;
5 l_nextval number;
6 begin
7 select id + 1
8 into l_nextval
9 from my_id
10 for update of id;
11
12 update my_id set
13 id = l_nextval;
14
15 commit;
16 return (l_nextval);
17
18 exception
19 when no_data_found then
20 lock table my_id in exclusive mode;
21
22 insert into my_id (id)
23 values (1);
24
25 commit;
26 return(1);
27 end;
28 /
Function created.
Use it as
SQL> select f_id from dual;
F_ID
----------
1
SQL>
That's it ... code you'll use will then be something like this:
SQL> create table test
2 (id number constraint pk_test primary key,
3 name varchar2(10),
4 datum date
5 );
Table created.
SQL> create or replace procedure p_test (par_id in number)
2 is
3 l_id test.id%type;
4 begin
5 select id
6 into l_id
7 from test
8 where id = par_id
9 for update;
10
11 update test set datum = sysdate where id = par_id;
12 exception
13 when no_data_found then
14 insert into test (id, name, datum)
15 values (f_id, 'Little', sysdate); --> function call is here
16 end;
17 /
Procedure created.
SQL> exec p_test (1);
PL/SQL procedure successfully completed.
SQL> select * from test;
ID NAME DATUM
---------- ---------- -------------------
1 Little 04.09.2021 20:49:21
SQL> exec p_test (1);
PL/SQL procedure successfully completed.
SQL> select * from test;
ID NAME DATUM
---------- ---------- -------------------
1 Little 04.09.2021 20:49:21 --> row was inserted
SQL> exec p_test (1);
PL/SQL procedure successfully completed.
SQL> select * from test;
ID NAME DATUM
---------- ---------- -------------------
1 Little 04.09.2021 20:49:30 --> row was updated
SQL>
Use a sequence to generate a surrogate primary key instead of using a natural key. If you had a real natural key, then it would be extremely unlikely for two users to submit the same value at the same time.
There are several ways to automatically generate primary keys. I prefer to use sequence defaults, like this:
create sequence test_seq;
create table test1
(
k number default test_seq.nextval,
value varchar2(4000),
constraint test1_pk primary key(k)
);
If you can't switch to a surrogate key or a real natural key:
Change the "insert ... if not exist.../commit" to a simpler "insert ... if not exist", and perform all operations in a single transaction. Inserting the same primary key in different sessions, even uncommitted, is impossible in Oracle. Although the SELECT won't cause a block, the INSERT will. This behavior is an exception to Oracle's implementation of isolation, the "I" in "ACID", and in this case that behavior can work to your advantage.
If two sessions attempt to insert the same primary key at the same time, the second session will hang, and when the first session finally commits, the second session will fail with the exception "ORA-00001: unique constraint (X.Y) violated". Let that exception become your flag for knowing when a user has submitted a duplicate value. You can catch the exception in the application and have the user try again.
Closed. This question needs to be more focused. It is not currently accepting answers.
Want to improve this question? Update the question so it focuses on one problem only by editing this post.
Closed 1 year ago.
Improve this question
I Have Two Tables
first :
CREATE TABLE z_names (ID number,
NAME VARCHAR2(200))
Second:
CREATE TABLE Z_FNAME
("FAMILY" VARCHAR2(200 BYTE),
"ID" NUMBER,
"NAME" VARCHAR2(200 BYTE),
"NAME_ID" NUMBER
)
How Write Procedure for Insert Into Second Table By String ,With This Conditions :
1- Example Of input String : nam1;fam1,nam2;fam2 => nam1 Insert Into NAME Column And fam1 Into FAMILY
2-ID Generated By Trigger I Have Writed Before
3-NAME_ID Comes From FIRST table
With a table you previously created (I remember that question so I reused it, as well as the sequence), you'd split input string into rows and fetch names, somehow; I chose regular expressions, presuming that names consist of only one word.
Table with names (inserted previously):
SQL> select * From z_names;
ID NAME
---------- --------------------
1 john
2 jim
3 jack
Procedure expects that strings you're passing as parameters contain name which is already inserted into the z_names table.
SQL> create or replace procedure p_test (par_string in varchar2) is
2 begin
3 insert into z_fname (family, id, name, name_id)
4 with temp as
5 (select regexp_substr(par_string, '[^,]+', 1, level) nf
6 from dual
7 connect by level <= regexp_count(par_string, ',') + 1
8 )
9 select regexp_substr(t.nf, '\w+', 1, 2) family,
10 z_names_seq.nextval id,
11 regexp_substr(t.nf, '\w+', 1, 1) name,
12 n.id
13 from temp t join z_names n on n.name = regexp_substr(t.nf, '\w+', 1, 1);
14 end;
15 /
Procedure created.
Testing:
SQL> exec p_test('john;Little,jack;Foot,jim;Bigfoot');
PL/SQL procedure successfully completed.
SQL> select * from z_fname;
FAMILY ID NAME NAME_ID
---------- ---------- -------------------- ----------
Little 10 john 1
Bigfoot 11 jim 2
Foot 12 jack 3
SQL>
However, from my point of view, that's somewhat awkward approach. I don't know what tables you use in this exercise represent, but I'd expect them to be related to each other (via referential integrity constraint). It also means that z_fname most probably isn't normalized - you would store only the foreign key constraint value which points to the master table (z_names), not store both name AND id.
It's just a template and you should scheck it and modify as you want (that's all what i can do with all data than you give me):
CREATE OR REPLACE PROCEDURE some_name
(name_in IN varchar2, fam_in IN varchar2)
IS
max_id_names number; --variable for max id from z_names and new name
max_id_fam number; --variable for max id from Z_FNAME
BEGIN
--1. Find max id from "ID Generated By Trigger I Have Writed Before" and SET IT TO max_id_names
INSERT INTO z_names
(
max_id_names,
NAME
)
VALUES
(
:max_id_names, --you will find it
name_in -- IN param
);
--2. Find max id from "ID Generated By Trigger I Have Writed Before" and SET IT TO max_id_fam
INSERT INTO Z_FNAME
(
FAMILY,
ID,
NAME,
NAME_ID
)
VALUES
(
fam_in, -- IN param
:max_id_fam, -- you will find it
name_in, --IN param
:max_id_names --you already have it because you inserted the names
);
END;
/
I have a table:
id
name
amount
If the same name was inserted, then amount should be incremented.
Else, insert with 0 amount.
How to create a trigger with a given condition?
You shouldn't really be doing that. AMOUNT column effectively counts number of NAME appearances in that table. You can always count it, can't you? So, what's the purpose of doing that?
If you want to know which name was inserted prior (or after) some other name (equal to the previous one), sort them by ID (if it is incremental, such as an identity column or if it gets its value from a sequence).
If ID isn't incremental, add DATE_INSERTED column (and sort by it; or apply ROW_NUMBER analytic function which orders values by DATE_INSERTED).
Also, what happens if you delete one of those duplicate names? Will you retroactively decrement AMOUNT column for all previous instances of that NAME?
But, if you insist, here's one option. As you can't just select from a table you're inserting into (because of the mutating table error), I'm using a compound trigger. The part of "insert amount 0" is done by setting the column's default value (you don't need any code for that).
SQL> create table test
2 (id number primary key,
3 name varchar2(20),
4 amount number default 0);
Table created.
SQL> create or replace trigger trg_ai_test
2 for insert on test
3 compound trigger
4
5 type test_rt is record (id test.id%type,
6 name test.name%type);
7 type rli_t is table of test_rt index by pls_integer;
8 g_rli rli_t;
9
10 after each row is
11 begin
12 g_rli(g_rli.count + 1).id := :new.id;
13 g_rli(g_rli.count).name := :new.name;
14 end after each row;
15
16 after statement is
17 l_cnt number;
18 begin
19 for i in 1 .. g_rli.count loop
20 dbms_output.put_Line('x');
21 dbms_output.put_line(i ||' '|| g_rli(i).id ||' '||g_rli(i).name);
22 select count(*) into l_cnt
23 from test
24 where name = g_rli(i).name;
25
26 update test set
27 amount = l_cnt
28 where id = g_rli(i).id;
29 end loop;
30
31 end after statement;
32 end trg_ai_test;
33 /
Trigger created.
SQL>
Testing:
SQL> insert into test (id, name) values (1, 'little');
1 row created.
SQL> insert into test (id, name) values (2, 'foot');
1 row created.
SQL> insert into test (id, name) values (3, 'little');
1 row created.
SQL> insert into test (id, name) values (9, 'little');
1 row created.
SQL> select * from test;
ID NAME AMOUNT
---------- -------------------- ----------
1 little 1
2 foot 1
3 little 2
9 little 3
SQL>
Or, a simpler solution (described above), without using that much code:
SQL> select id, name,
2 row_number() over (partition by name order by id) as amount
3 from test;
ID NAME AMOUNT
---------- -------------------- ----------
2 foot 1
1 little 1
3 little 2
9 little 3
SQL>
I have an Oracle table with 4 columns (Name, Phone, Email, Count).
If the user is updating value of Name column, then I need to increment Count column value by 1.
If the user is updating values other than Name column, then I don't need to increment Count column value by 1.
Initially, when the record is inserted, Count should be 0. And every time when Name column is updated, the Count should be incremented by 1 (like 1, 2, 3 .....).
How we can achieve this? I am very new to databases.
Thanks a lot for your help.
You can do that in an update and insert trigger or in your program. The later however requires all possible programs to cooperate. The former (triggers) is black art.
A program can do it like this:
UPDATE Person SET Count=Count+1, Phone='123' WHERE name=`csr` and Phone <> '123';
This will update one or no record (i.e. if phone was already 123 it will do nothing).
BTW: there is no nice solution to insert it if it was missing.
Hmm, you changed your question, updating the Name is problematic if you do not have another primary key, is that really what you want?
If you are looking at plsql procedure, then you can use this method,
I am using 3 input variables,
1.) column name to edit
2.) old value
3.) New value to be updated
SQL> create or replace procedure updateval (colname varchar2,oldval varchar2,newval varchar2) is
2 l_prop varchar2(10);
3 l_newval varchar2(10);
4 l_old_val varchar2(10);
5 begin
6 l_prop:=colname;
7 l_newval:=newval;
8 l_old_val:=oldval;
9 IF (upper(l_prop)='NAME') THEN
10 update TESTING123 set name=l_newval,count=count+1 where name=l_old_val;
11 elsif (upper(l_prop)='PHONE') THEN
12 update TESTING123 set PHONE=l_newval ,count=count+1where PHONE=l_old_val;
13 elsif (upper(l_prop)='EMAIL') THEN
14 update TESTING123 set EMAIL=l_newval ,count=count+1 where EMAIL=l_old_val;
15 END IF;
16 end;
17 /
Procedure created.
SQL>
SQL> select * from testing123;
NAME PHONE EMAIL COUNT
---------- ---------- ---------- ----------
abc1 12345 ABC#a.COM 1
xyz 3435 xyz#a.COM 0
SQL> exec updateval ('NAME','abc1','newabc1');
PL/SQL procedure successfully completed.
SQL>
SQL>
SQL> select * from testing123;
NAME PHONE EMAIL COUNT
---------- ---------- ---------- ----------
newabc1 12345 ABC#a.COM 2
xyz 3435 xyz#a.COM 0
How do I return the value of an identity column (id) in Oracle 12c after insertion? Seems like most of the approaches out there uses sequence to get back the id of the inserted item.
Simply use the RETURNING clause.
For example -
RETURNING identity_id INTO variable_id;
Test case -
SQL> set serveroutput on
SQL> CREATE TABLE t
2 (ID NUMBER GENERATED ALWAYS AS IDENTITY, text VARCHAR2(50)
3 );
Table created.
SQL>
SQL> DECLARE
2 var_id NUMBER;
3 BEGIN
4 INSERT INTO t
5 (text
6 ) VALUES
7 ('test'
8 ) RETURNING ID INTO var_id;
9 DBMS_OUTPUT.PUT_LINE('ID returned is = '||var_id);
10 END;
11 /
ID returned is = 1
PL/SQL procedure successfully completed.
SQL>
SQL> select * from t;
ID TEXT
---------- --------------------------------------------
1 test
SQL>