Oracle PL/SQL Scalar Variable Size Syntax Ambiguity - oracle

What types of expressions are allowed for declaring a scalar variable's size in a PL/SQL package?
Here is a db<>fiddle showing size expressions other than numeric literals where some produce errors and others don't. Below is a snippet.
create or replace package p as
vc varchar2(length('four'));
end;
/
Package created.
but
create or replace package p as
vc varchar2(greatest(3,4)); --PLS-00491: numeric literal required
end;
/
ORA-24344: success with compilation error
The previous example with length() shows that expressions other than numeric literal are allowed.
Both length() and greatest() are SQL functions. Why does one function invoke PLS-00491 and the other does not?
Note: The syntax for specifying the size of PL/SQL datatype does not seem to be provided in the documentation Scalar Variable Declaration.
Using Oracle 19c Enterprise Edition.
Thanks in advance.

I suspect it's because the length() function is deterministic - the length in characters of the string literal 'four' is always 4 - while greatest() is not. As shown in the documentation, it is affected by NLS settings; so while with my default English/binary settings I see this:
select greatest('á', 'b') from dual;
GREATEST('Á','B')
-----------------
á
if I change the settings I get a different result:
alter session set nls_comp = 'linguistic';
alter session set nls_language = 'spanish';
select greatest('á', 'b') from dual;
GREATEST('Á','B')
-----------------
b
db<>fiddle demo
While comparing numbers as you are isn't affected by NLS settings, it's the same function, so it's still not deterministic.
So, in theory at least, the size of your PL/SQL variable declared based on greatest() could be different depending on your session settings, which isn't tenable.

Related

Saving Persian/Arabic Digits and Numbers inside Oracle Database

We have an Oracle Database which has many records in it. Recently we noticed that we can not save Persian/Arabic digits within a column with a datatype nvarchar2 and instead of the numbers it shows question marks "?".
I went through this to check the charset using these commands :
SELECT *
from NLS_DATABASE_PARAMETERS
WHERE PARAMETER IN ('NLS_CHARACTERSET', 'NLS_NCHAR_CHARACTERSET');
and this command
SELECT USERENV('language') FROM DUAL;
The results are these two respectively:
I also issue this command :
SELECT DUMP(myColumn, 1016) FROM myTable;
And the result is like this :
Typ=1 Len=22 CharacterSet=AL16UTF16: 6,33,6,44,6,27,6,45,0,20,0,3f,0,3f,0,2f,0,3f,0,2f,0,3f
The results seem to be okay but unfortunately we still cannot save any Persian/Arabic digit within that column. however the Persian/Arabic alphabets are okay. Do you know what is the cause of this problem ?
Thank You
USERENV('language') does not return your client characters set.
So, SELECT USERENV('language') FROM DUAL; is equal to
SELECT l.value||'_'||t.value||'.'||c.value
FROM (SELECT * FROM NLS_SESSION_PARAMETERS WHERE PARAMETER = 'NLS_LANGUAGE') l
CROSS JOIN (SELECT * FROM NLS_SESSION_PARAMETERS WHERE PARAMETER = 'NLS_TERRITORY') t
CROSS JOIN (SELECT * FROM NLS_DATABASE_PARAMETERS WHERE PARAMETER = 'NLS_CHARACTERSET') c;
It is not possible to get the client NLS_LANG by any SQL statement (although there seems to be a quirky workaround: How do I check the NLS_LANG of the client?)
Check your client NLS_LANG setting. It is defined either by Registry (HKLM\SOFTWARE\ORACLE\KEY_%ORACLE_HOME_NAME%\NLS_LANG, resp. HKLM\SOFTWARE\Wow6432Node\ORACLE\KEY_%ORACLE_HOME_NAME%\NLS_LANG) or as Environment variable. The Environment variable takes precedence.
Then you must ensure that your client application (you did not tell us which one you are using) uses the same character set as specified in NLS_LANG.
In case your application runs on Java have a look at this: Database JDBC Developer's Guide - Globalization Support
See also OdbcConnection returning Chinese Characters as "?"
Do yourself a favor and convert the main character set of your database from AR8MSWIN1256 to AL32UTF8. Most of these problems will simply go away. You can forget about NCHAR and NVARCHAR2. They are not needed anymore.
It's a one-time effort that will pay back a thounsand times.
See Character Set Migration for instructions.

Why Does Oracle 10g to_char(date time) Truncate Strings?

