default value, oracle sp call - sql

I have an oralcle SP forced on me that will not accept an empty parameter in an update. So if I wanted to set a value back to the default of ('') it will not let me pass in the empty string. Is there a keyword you can use such as default, null, etc that oracle would interpret back to the default specified for a particular column?

Sometimes things are just as simple as you hope they might be.
First, a table with a default value ...
SQL> create table t23 (
2 id number not null primary key
3 , col_d date default sysdate not null )
4 /
Table created.
SQL> insert into t23 values (1, trunc(sysdate, 'yyyy'))
2 /
1 row created.
SQL> select * from t23
2 /
ID COL_D
---------- ---------
1 01-JAN-10
SQL>
Next a procedure which updates the default column ...
SQL> create or replace procedure set_t23_date
2 ( p_id in t23.id%type
3 , p_date in t23.col_d%type )
4 is
5 begin
6 update t23
7 set col_d = p_date
8 where id = p_id;
9 end;
10 /
Procedure created.
SQL>
... but which doesn't work as we would like:
SQL> exec set_t23_date ( 1, null )
BEGIN set_t23_date ( 1, null ); END;
*
ERROR at line 1:
ORA-01407: cannot update ("APC"."T23"."COL_D") to NULL
ORA-06512: at "APC.SET_T23_DATE", line 6
ORA-06512: at line 1
SQL>
So, let's try adding a DEFAULT option ...
SQL> create or replace procedure set_t23_date
2 ( p_id in t23.id%type
3 , p_date in t23.col_d%type )
4 is
5 begin
6 if p_date is not null then
7 update t23
8 set col_d = p_date
9 where id = p_id;
10 else
11 update t23
12 set col_d = default
13 where id = p_id;
14 end if;
15 end;
16 /
Procedure created.
SQL>
... and lo!
SQL> exec set_t23_date ( 1, null )
PL/SQL procedure successfully completed.
SQL>
SQL> select * from t23
2 /
ID COL_D
---------- ---------
1 28-FEB-10
SQL>
I ran this example on an 11g database. I can't remember when Oracle introduced this exact support for DEFAULT, but it has been quite a while (9i???)
edit
The comments are really depressing. The entire point of building PL/SQL APIs is to make it easier for application developers to interact with the database. That includes being sensible enough to rewrite stored procedures when necessary. The big difference between building something out of software and, say, welding cast-iron girders together is that software is malleable and easy to change. Especially when the change doesn't alter the signature or behaviour of an existing procedure, which is the case here.

The procedure that's been forced on you:
create or replace procedure notEditable(varchar2 bar) as
begin
--update statement
null;
end;
How to use:
begin
notEditable(bar=>null);
end;
I didn't actually compile, but I believe this is the correct syntax.

Related

Automatically updating a field after a manuall update in Oracle SQL

I have a table that has a bunch of columns, one of which is a 'last_update_time' column. Whenever I change the value of any of the other columns, I would like 'last_update_time' to automatically update to SYSDATE.
I know that you would usually use triggers for this kind of thing, but as far as I am aware it is not possible to have a trigger for a table mutate that same table.
What is the best way of implementing the functionallity described above?
You can use a trigger before update of. You only need to specify the columns that might be updated in the clause before of
Take a look at this
SQL> create table testtrg ( c1 number, c2 date ) ;
Table created.
SQL> create or replace trigger mytrig
before update of c1
on testtrg
referencing new as new old as old
for each row
declare
begin
:new.c2 := sysdate;
end;
/ 2 3 4 5 6 7 8 9 10 11 12 13
Trigger created.
SQL> select * from testtrg;
no rows selected
SQL> insert into testtrg values ( 1 , sysdate - 360 );
1 row created.
SQL> commit;
Commit complete.
SQL> select * from testtrg ;
C1 C2
---------- ---------
1 28-JUL-19
SQL> update testtrg set c1=2 ;
1 row updated.
SQL> commit ;
Commit complete.
SQL> select * from testtrg ;
C1 C2
---------- ---------
2 22-JUL-20
SQL>
In my tables I like to keep track of when it was created in addition to when it was last modified. My triggers look something like this:
CREATE OR REPLACE TRIGGER tnsnames.tns_server_override_trg
BEFORE INSERT OR UPDATE
ON "TNSNAMES".tns_server_override
FOR EACH ROW
BEGIN
:new.modified_dt := SYSDATE;
:new.modified_by := SYS_CONTEXT( 'USERENV', 'OS_USER' );
CASE
WHEN INSERTING
THEN
:new.created_dt := :new.modified_dt;
:new.created_by := :new.modified_by;
WHEN UPDATING
THEN
:new.created_dt := :old.created_dt;
:new.created_by := :old.created_by;
END CASE;
END;

Stored procedure variable error in PLSQL when declaring variables

