Is there any positive or negative (performance) impact if constants in Oracle packages/procedures/functions are defined as (implicit) not null? E.g.
// Varchar2
C_LF_V1 constant varchar2(1) := chr(10);
C_LF_V2 constant varchar2(1) not null := chr(10);
// PLS number
C_MAX_PLS_STR_LENB_V1 constant pls_integer := 32767;
C_MAX_PLS_STR_LENB_V2 constant pls_integer not null := 32767;
C_MAX_PLS_STR_LENB_V3 constant positiven := 32767;
C_MAX_PLS_STR_LENB_V4 constant simple_integer := 32767;
// Date
C_MAX_DATE_V1 constant date := date '9999-12-31'
C_MAX_DATE_V2 constant date not null := date '9999-12-31'
// ...
The documentation is not specific for such details at all, as (unfortunately) way too often - having checked from 11 to 19 (in case it actually is version specific, implementation as of 19+ would be of interest).
Of course, running some tests is possible/easy, but that would only provide platform/version/machine specific results, whereas the low-level-implementation/conceptual logic is much more interesting, i.e.
will (implicit) not null be compiled as known to be not-NULL or will it cause a NULL check upon every access?
Update (clarification):
my questions relates to constants that are known to be NOT NULL (so it's not about NULL handling)
I'm not after "wannabe" nanoseconds tuning at all - I'd like to know what actually happens behind the scene; e.g. for simple_integer the doc says that it may make heavy computations more efficient, which implies that upon compilation it "knows" that such a variable can never be NULL -> so does this implication also apply to e.g. a date defined as NOT NULL? - or a bit more tricky to a varchar2? (where defining a NOT NULL subtype is allowed but does not make sense as the nullable attribute is ignored)
I don't know.
Just thinking aloud.
This compiles:
SQL> create or replace package pkg_test as
2 c_v1 constant varchar2(1) := null;
3 end;
4 /
Package created.
Specifying the constant to be NOT NULL and assigning NULL to it won't work:
SQL> create or replace package pkg_test as
2 c_v1 constant varchar2(1) not null := null;
3 end;
4 /
Warning: Package created with compilation errors.
SQL> show err
Errors for PACKAGE PKG_TEST:
LINE/COL ERROR
-------- -----------------------------------------------------------------
2/8 PL/SQL: Declaration ignored
2/41 PLS-00382: expression is of wrong type
Although empty strings are treated as NULLs, this will not raise any errors:
SQL> create or replace package pkg_test as
2 c_v1 constant varchar2(1) not null := '';
3 end;
4 /
Package created.
SQL>
Does it make sense? A constant whose value is NULL? Maybe, maybe not, I can't remember I used it.
As of a package body: let's reuse the last code and add a procedure:
SQL> create or replace package pkg_test as
2 c_v1 constant varchar2(1) not null := '';
3
4 procedure p_test;
5 end;
6 /
Package created.
SQL> create or replace package body pkg_test is
2 procedure p_test is
3 begin
4 c_v1 := 'x';
5 end;
6 end;
7 /
Warning: Package Body created with compilation errors.
SQL> show err
Errors for PACKAGE BODY PKG_TEST:
LINE/COL ERROR
-------- -----------------------------------------------------------------
4/5 PL/SQL: Statement ignored
4/5 PLS-00363: expression 'C_V1' cannot be used as an assignment
target
SQL>
Right; it doesn't even make sense - it is a constant, you aren't supposed to modify its value.
Documentation says:
The information in "Declaring Variables" also applies to constant declarations, but a constant declaration has two more requirements: the keyword CONSTANT and the initial value of the constant.
(...)
In a variable declaration, the initial value is optional unless you specify the NOT NULL constraint . In a constant declaration, the initial value is required.
It looks like Oracle treats variable and constant declarations almost the same, with some differences. As if constants "inherited" NOT NULL constraint from variables. Does it make sense? Can't tell.
More from the same document:
If the declaration is in a package specification, the initial value is assigned to the variable or constant for each session (whether the variable or constant is public or private).
So: it is assigned once for the session. It is a constant, after all ...
Therefore, from my point of view, I wouldn't expect any performance impact on whether you impose a NOT NULL constraint to a constant or not. I'd say that there are other things to worry about, such as tuning queries within the package body; they, if written poorly, will have significant impact on performance. Constants being NULL or NOT NULL will be the least of your worries.
There are some minor compile time optimizations that can be utilised, but its unlikely they'd have any significant benefits, eg if you do something like
create or replace
package PKG is
x constant int not null := 0;
procedure p;
end;
/
create or replace
package body PKG is
procedure p is
y int;
begin
for i in 1 .. 1000000
loop
if x is not null then
if x is not null then
if x is not null then
if x is not null then
if x is not null then
if x is not null then
if x is not null then
if x is not null then
if x is not null then
if x is not null then
if x is not null then
if x is not null then
if x is not null then
if x is not null then
if x is not null then
if x is not null then
if x is not null then
if x is not null then
if x is not null then
if x is not null then
if x is not null then
if x is not null then
if x is not null then
if x is not null then
if x is not null then
if x is not null then
if x is not null then
if x is not null then
if x is not null then
if x is not null then
if x is not null then
if x is not null then
if x is not null then
if x is not null then
if x is not null then
if x is not null then
if x is not null then
if x is not null then
if x is not null then
if x is not null then
if x is not null then
if x is not null then
if x is not null then
if x is not null then
if x is not null then
if x is not null then
if x is not null then
if x is not null then
if x is not null then
if x is not null then
if x is not null then
if x is not null then
if x is not null then
if x is not null then
if x is not null then
if x is not null then
if x is not null then
if x is not null then
if x is not null then
if x is not null then
y := i;
end if;
end if;
end if;
end if;
end if;
end if;
end if;
end if;
end if;
end if;
end if;
end if;
end if;
end if;
end if;
end if;
end if;
end if;
end if;
end if;
end if;
end if;
end if;
end if;
end if;
end if;
end if;
end if;
end if;
end if;
end if;
end if;
end if;
end if;
end if;
end if;
end if;
end if;
end if;
end if;
end if;
end if;
end if;
end if;
end if;
end if;
end if;
end if;
end if;
end if;
end if;
end if;
end if;
end if;
end if;
end if;
end if;
end if;
end if;
end if;
end loop;
end;
end;
/
set timing on
exec pkg.p;
you'll see that removing CONSTANT in the constant definition yields a faster result, but we're still talking micro/nanoseconds of benefit per call. Similarly, if it is not a constant but has the NOT NULL you get the same benefit.
The main intent of such declarations is just another form of code and data correctness rather than performance.
Related
In this procedure I want to make a list of all procedures in my code and the user chooses a number to execute a certain procedure or function.
Here's my code
CREATE OR REPLACE PROCEDURE calling
IS
chosen VARCHAR2(1);
V_count NUMBER;
BEGIN
&chosen;
IF chosen='1' THEN
V_count:=visited.count_nb_city_visited(&idclient);
ELSIF chosen='2' THEN
V_count:=visited.max_vis;
visited.extract_best(V_count);
ELSIF chosen='3' THEN
ORDER_ORIGIN_CITY.ORDER_CITY;
ELSIF chosen='4' THEN
ORDER_ORIGIN_CITY.ORDER_CLIENT;
END IF;
END;
When running the code, this warning pops up "PLS-00103: Symbol "2" encountered (if I choose the number 2 and the same error is shown with any number I choose)
You can't (shouldn't) use substitution variables in a stored procedure as it will substitute the values in on compilation (and not as you are probably expecting on execution).
So if you substitute 2 for &chosen and 42 for &idclient then your code will be compiled as:
CREATE OR REPLACE PROCEDURE calling
IS
chosen VARCHAR2(1);
V_count NUMBER;
BEGIN
2;
IF chosen='1' THEN
V_count:=visited.count_nb_city_visited(42);
ELSIF chosen='2' THEN
V_count:=visited.max_vis;
visited.extract_best(V_count);
ELSIF chosen='3' THEN
ORDER_ORIGIN_CITY.ORDER_CITY;
ELSIF chosen='4' THEN
ORDER_ORIGIN_CITY.ORDER_CLIENT;
END IF;
END;
And will always execute with those fixed values.
You are getting the error because 2; is not a valid PL/SQL statement.
Instead, you should pass all the bind variables in the signature. Something like:
CREATE OR REPLACE PROCEDURE calling (
chosen IN NUMBER,
idclient IN NUMBER
)
IS
V_count NUMBER;
BEGIN
IF chosen = 1 THEN
V_count:=visited.count_nb_city_visited(idclient);
ELSIF chosen = 2 THEN
V_count:=visited.max_vis;
visited.extract_best(V_count);
ELSIF chosen = 3 THEN
ORDER_ORIGIN_CITY.ORDER_CITY;
ELSIF chosen = 4 THEN
ORDER_ORIGIN_CITY.ORDER_CLIENT;
END IF;
END;
/
If you want to call it from an anonymous PL/SQL block, then you can use substitution variables in that block:
BEGIN
calling(
chosen => &chosen,
idclient => &idclient
);
END;
/
I am pretty amateur to PL/SQL, and I do not know if I am using the IF statements correctly. I am using Oracle Live SQL. This is all trying to insert a new row into a table called 'employees'. And the only NOT NULL values are employeeid, employeename, and jobid
CREATE OR REPLACE PROCEDURE employees.insert_employee
(
p_employeeid employees.employeeid%TYPE,
p_employeename employees.employeename%TYPE,
p_phone employees.phone%TYPE,
p_jobid employees.jobid%TYPE,
p_salary employees.salary%TYPE,
p_managerid employees.managerid%TYPE,
p_departmentid employees.departmentid%TYPE
)
AS
BEGIN
IF p_employeeid IS NULL THEN /* If one of the mandatory values are null */
RAISE VALUE_ERROR;
END IF;
IF p_employeename IS NULL THEN
RAISE VALUE_ERROR;
END IF;
IF p_jobid IS NULL THEN
RAISE VALUE_ERROR;
END IF;
IF p_jobid != employees.jobid THEN /* if jobid entered is not in the table */
RAISE VALUE_ERROR;
END IF;
IF p_salary < 0 THEN /* if the entered salary is negative */
RAISE VALUE_ERROR;
END IF;
IF p_departmentid != employees.departmentid THEN /* if the departmentid entered is not in the table */
RAISE VALUE_ERROR;
END IF;
IF p.employeeid = employees.employeeid THEN /* if the employeeid already exists */
RAISE RAISE_APPLICATION_ERROR(-2000);
END IF;
INSERT INTO employees (employeeid, employeename, phone, jobid, salary, managerid, departmentid)
VALUES(p_employeeid, p_employeename, p_phone, p_jobid, p_salary, p_managerid, p_departmentid);
END;
I don't think that it would even compile. Besides, you shouldn't do it that way (as you were already told). A few more objections, if I may (regarding the original question: whether you use IF correctly).
Bunch of first IFs can be shortened with OR:
IF p_employeeid IS NULL OR
p_employeename IS NULL OR
p_jobid IS NULL OR
p_salary < 0
THEN
RAISE VALUE_ERROR;
END IF;
You can't reference table values that way, e.g.
IF p_jobid != employees.jobid THEN /* if jobid entered is not in the table */
RAISE VALUE_ERROR;
END IF;
There's no employees.jobid - you have to select it first. For example:
declare
l_cnt;
begin
select count(*)
into l_cnt
from employees e
where e.jobid = p_jobid;
if l_cnt = 0 then -- there's no such job in the table
raise value_error;
end if;
end;
Finally, the final condition you checked and tried to raise something
RAISE RAISE_APPLICATION_ERROR(-2000);
is wrong for 3 reasons:
you don't RAISE RAISE_...
User defined exception's range is from -20001 to -20999 (five digits, not 4)
RAISE_APPLICATION_ERROR requires yet another argument
so - correctly - you'd
raise_application_error(-20001, 'That does not exist');
Even if the syntax is right, I don't think you are using them correctly.
1) If things should not be allowed to be null then mark them as NOT NULL on the tables.
2) If the departmentID must exist then that's a foreign key constraint.
3) If the employeeID exists that should be a unique constraint (even if your syntax works, which it does not)
Properly declared, the DB engine will ensure all this for you.
I am creating a trigger to add same SEQ.nextval numbers to two fields such as follows:
SITE_NUM SITE_NUM_COPY Site_Name
346 346 XYZ
347 347 ABC
348 348 DEF
Whenever a new row is added through an application I want the SITE_NUM and SITE_NUM_COPY will be auto generated through the trigger.
This is what I have put together:
create or replace trigger SITE_TRIGGER
before insert or update on STRATEGY_SITES for each row
begin
if updating then
if :new.SITE_NUM is null and :new.SITE_NUM_Copy is NULL then
:new.SITE_NUM := :old.SITE_NUM
and :old.SITE_NUM := :new.SITE_NUM_COPY;
end if;
end if;
if inserting then
:new.SITE_NUM := SITENUM_SEQ.nextval
and :new.SITE_NUM_COPY := SITENUM_SEQ.nextval;
end if;
end;
Getting the following error:
Error(7,31): PLS-00103: Encountered the symbol "=" when expecting one of the following:
. ( * # % & = - + ; < / > at in is mod remainder not rem <> or != or ~= >= <= <> and or like like2 like4 likec between || indicator multiset member submultiset
The symbol "* was inserted before "=" to continue.
The error is not very helpful but is stemming from you using and to, apparently, seperate two statements inside each if block. The statement terminator/separator is a semicolon.
It looks like you are trying to do this for the first one:
if updating then
if :new.SITE_NUM is null and :new.SITE_NUM_Copy is NULL then
:new.SITE_NUM := :old.SITE_NUM;
:old.SITE_NUM := :new.SITE_NUM_COPY; -- illegal
end if;
end if;
but you cannot change the :old values; it isn't clear what you expect to happen, ot least as the value assigning must be null. You may just want to remove that line.
And for the second part:
if inserting then
:new.SITE_NUM := SITENUM_SEQ.nextval;
:new.SITE_NUM_COPY := SITENUM_SEQ.currval;
end if;
I've changed the second assignment to use currval because you said you wanted the same value for both columns. If you call nextval for both assignments they will get different values simce the sequnce will increment twice.
In an anonymous block I have an input string that is empty/null and want to check that against a non-null string. Example:
DECLARE
v_notnull varchar2(50):='this string is never null';
v_input varchar2(50):='';
BEGIN
IF trim(v_input) != trim(v_notnull) THEN
dbms_output.put_line('the strings do NOT match');
ELSE
dbms_output.put_line('the strings DO match');
END IF;
END;
The issue here is that when I run this block, the output is always 'the strings DO match' even though I am inputting the empty string '' (aka null) into v_input which is not the same as the string 'this string is never null'. How can I make sure oracle covers the empty string case? When v_input is empty I want the output to be 'the strings do NOT match'.
The documentation has a section on null handling. An empty string is treated the same as null, and you cannot compare nulls (of any type) with equality - as the table in the documentation shows, the result of comparing anything with null is unknown - neither true nor false. You have to use is [not] null to compare anything with null.
In this case you could spell it out explicitly, by seeing is one variable is null and the other isn't, and vice versa, and only compare the actual values if that tells you neither are null:
DECLARE
v_notnull varchar2(30):='this string is never null';
v_input varchar2(30):='';
BEGIN
IF (trim(v_input) is null and trim(v_notnull) is not null)
or (trim(v_input) is not null and trim(v_notnull) is null)
or trim(v_input) != trim(v_notnull) THEN
dbms_output.put_line('the strings do NOT match');
ELSE
dbms_output.put_line('the strings DO match');
END IF;
END;
/
the strings do NOT match
PL/SQL procedure successfully completed.
I've added the missing varchar2 sizes; presumably you based this on a procedure that took arguments without running what you were posting stand-alone...
'' is NULL in oracle. So, any comparison with null will always result in false.
Demo:
SQL> DECLARE
2 v_notnull varchar2(1000):='this string is never null';
3 v_input varchar2(1000):='';
4 BEGIN
5 IF v_input is null THEN
6 dbms_output.put_line('v_input is null'); -- should print because v_input is actually null
7 END IF;
8
9 IF trim(v_input) = trim(v_notnull) THEN -- always false because of null
10 dbms_output.put_line('the strings do NOT match');
11 ELSE
12 dbms_output.put_line('the strings DO match'); -- so should print this
13 END IF;
14 END;
15 /
v_input is null -- verified
the strings DO match -- verified
PL/SQL procedure successfully completed.
SQL>
Often you only care whether the values match, in which case null values make no difference:
declare
v_notnull varchar2(50) := 'this string is never null';
v_input varchar2(50) := '';
begin
if trim(v_input) = trim(v_notnull) then
dbms_output.put_line('the strings DO match');
else
dbms_output.put_line('the strings do NOT match');
end if;
end;
In some cases you can simplify a comparison of nullable values using a coalesce() or nvl() expression:
if nvl(v_yesno,'N') <> 'Y' then ...
You might be able to use a dummy comparison value (although of course there is a risk that it will match an actual value, depending on the situation):
if nvl(somevalue, chr(0)) <> nvl(othervalue, chr(0)) then ...
By the way, you can distinguish between null and '' by copying the value to a char variable, as a '' will trigger its normally unhelpful blank-padding behaviour. I can't really think of a situation where this would be useful, but just for fun:
declare
v1 varchar2(1) := null;
v2 varchar2(1) := '';
c char(1);
begin
c := v1;
if v1 is null and c is not null then
dbms_output.put_line('v1 = ''''');
elsif c is null then
dbms_output.put_line('v1 is null');
end if;
c := v2;
if v2 is null and c is not null then
dbms_output.put_line('v2 = ''''');
elsif c is null then
dbms_output.put_line('v2 is null');
end if;
end;
Output:
v1 is null
v2 = ''
I have below PL/SQL code and this is not going into IF block. Why is this not going into the IF Block?
Appreciate your responses.
DECLARE
p_datacenterid VARCHAR2(50) := '';
p_dcid VARCHAR2(50);
BEGIN
dbms_output.put_line('test');
-- Check if DataCenterId is null
IF nvl(p_datacenterid, '') = '' THEN
dbms_output.put_line('DataCenterID is empty');
SELECT datacenterid
INTO p_dcid
FROM pod_tab
WHERE url = 'dit3.ezlm.adp.com'
AND rownum = 1;
END IF;
END;
/
It looks like you are getting tripped up on comparing null values. An empty string is converted by Oracle into Null in the background. See here for details.
The IF statement ends up resolving to IF null = null which is never true.
Something like this would work
IF p_DataCenterID IS NOT NULL THEN
--assert the values are within a range
ELSE
--look up the value
END IF;
In PL/SQL and the Oracle database the empty string ('') is the same as NULL, and thus you must test for it as you would for NULL by using the IS NULL test instead of = NULL or = ''. Try running the following:
declare
p_DataCenterID varchar2(50) := ''; -- NOTE: '' is the same as NULL
p_dcID varchar2(50);
BEGIN
dbms_output.put_line( 'test');
-- Check if DataCenterId is null
IF p_DataCenterID IS NULL THEN
dbms_output.put_line('DataCenterID is empty');
SELECT DataCenterId
INTO p_dcID
FROM Pod_Tab
WHERE URL = 'dit3.ezlm.adp.com'
AND ROWNUM = 1;
END IF;
END;
/
This will print
test
DataCenterID is empty
Best of luck.