I am in bit confusion regarding update lock, in the below program
CREATE OR REPLACE
PROCEDURE pro_cedure(
p_dept_id NUMBER )
IS
CURSOR mycursor
IS
SELECT deptno,comm,extra
FROM emp
WHERE comm IS NULL
AND extra IS NOT NULL
AND deptno = p_dept_id
FOR UPDATE OF comm;
BEGIN
FOR emp_rec IN mycursor
LOOP
UPDATE emp SET comm = extra
WHERE CURRENT OF mycursor;
INSERT INTO changed
(
deptno,
oldval,
newval,
seq_nextval
)
VALUES
(
emp_rec.deptno,
emp_rec.comm,
emp_rec.extra,
sequence_name.nextval
);
END LOOP;
END;
if some other user try to update the same table or the same row while I am updating does it causes a lock with this program?
If you run this procedure 2 times parallel, the second wont execute the select until the first commits. (Of course if you run other statements with the same for update of comm clause they will be queued the same way.)
Related
I am trying to write a code block where record insert if record already exist then update
table. i am trying If (sql%rowcount = 0) then but it's not working in cursor and more one records.
What I tried so far as the code block is
declare
remp_id varchar2(60);
remp_name varchar2(100);
rdesig varchar2(100);
rdept_no number;
rdesig_no number;
rdept_name varchar2(60);
cursor alfa is
select emp_code, emp_name, desig, dept_name, dept_no, desig_no
from emp
where emp_code between :first_code and :second_code;
begin
open alfa;
loop
fetch alfa
into remp_id, remp_name, rdesig, rdept_name, rdept_no, rdesig_no;
exit when alfa%notfound;
update att_reg_mo
set emp_code = remp_id,
emp_name = remp_name,
desig = rdesig,
dept_name = rdept_name,
dept_no = rdept_no,
desig_no = rdesig_no,
att_date = :att_date,
emp_att = :emp_att,
att_type = 'MA',
reg_date = :reg_date
where emp_code between :first_code and :second_code
and reg_date = :reg_date
and att_date = :att_date;
commit;
if (sql%rowcount = 0) then
insert into att_reg_mo
(emp_code,
emp_name,
desig,
dept_name,
att_date,
emp_att,
att_type,
reg_date,
dept_no,
desig_no)
values
(remp_id,
remp_name,
rdesig,
rdept_name,
:att_date,
:emp_att,
'MA',
:reg_date,
rdept_no,
rdesig_no);
end if;
commit;
end loop;
close alfa;
end;
when i am fire the trigger then record is insert but where need to update record it's update with null values
Or you could use something like that:
DECLARE
cursor test is
select 1 as v from dual
union
select 2 as v from dual;
n_var NUMBER;
BEGIN
for rec in test loop
BEGIN
select 1 into n_var from dual where rec.v=2;
DBMS_OUTPUT.PUT_LINE('Please Update Any Table');
EXCEPTION
WHEN no_data_found THEN
DBMS_OUTPUT.PUT_LINE('Please Insert Any Table');
END;
end loop;
EXCEPTION
WHEN OTHERS THEN
DBMS_OUTPUT.PUT_LINE ('Unexpected error');
END;
SQL%attribute always refers to the most recently run SELECT or DML statement. It refreshes as to start from zero after any of transaction statement such as COMMIT, ROLLBACK or SAVEPOINT is issued, for a session in which AUTOCOMMIT is presumed to be OFF by default. Therefore, you always get zero from SQL%ROWCOUNT which is just before the INSERT statement, and keeping inserting to the concerned table during every run of code block.
So, remove the f i r s t COMMIT, removing also keeps the atomicity of the whole transaction, from your code block.
Demo
I am writing procedure to insert data to one table by fetching data from another table. While inserting the data an error occurs (SQL Statement ignored) and (in this place, this column is forbidden). Can you suggest what I am doing wrong
SET SERVEROUTPUT ON;
CREATE OR REPLACE PROCEDURE KOL2
IS
CURSOR CUR IS SELECT empno, ename, SUM(SAL) sal FROM EMP where job =
'MANAGER';
nr_kier NUMBER; nazwisko_kier varchar(20); suma_pensji int;
ostatnia_mod date;
BEGIN
OPEN CUR;
LOOP
FETCH CUR INTO nr_kier, nazwisko_kier, suma_pensji;
EXIT WHEN CUR%NOTFOUND;
INSERT INTO statystyka_kierownikow (nr_kier , naziwsko_kier,
suma_pensji, ostatnia_mod)
VALUES (nr_kier, nazwisko_kier, sal, sysdate);
END LOOP;
CLOSE CUR;
COMMIT;
END;
what you are trying to do is time consuming task. It is not even suitable for SQL. You are trying to fetch every record and make insertion one-by-one.
Check my solution below.
INSERT INTO statystyka_kierownikow
(nr_kier , naziwsko_kier, suma_pensji, ostatnia_mod)
SELECT empno, ename, SUM(SAL) sal
FROM EMP where job = 'MANAGER'
GROUP BY empno, ename;
The example below works fine and it return some rows. But I need summary of the rows.
DECLARE
x number;
Cursor c1 is
select sal,deptno from emp;
rw c1%rowtype;
BEGIN
x:=0;
open c1;
LOOP
fetch c1 into rw;
FOR i IN 1..rw.deptno LOOP
x:=x+rw.sal;
end loop;
exit when c1%notfound;
DBMS_OUTPUT.PUT_LINE(x);
END LOOP;
close c1;
END;
/
Suppose you have three employees and every employee's has different salary. The salary has due for 10 months and 20 months and 30 months. The salary is due for long time. So you want to add 2% bonus amount with salary for every month as the way:
The below description is for single Employee for 10 months:
Month-1 Salary = 800 => 800*2% = 16.00 => Total = 800+16 =816
Month-2 Salary = 816 => 816*2% = 16.32 => Total = 816+16.32 =832.32
............................................................................
Month-10 Salary = 956.07 => 956.07*% = 19.12 => Total = 956.07+19.12 =975.20
The Months-1 Total Salary=816. So Month-2 Salary=816. This will continue up 10 months.Every Employee has the same condition. So I need summary of the total column. Thanks and best regards.
When you use aggregate function SUM in your query (unlike, when you adding yourself), you don't need to convert NULL. SUM takes care of it. Although, as #DavidAldridge pointed, if you expect that all rows in summarized group of records may contain NULL, your sum will also be NULL. If you want to return a value, you can wrap your sum as follows coalesce(sum(sal),0)
This will give you SUM of all salaries
select SUM(sal) TotalSal from emp;
This will give you SUM by department
select SUM(sal) TotalDeptSal, deptno
from emp
group by deptno;
In you question you posted that you need to execute it in stored procedure while your code as an anonymous block. If you want to return single value from Stored procedure you have a choice to declare function with return parameter or stored procedure with output parameter. To return a recordset from stored procedure in Oracle you need to declare a refcursor output parameter
CREATE OR REPLACE PROCEDURE Get_TotalSal_ByDept (
p_recordset OUT SYS_REFCURSOR) AS
BEGIN
OPEN p_recordset FOR
select SUM(sal) TotalDeptSal, deptno
from emp
group by deptno;
END;
Edit
I see that you added row - total. It is not changing much from the original question. Still, using cursor is not needed. You can run 2 queries and return 2 output parameters, one with data by department and another is total.
CREATE OR REPLACE PROCEDURE Get_SalByDept_WithTotal (
p_total OUT NUMBER,
p_recordset OUT SYS_REFCURSOR) AS
BEGIN
select SUM(sal) INTO p_total from emp;
OPEN p_recordset FOR
select SUM(sal) TotalDeptSal, deptno
from emp
group by deptno;
END;
Is this what you looking for? The Running totals?
SELECT totals.deptNo, totals.depttotal, SUM(totals.depttotal) OVER (ORDER BY totals.id)
FROM (
select deptNo, deptTotal, rownum id
from (
select deptNo, sum(sal * deptNo) deptTotal
from emp
group by deptNo)
) totals
ORDER BY totals.id;
If you have some sort of department Id you can use that instead of artificially generated one from ROWNUM
I need to create a table if it does not exist, and when it is created add a single row to it.
I'm new to oracle and PL/SQL so I basically need an equivalent of the following T-SQL:
IF OBJECT_ID('my_table', 'U') IS NULL
BEGIN
CREATE TABLE my_table(id numeric(38,0), date datetime)
INSERT INTO my_table
VALUES (NULL, 0)
END
if you want to check table creation
DECLARE count NUMBER;
BEGIN
count := 0;
SELECT COUNT(1) INTO count from user_tables WHERE table_name= 'MY_TABLE';
IF COL_COUNT = 0 THEN
EXECUTE IMMEDIATE 'create table ....';
END IF;
END;
/
A checking for DML .please note you have to sepcify your pk columns and values.
DECLARE count NUMBER;
BEGIN
count := 0;
SELECT COUNT(1) INTO count from MY_TABLE WHERE id= 0 and name='Something';
IF COL_COUNT = 0 THEN
EXECUTE IMMEDIATE 'insert into MY_TABLE (id,name) values(0,''something'') ';
END IF;
END;
/
also note I recomand to specify columns when you insert into a table
Another approach is to use exception logic. I changed field names and types according to Oracle rules
declare
eAlreadyExists exception;
pragma exception_init(eAlreadyExists, -00955);
begin
execute immediate 'CREATE TABLE my_table(id number, dateof date)';
execute immediate 'INSERT INTO my_table VALUES (NULL, sysdate)';
exception when eAlreadyExists then
null;
end;
but may be it is not a good idea to create tables dynamically
In my opinion, you should not be creating objects on the fly. You should think about your design before implementing it.
Anyway, if you really want to do it this way, then you need to do it programmatically in PL/SQL (ab)using EXECUTE IMMEDIATE.
However, I would prefer the CTAS i.e. create table as select if you want to create a table ta once with a single row. For example,
SQL> CREATE TABLE t AS SELECT 1 id, SYSDATE dt FROM DUAL;
Table created.
SQL> SELECT * FROM t;
ID DT
---------- ---------
1 29-MAY-15
SQL>
The table is created permanently.
If you are looking for a temporary table, which you could use to store session specific data , then look at creating Global temporary table.
From documentation,
Use the CREATE GLOBAL TEMPORARY TABLE statement to create a temporary
table. The ON COMMIT clause indicates if the data in the table is
transaction-specific (the default) or session-specific
You can use NOT EXISTS with select statement:
IF NOT EXISTS(SELECT 1 FROM my_table) THEN
CREATE TABLE my_table(id NUMBER, date date);
COMMIT;
INSERT INTO my_table(id, date) values (NULL, O);
COMMIT;
END IF;
UPDATE
According to the comment, I cannot use Exist directly in PL/SQL. So this is another way to do it:
begin
select case
when exists(select 1
from my_table)
then 1
else 0
end into l_exists
from dual;
if (l_exists = 1)
then
-- anything
else
EXECUTE IMMEDIATE 'CREATE TABLE my_table(id NUMBER, date date)';
EXECUTE IMMEDIATE 'INSERT INTO my_table(id, date) values (NULL, O)';
end if;
end;
i'm having problem updating and insert into below column. Please advise on this.
This is the input
depnto extra comm
----------------------------
20 300 NULL
20 300 400
20 NULL NULL
20 500 NULL
This is the expected output
depnto Extra comm
---------------------
20 300 300
20 300 400
20 NULL NULL
20 500 500
I need to update comm column with extra column on below conditions.
If comm Is null then extra value is updated to comm.
If comm Is not null, no need to update,
If both are null, leave as null,
if comm column has a value no need to overwrite.
My program is below. Even I need to keep track which are rows are updated and to which value in another table.
PROCEDURE (dept_id )
AS
BEGIN
FOR r IN (SELECT *
FROM emp
WHERE comm IS NULL AND extra IS NOT NULL AND deptno = dept_id)
LOOP
UPDATE emp
SET comm = extra
WHERE comm IS NULL AND extra IS NOT NULL AND deptno = dept_id;
INSERT INTO changed_comm (deptno, oldval, newval)
VALUES (dept_id, r.comm, r.extra);
END LOOP;
EXCEPTION
WHEN NO_DATA_FOUND
THEN
NULL;
END;
please provide some opinion on above. Its not inserting correctly.
You do not need FOR LOOP, just a single UPDATE does the work:
UPDATE emp
SET comm = extra
WHERE comm IS NULL AND extra IS NOT NULL;
Here is a demo: http://www.sqlfiddle.com/#!4/aacc3/1
--- EDIT ----
I didn't notice, that in the expected output deptno 10 was updated to 20, to update deptno an another query is needed:
UPDATE emp
SET deptno = 20
WHERE deptno = 10;
---- EDIT -----
If you want to insert changed values to the other table, try a procedure with RETURNING..BULK COLLECT and FORALL:
CREATE OR REPLACE PROCEDURE pro_cedure( p_dept_id number )
IS
TYPE changed_table_type IS TABLE OF changed%ROWTYPE;
changed_buff changed_table_type;
BEGIN
SELECT deptno, comm, extra BULK COLLECT INTO changed_buff
FROM emp
WHERE comm IS NULL AND extra IS NOT NULL AND deptno = p_dept_id
FOR UPDATE;
UPDATE emp
SET comm = extra
WHERE comm IS NULL AND extra IS NOT NULL AND deptno = p_dept_id;
FORALL i IN 1 .. changed_buff.count
INSERT INTO changed VALUES changed_buff( i );
END;
/
The procedure should work if you are not going to process huge number of records in a one call (more than 1000 ... or maximum a few thousands). If one dept_id can contain ten thousands and more rows, then this procedure might be slow, becasue it will consume a huge amount of PGA memory. In such a case, an another approach with bulk collectiong in chunks is required.
-- EDIT --- how to store sequence values -------
I assume that the table changed has 4 columns, like this:
CREATE TABLE "TEST"."CHANGED"
( "DEPTNO" NUMBER,
"OLDVAL" NUMBER,
"NEWVAL" NUMBER,
"SEQ_NEXTVAL" NUMBER
) ;
and we will store sequence values in the seq_nextval column.
In such a case the procedure might look like this:
create or replace
PROCEDURE pro_cedure( p_dept_id number )
IS
TYPE changed_table_type IS TABLE OF changed%ROWTYPE;
changed_buff changed_table_type;
BEGIN
SELECT deptno, comm, extra, sequence_name.nextval
BULK COLLECT INTO changed_buff
FROM emp
WHERE comm IS NULL AND extra IS NOT NULL AND deptno = p_dept_id
FOR UPDATE;
UPDATE emp
SET comm = extra
WHERE comm IS NULL AND extra IS NOT NULL AND deptno = p_dept_id;
FORALL i IN 1 .. changed_buff.count
INSERT INTO changed VALUES changed_buff( i );
END;
--- EDIT --- version with cursor for small sets of data -----
Yes, for small sets of data bulk collecting doesn't give significant increase of the speed, and plain cursor with for..loop is sufficient in such a case.
Below is an example how tu use the cursor together with update, notice the FOR UPDATE clause, it is required when we plan to update a record fetched from the cursor using WHERE CURRENT OF clause.
This time a sequence value is evaluated within the INSERT statement.
create or replace
PROCEDURE pro_cedure( p_dept_id number )
IS
CURSOR mycursor IS
SELECT deptno, comm, extra
FROM emp
WHERE comm IS NULL AND extra IS NOT NULL
AND deptno = p_dept_id
FOR UPDATE;
BEGIN
FOR emp_rec IN mycursor
LOOP
UPDATE emp
SET comm = extra
WHERE CURRENT OF mycursor;
INSERT INTO changed( deptno, oldval, newval, seq_nextval)
VALUES( emp_rec.deptno, emp_rec.comm,
emp_rec.extra, sequence_name.nextval );
END LOOP;
END;
BEGIN
FOR person IN (SELECT A FROM EMP WHERE B IN (SELECT B FROM ustom.cfd_180518) )
LOOP
--dbms_output.put_line(person.A);
UPDATE custom.cfd_180518 SET c = person.a;
END LOOP;
END;