Using Oracle 11g when creating the following stored procedure
create or replace PROCEDURE sp_EqualVote(AREA IN NVARCHAR2, DATEOFVOTE IN DATE)
IS
DECLARE test nvarchar(255);
BEGIN
SELECT
AREA,
DATEOFVOTE,
CASE
WHEN (REMAINVOTES = LEAVEVOTES) THEN REMAINVOTES
END AS EqualVote
INTO test
FROM VOTING
WHERE REMAINVOTES = LEAVEVOTES;
END;
END;
I encounter the following error, I'm not quite sure where to go
PLS-00103: Encountered the symbol "DECLARE" when expecting one of the following: begin function pragma procedure subtype type <an identifier> <a double-quoted delimited-identifier> current cursor delete exists prior external language The symbol "begin" was substituted for "DECLARE" to continue.
I'm a university student and not really that familiar with PLSQL. The idea is the stored procedure should display if an an area has equal votes, given the area and date in the procedure then display an equalvotes labeled column with a value of 50
Quite a few mistakes.
you don't need DECLARE within the named PL/SQL procedure
parameters names should differ from column names, so you'd rather use - for example - p_area in nvarchar2, p_dateofvote in date
if you select 3 columns, you have to put them INTO 3 variables - you've declared only one, so either declare two more, or remove AREA and DATEOFOTE from SELECT
what are those parameters used for? Usually, as a part of the WHERE clause - which is not the case in your code
pay attention to number of rows returned by the SELECT statement. If you're selecting into a scalar variable, make sure that it returns only one row
what will you do with TEST variable, once you get its value? Currently, nothing
you've got an END that is a surplus.
Therefore, consider something like this which should at least compile (depending on table description):
SQL> create table voting (area nvarchar2(10),
2 dateofvote date,
3 remainvotes nvarchar2(10),
4 leavevotes nvarchar2(10));
Table created.
SQL> create or replace procedure
2 sp_equalvote(p_area in nvarchar2, p_dateofvote in date)
3 is
4 test nvarchar2(255);
5 begin
6 select
7 case when remainvotes = leavevotes then remainvotes end
8 into test
9 from voting
10 where remainvotes = leavevotes
11 and area = p_area
12 and dateofvote = p_dateofvote;
13 end;
14 /
Procedure created.
SQL>
[EDIT]
After reading the comment, perhaps you'd rather use a function.
Some sample values:
SQL> insert into voting values (1, date '2019-02-20', 100, 15);
1 row created.
SQL> insert into voting values (1, date '2019-03-10', 300, 300);
1 row created.
Function:
SQL> create or replace function
2 sp_equalvote(p_area in nvarchar2, p_dateofvote in date)
3 return nvarchar2
4 is
5 test nvarchar2(255);
6 begin
7 select
8 case when remainvotes = leavevotes then 'draw'
9 else 'not equal'
10 end
11 into test
12 from voting
13 where area = p_area
14 and dateofvote = p_dateofvote;
15
16 return test;
17 end;
18 /
Function created.
SQL>
Testing:
SQL> select * From voting;
AREA DATEOFVOTE REMAINVOTE LEAVEVOTES
---------- ---------- ---------- ----------
1 20.02.2019 100 15
1 10.03.2019 300 300
SQL> select sp_equalvote(1, date '2019-02-20') res from dual;
RES
--------------------
not equal
SQL> select sp_equalvote(1, date '2019-03-10') res from dual;
RES
--------------------
draw
SQL>
DECLARE is not allowed in the body of a PL/SQL procedure. The IS or AS serves the purpose of delimiting where the variable declaration section starts - so your procedure should be
create or replace PROCEDURE sp_EqualVote(AREA IN NVARCHAR2, DATEOFVOTE IN DATE)
IS
test nvarchar(255);
BEGIN
SELECT
AREA,
DATEOFVOTE,
CASE
WHEN (REMAINVOTES = LEAVEVOTES) THEN REMAINVOTES
END AS EqualVote
INTO test
FROM VOTING
WHERE REMAINVOTES = LEAVEVOTES;
END;
You also had an extra END, which I removed.
Best of luck.

Select records from table where table name come from another table