I got a bug report where Oracle 10g was truncating return values from to_char(datetime):
SQL> select to_char(systimestamp, '"day:"DD"hello"') from dual;
TO_CHAR(SYSTIMESTAMP,'"DAY:"DD"HE
---------------------------------
day:27hel
Notably, this does not appear to happen in Oracle 11g. My question is, why does it happen at all? Is there some configuration variable to set to tell to_char(datetime) to allocate a bigger buffer for its return value?
I'm not sure but it might be just displaying in SQL*Plus. Have you tried to run it in Toad? Or if you assign result to varchar2 in PL/SQL block and output result?
Here what I've found in SQL*Plus Reference for 10g:
The default width and format of unformatted DATE columns in SQL*Plus
is determined by the database NLS_DATE_FORMAT parameter. Otherwise,
the default format width is A9. See the FORMAT clause of the COLUMN
command for more information on formatting DATE columns.
Your values is trimmed to 9 characters which corresponds to default A9 format. I don't have same version and this behaviour is not reproducing in 11g so can you please check my theory?
I've got the same problem and I know the solution.
I use Release 11.2.0.4.0 but I beleave it is possible to repeat the situation in other versions. It somehow depends on client. (E.g. I cannot repeat it using SQL*Plus, only with PL/SQL Devepoper)
Try this:
select to_char(systimestamp, '"day:"DD"йцукенг OR any other UTF-encoded-something"') from dual
union all
select to_char(systimestamp, '"day:"DD"hello"') from dual;
You'll get the following result:
day:08йцукенг OR any other UTF-encoded-so
day:08hello
You can see the "mething" is lost. This is exactly 7 bytes exceeded because of 7 two-byte simbols "йцукенг". Oracle allocates buffer for the number of characters, not a number of required bytes.
The command
alter session set nls_length_semantics=byte/char
unfortunately does not affect this behavior.
So my solution is to cast a result as varchar2(enough_capacity)
select cast(to_char(systimestamp, '"day:"DD"йцукенг OR any other UTF-encoded-something"') as varchar(1000)) from dual
union all
select to_char(systimestamp, '"day:"DD"hello"') from dual
Explicit typecasting makes expression independent from client or configuration.
BTW, the same thing happens in all implicit to_char-conversions. E.g.
case [numeric_expression]
when 1 then '[unicode_containing_string]'
end
Result might be cutted.

PLSQL_V2_COMPATIBILITY Compatibility

I have migrated oracle database from 10g to 12c. From 12c oracle doesn't support PLSQL_V2_COMPATIBILITY parameter.
This parameter is used to :
https://www.safaribooksonline.com/library/view/oracle-database-administration/1565925165/re157.html
One of the highlighting point in above url is :
The PL/SQL compiler will allow OUT parameters to be used in expression
contexts in some cases, for example, in dot-qualified names on the
right-hand side of assignment statements. This behaviour is restricted
to fields of OUT parameters that are records, and OUT parameters
referenced in the FROM list of a SELECT statement.
Due to this change many of our packages went into error which previously using functions with record as out parameter.
CREATE OR REPLACE Package SJMUSER.nagendra AS
TYPE r_standard_url IS RECORD (
v_loginurl VARCHAR2(1000),
v_changepasswordurl VARCHAR2(1000),
v_newloginurl VARCHAR2(1000)
);
TYPE t_standardurl_tbl IS TABLE OF r_standard_url
INDEX BY BINARY_INTEGER;
t_standardurlstype t_standardurl_tbl;
FUNCTION Producestandardurls
(
tp_myuserid IN USERS.User_Id%TYPE,
v_scrntime IN VARCHAR2,
v_scrntoken IN VARCHAR2,
v_wwikey IN VARCHAR2
)
RETURN t_standardurlstype;
end nagendra;
/
Error :
PLS-00488 't_standardurlstype' must be type.
Whether this will required entire code changes ? like not using record as out parameter ? whether there is any solution for this ?
For the example you've shown, you just need to change the return type (for this function; potentially OUT parameter types for others) from:
RETURN t_standardurlstype;
to either:
RETURN t_standardurlstype%type;
or more simply:
RETURN t_standardurl_tbl;
Your are currently trying to return the variable t_standardurlstype, which is an instance of the t_standardurl_tbl type. With the PLSQL_V2_COMPATIBILITY setting it's allowing the type to be inferred from the variable, but you can't do that any more. You may not actually be using the variable anywhere else - if not you can remove its declaration, if you use the second form above.
So yes, you will need to make code changes, but only to how return and OUT parameters are declared, in both the package specification and body. You can continue to use a record as an OUT parameter, it just needs to be declared correctly.
There are other implications of that setting though. See My Oracle Support note 47354.1 for details, but summarising what that says, you also need to watch out for these changed behaviours, as PL/SQL now:
correctly enforces the read-only semantics of IN parameters and does not let index table methods modify index tables passed in as IN parameters.
does not permit OUT parameters to be used in expression contexts.
will not allow OUT parameters in the FROM clause of a SELECT list, where their value is read.
will return an error on the illegal syntax return expression which should be return type (which is what the code in your question is hitting).
does not allow the passing of an IN argument into another procedure as an OUT.
There is no quick fix or magic wand for these; if your code relies on any of the old behaviour in any of those categories then it will have to be modified to obey the 'new' syntax rules.

Oracle CHAR Comparison Not Working in Function

Could someone please explain to me the difference between the below two Oracle queries? I know they look very similar but the first one returns results and the second one does not. My implementation of the function can be seen below as well.
--Returns results
SELECT *
FROM <TABLE_NAME>
WHERE ID = CAST(<UserID> AS CHAR(2000)); --ID is defined as CHAR(8) in the DB.
--Does not return results
SELECT *
FROM <TABLE_NAME>
WHERE ID = CAST_TO_CHAR(<UserID>); --ID is defined as CHAR(8) in the DB.
--Function definition
CREATE OR REPLACE FUNCTION CAST_TO_CHAR(varToPad IN VARCHAR2)
RETURN CHAR IS returnVal CHAR(2000);
BEGIN
SELECT CAST(varToPad AS CHAR(2000))
INTO returnVal
FROM DUAL;
RETURN returnVal;
END;
/
It almost seems to me that the type is not persisting when the value is retrieved from the database. From what I understand from CHAR comparisons in Oracle, it will take the smaller of the two fields and truncate the larger one so that the sizes match (that is why I am casting the second variable to length 2000).
The reason that I need to achieve something like this is because a vendor tool that we are upgrading from DB2 to Oracle defined all of the columns in the Oracle database as CHAR instead of VARCHAR2. They did this to make their legacy code more easily portable to a distributed environment. This is causing big issues in our web applications because compares are now being done against fixed length CHAR fields.
I thought about using TRIM() but these queries will be accessed a lot and I do not want them to do a full table scan each time. I also considered RPAD(, ) but I don't really want to hard code lengths in the application as these may change in the future.
Does anyone have any thoughts about this? Thank you in advance for your help!
I have similar problem. It turned out that these are the rules of implicit data conversion. Oracle Database automatically converts a value from one datatype to another when such a conversion makes sense.
If you change your select:
SELECT *
FROM <TABLE_NAME>
WHERE CAST(ID as CHAR(2000)) = CAST_TO_CHAR(<UserID>);
You will see that's works properly.
And here's another test script showing that the function works correctly:
SET SERVEROUTPUT ON --for DBMS_OUTPUT.PUT_LINE.
DECLARE
test_string_c CHAR(8);
test_string_v VARCHAR2(8);
BEGIN
--Assign the same value to each string.
test_string_c := 'string';
test_string_v := 'string';
--Test the strings for equality.
IF test_string_c = CAST_TO_CHAR(test_string_v) THEN
DBMS_OUTPUT.PUT_LINE('The names are the same');
ELSE
DBMS_OUTPUT.PUT_LINE('The names are NOT the same');
END IF;
END;
/
anonymous block completed
The names are the same
Here are some rules govern the direction in which Oracle Database makes implicit datatype conversions:
During INSERT and UPDATE operations, Oracle converts the value to
the datatype of the affected column.
During SELECT FROM operations, Oracle converts the data from the
column to the type of the target variable.
When comparing a character value with a numeric value, Oracle
converts the character data to a numeric value.
When comparing a character value with a DATE value, Oracle converts
the character data to DATE.
When making assignments, Oracle converts the value on the right side
of the equal sign (=) to the datatype of the target of the assignment
on the left side.
When you use a SQL function or operator with an argument of a
datatype other than the one it accepts, Oracle converts the argument
to the accepted datatype.
Complete list of datatype comparison rules you can explore here

Restrict thousand separator in TO_NUMBER (Oracle)

I'm trying to parse numbers using the following code
TO_NUMBER('1,234.56', '9999999D99')
For some reason comma is ignored and the value is parsed correctly, despite the format doesn't have it. Is there any way to restrict usage of thousand group separator?
So far I only came up with setting a bogus symbol as a separator with the hope that user will not use it
TO_NUMBER('1,234.56', '9999999D99', 'NLS_NUMERIC_CHARACTERS=''.ã''');
This is a very strange request - if Oracle is correctly converting the string to a number then it seems to be doing it's job correctly. However, if you really need to do this for whatever reason then simply remove the format mask.
SQL> select to_number('1,234.56') from dual;
select to_number('1,234.56') from dual
*
ERROR at line 1:
ORA-01722: invalid number
SQL> select to_number('1234.56') from dual;
TO_NUMBER('1234.56')
--------------------
1234.56
SQL>
Though the SQL Language Reference doesn't mention any default values I believe that the default value for the format mask described in the OLAP DML Reference for TO_NUMBER() applies:
The default number format identifies a period (.) as the decimal marker and does not recognize any other symbol.
This, in turn, means that a comma is an invalid value for the conversion and thus the conversion will fail.

Resources