We generate tables dynamically Eg. Table T_1, T_2, T_3, etc & we can get that table names from another table by following query.
SELECT CONCAT('T_', T_ID) AS T_NAME FROM T_NAMES WHERE T_KEY = 'ABC';
Now I want to get records from this retrieved table name. What can I do ?
I'm doing like following but that's not working :
SELECT * FROM (SELECT CONCAT('T_', T_ID) AS T_NAME FROM T_NAMES WHERE T_KEY = 'ABC')
FYI : I'm hitting two individual queries as of now though I want to eliminate one and I can not follow cursor/procedure approach due to some limitations.
A procedure which utilizes refcursor seems to be the most appropriate to me. Here's an example:
SQL> -- creating test case (your T_NAMES table and T_1 which looks like Scott's DEPT)
SQL> create table t_names (t_id number, t_key varchar2(3));
Table created.
SQL> insert into t_names values (1, 'ABC');
1 row created.
SQL> create table t_1 as select * from dept;
Table created.
SQL> -- a procedure; accepts KEY and returns refcursor
SQL> create or replace procedure p_test
2 (par_key in varchar2, par_out out sys_refcursor)
3 as
4 l_t_name varchar2(30);
5 begin
6 select 'T_' || t_id
7 into l_t_name
8 from t_names
9 where t_key = par_key;
10
11 open par_out for 'select * from ' || l_t_name;
12 end;
13 /
Procedure created.
OK, let's test it:
SQL> var l_out refcursor
SQL> exec p_test('ABC', :l_out)
PL/SQL procedure successfully completed.
SQL> print l_out
DEPTNO DNAME LOC
---------- -------------- -------------
10 ACCOUNTING NEW YORK
20 RESEARCH DALLAS
30 SALES CHICAGO
40 OPERATIONS BOSTON
SQL>
I could propose to you Dynamic SQL.
First of all, you need to create a cursor. The cursor will iterate by the dynamic tables. Then you could use dynamic SQL to create a query and then execute it.
So example:
https://livesql.oracle.com/apex/livesql/file/content_C81136WLRFYZF8ION6Q57GWE1.html - detailed cursor example.
https://docs.oracle.com/cd/B28359_01/appdev.111/b28370/dynamic.htm#i13057 - dynamic SQL in Oracle

Returning the value of identity column after insertion in Oracle

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>

Oracle Constraints

I am working on a small school project using oracle database. I have created some tables and two of them are Mobile (Mobile_Number,Status_Flag) Status_Flag shows if a number is active or not and there is another table Owner_Mobile(Owner_Id FK,Mobile_ID FK). Now I should write a Constraint that prohibits the insert operation if the corresponding Status_Flag is N for the specified number. I tried to make it using sub query but this is not possible.
the constrain should be applied to OWNER_MOBILE table of course. For example if I say: INSERT INTO OWNER_MOBILE(25541,042536) the constrain should check the Mobile table and see if the Mobile 042536 is active or not . If the number is not active the insert statement should generate a error
You can use the trigger or another PL/SQL API for this but you should take into account ACID transaction principles. Let's consider the case when flag value = 0 should prevent insertion:
SQL> create table mobile (mobile_id int primary key, flag int)
2 /
SQL> create table owner_mobile(owner_id int,
2 mobile_id int references mobile(mobile_id))
3 /
SQL> insert into mobile values (1,1)
2 /
SQL> commit
2 /
SQL> create or replace trigger
2 tr_owner_mobile
3 before insert on owner_mobile
4 for each row
5 declare
6 l_flag mobile.flag%type;
7 begin
8 select flag into l_flag
9 from mobile where mobile_id = :new.mobile_id;
10
11 if l_flag = 0 then
12 raise_application_error(-20000, 'Unavalable mobile');
13 end if;
14 end;
15 /
In the code above I simply select flag and rely on the retrieved value - I don't care of ACID.
In the first transaction I update flag value but don't commit:
SQL> update mobile set flag = 0 where mobile_id = 1;
In the second transaction I insert into owner_mobile and get the success:
SQL> insert into owner_mobile values(1,1);
1 row inserted.
Next, I commit the first transaction and later - the second one. What I get then:
SQL> select * from mobile;
MOBILE_ID FLAG
---------- ----------
1 0
SQL> select * from owner_mobile;
OWNER_ID MOBILE_ID
---------- ----------
1 1
Seems this is not what I expect.
I can use select for update to prevent inconsistent behavoiur:
SQL> update mobile set flag = 1;
1 row updated.
SQL> delete from owner_mobile;
1 row deleted.
SQL> commit;
SQL> create or replace trigger
2 tr_owner_mobile
3 before insert on owner_mobile
4 for each row
5 declare
6 l_flag mobile.flag%type;
7 begin
8 select flag into l_flag
9 from mobile where mobile_id = :new.mobile_id
10 for update;
11
12 if l_flag = 0 then
13 raise_application_error(-20000, 'Unavalable mobile');
14 end if;
15 end;
16 /
Now do the same:
SQL> update mobile set flag = 0 where mobile_id = 1;
1 row updated.
Second transaction is waiting because parent row is locked:
SQL> insert into owner_mobile values(1,1);
After commit in first transaction I get in the second one:
SQL> insert into owner_mobile values(1,1);
insert into owner_mobile values(1,1)
*
error in line 1:
ORA-20000: Unavalable mobile
ORA-06512: at "SCOTT.TR_OWNER_MOBILE", line 9
ORA-04088: error in trigger 'SCOTT.TR_OWNER_MOBILE'
So whatever you do to achieve requirements you will have to consider transaction isolation.