Oracle user defined records - oracle

In Oracle PL/SQL records we can use anchor datatypes (including %TYPE and %ROWTYPE) to define the fields.
When I populate a record from a query, in my select clause I want type conversion. Is that possible using an Oracle built-in function or some other approach?
In this example scenario I am using a simple decode function to perform a conversion:
DECLARE
TYPE TEST_RECORD IS RECORD(
FIRST_NAME EMPLOYEE_MT.FIRST_NAME%TYPE,
LAST_NAME EMPLOYEE_MT.LAST_NAME%TYPE,
MARITIAL_STATUS EMPLOYEE_MT.MARITAL_STATUS%TYPE);
EMPLOYEE_NAME TEST_RECORD;
BEGIN
SELECT EMP.FIRST_NAME,
EMP.LAST_NAME,
DECODE(EMP.MARITAL_STATUS, 1, 'MARRIED', 0, 'UN-MARRIED')
INTO EMPLOYEE_NAME
FROM EMPLOYEE_MT EMP
WHERE EMP.EMPLOYEE_ID = 1;
DBMS_OUTPUT.put_line(EMPLOYEE_NAME.MARITIAL_STATUS);
END;
which gets error:
ORA-06502: PL/SQL: numeric or value error: character to number conversion error
ORA-06512: at line 9

You have defined your record type with the maritial_status (shouldn't that be marital_status?) field using the same data type as the table column. From your decode that appears to be a number data type. You're then trying to set the record's field value to a string, either 'MARRIED' or 'UN-MARRIED', when that field is expecting a number. Clearly neither of those strings can be converted to a number, hence the error you're getting.
If you want the record to store the string value, you'll have to define it like that - explicitly as a string, rather than using %TYPE:
DECLARE
TYPE TEST_RECORD IS RECORD(
FIRST_NAME EMPLOYEE_MT.FIRST_NAME%TYPE,
LAST_NAME EMPLOYEE_MT.LAST_NAME%TYPE,
MARITAL_STATUS VARCHAR2(10));
...
DBMS_OUTPUT.put_line(EMPLOYEE_NAME.MARITAL_STATUS);
...
You can't do that automatically using %TYPE as the data type just doesn't match. You've explicitly told Oracle that you want the field's data type to be a number, so Oracle isn't going to let you put a string in that field instead. It isn't about there not being a built-in function, it just doesn't make sense.
This also means you can't use %ROWTYPE if you're changing the data type either (unless your modified value can be implicitly converted back to the column data type).

Related

create a random uuid function and use it to generate column value in oracle [duplicate]

I am trying to update a table in Oracle. The table is created using following DDL:
CREATE TABLE TEST (
ID_NUM INTEGER,
NAME INTEGER,
VALUE INTEGER,
ITEMS_NUM INTEGER,
)
And there were some data injected into this table. Now, I need to update the table to change the ID_NUM column as VARCHAR and add formatted UUID as default value.
I have followed the queries given below:
CREATE OR REPLACE FUNCTION RANDOM_UUID RETURN VARCHAR IS
V_UUID VARCHAR(255);
BEGIN
SELECT REGEXP_REPLACE(RAWTOHEX(SYS_GUID()), '([A-F0-9]{8})([A-F0-9]{4})([A-F0-9]{4})([A-F0-9]{4})([A-F0-9]{12})', '\1-\2-\3-\4-\5') INTO V_UUID FROM DUAL;
RETURN V_UUID;
END RANDOM_UUID;
ALTER TABLE TEST
DROP COLUMN ID_NUM;
ALTER TABLE TEST
ADD ID_NUM VARCHAR(255) DEFAULT random_uuid() NOT NULL;
It gives an error as SQL Error [4044] [42000]: ORA-04044: procedure, function, package, or type is not allowed here
I have executed and validated the function using following command and it gives a valid formatted UUID.
SELECT RANDOM_UUID() FROM DUAL;
What could be the issue in the ALTER table statement. Can't we use a function for setting default value in Oracle?
Thanks in advance.
I think you can achieve it using the default clause on the column but without function (just replace the function call with the content of the function in default clause) as following. (Please note that the User functions are not allowed in the default clause)
ALTER TABLE TEST
ADD ID_NUM VARCHAR(255)
DEFAULT REGEXP_REPLACE(RAWTOHEX(SYS_GUID()), '([A-F0-9]{8})([A-F0-9]{4})([A-F0-9]{4})([A-F0-9]{4})([A-F0-9]{12})', '\1-\2-\3-\4-\5')
NOT NULL;
I have prepared the db<>fiddle demo to show you the error with function and success without function.
Cheers!!
You cannot use PL/SQL functions in the default expression. But it can be a SQL function.Here's an extract from the 19c Doc:
Default column values are subject to the following restrictions:
A DEFAULT expression cannot contain references to PL/SQL functions or
to other columns, the pseudocolumns LEVEL, PRIOR, and ROWNUM, or date
constants that are not fully specified.
And here's an example using a sql function:
SQL> create table tc (c1 number default sqrt(2));
Table TC created.
The default value has to be an actual value, not a function.

How to use sequence in a function in PLSQL?

In my schema, I have a reference number column in my settlement
table with a null value of varchar2 type, that I want to update it continuously.
Then I've created a sequence called ref_seq_num using a built-in function.
I want to use it (ref_seq_num) within my function get_ref_num to update the sequence ref. number to my settlement table,
which the return type also is varchar2 and I have a function like below
CREATE OR REPLACE FUNCTION get_ref_num RETURN settlement.ref_nr %TYPE IS
v_ref_seq settlement.ref_nr%TYPE;
BEGIN
v_ref_seq := to_char(sysdate, 'YYYYMMDD')||LPAD(ref_seq_num.nextval, 8,'0');
RETURN v_ref_seq;
END get_ref_num;
However, I bum into this error message 1/55 PLS-00302: component 'ref_nr' must be declared. I also tried changing the data type to varchar2 and error message is PLS-00215: String length constraints must be in range (1 .. 32767) How can I fix it?
According to your code, it seems that there's a table whose name is SETTLEMENT, but it doesn't contain the REF_NR column.
The following example shows how to do that:
SQL> create sequence ref_seq_num;
Sequence created.
A table that does contain the REF_NR column (which is then used in the function):
SQL> create table settlement (ref_nr varchar2(20));
Table created.
Your code, unmodified:
SQL> CREATE OR REPLACE FUNCTION get_ref_num RETURN settlement.ref_nr %TYPE IS
2 v_ref_seq settlement.ref_nr%TYPE;
3 BEGIN
4 v_ref_seq := to_char(sysdate, 'YYYYMMDD')||LPAD(ref_seq_num.nextval, 8,'0');
5 RETURN v_ref_seq;
6 END get_ref_num;
7 /
Function created.
Testing:
SQL> select get_ref_num from dual;
GET_REF_NUM
--------------------------------------------------------------------------------
2019050400000001
SQL>
If you have a column called ref_nr within settlement table, you code must work properly. I think the problem in the second case raises due to missing data precision part ( should be such as varchar2(16) ) for defining the variable as v_ref_seq varchar2. I would prefer using a numeric type such as number or int to hold the values for ref_nr, since they are all numeric, and this data type protects the data remain as numeric. Whenever you need to query you may use to_char function preventing exponential display( select to_char(ref_nr) from settlement ).
Moreover, if you use Oracle 12c version, you don't need to create such an extra function, just alter your table so that being sequence as your default for the column :
alter table settlement
modify ref_nr default to_char(sysdate, 'yyyymmdd')||lpad(ref_seq_num.nextval, 8,'0');

How to insert a blank value instead of NULL in Columns other than String datatype in hive

I have a create statement like
CREATE TABLE temp_tbl (EmpId String,Salary int);
I would like to insert an employee id and a blank value into table.
So What I have done is
insert overwrite table temp_tbl select '013' as EmpId,'' as Salary from tbl;
hive> select * from temp_tbl;
OK
013 NULL
But expected result is
hive> select * from temp_tbl;
OK
013 NULL ---> Blank instead of NULL
Also tried with "". Still I get it as NULL instead of blank
3.Tried to create table with serialization property
CREATE TABLE temp_tbl (EmpId String,Salary int) TBLPROPERTIES ('serialization.null.format' = '');
That too didn't change NULL value to blank.
What can be the workaround for the same.
Use Case while selecting the data.
Select
(CASE
WHEN columnName is null THEN ''
ELSE columnName
END) as 'Result' from temp_tbl;
All types except strings/varchar/char and some complex types like array, in Hive cannot be blank, only NULL is possible. Empty string '' is quite normal value of type String. You can produce empty array() as well (Array with zero size).
As a workaround, you can use some predefined values which are not normally in your data to represent some special numeric values, like -99999. Alternatively you can store your numeric values in a String column, in such case you will be able to have empty values in it. But it's not possible to assign (cast) empty strings to numeric types, because such empty value is not allowed.
If you try to assign empty string to numeric column or cast to numeric type, the result will be the same as if you are converting non-numeric string to numeric - NULL (in Hive if not possible to cast, it returns NULL) or get java.lang.NumberFormatException in Java.
Knowing that datatype Int can be either NULL or integer , I'd think of how to work around the problem.
I have the impression that 0 can do the job. Why can it not?
If 1 is not ideal, why not create a new temp_employees_with_no_salary table?
If 2 is not ideal, can you afford to change the datatype of temp_tbl.Salary from Int to String, then use CAST(Salary AS INT) to work with it?

I want to export data from oracle database to csv, and I am putting a number filter on a varchar column. It thows ORA:01722 error. Please suggest

Select count(*) from table where loc between 300 to 400.
loc is a varchar column.
it is not selecting all the data
checking the count, gives ORA :01722 error
exporting the results with error.
Edit from comment:
loc contains values less than 300, more than 400, and alphanumeric like 'GT' , '3KT1'
loc is a varchar column.
[From comment] The Loc column has char type value also like GJ, 3KT1
LOC contains values which are not convertible to numbers. This matters because your WHERE clause predicates are defined as numbers, so Oracle applies an implicit to_number(loc) to the query. This is why using proper data types is best practice: it doesn't help you now but please learn the lesson, and use NUMBER columns for numeric data.
In the meantime you have several options to deal with your shonky data model.
If you're lucky enough to be using Oracle 12c R2 you can use the new VALIDATE_CONVERSION() function to exclude values of loc which can't be cast to numbers. Find out more
If you're using an earlier version of Oracle you can build your own function:
create or replace function is_number
(p_str in varchar2) return number
is
n number;
rv number;
begin
begin
n := to_number(p_str);
rv := 1;
exception
when invalid_number then
rv := 0;
end;
return rv;
end;
The weakest option would be casting the predicates to strings. where loc between '300' to '400' would include '3000', '4' and various other values you probably don't want.
Here is a LiveSQL demo (free Oracle Technet account required, alas).
Your current query is trying to compare a varchar to a number. So it tries to convert the varchar to a number on the fly. This is called implicit conversion.
You should make it compare a varchar to a varchar.
Use single quotes so that you are comparing to varchars, not numbers
Select count(*) from table where loc between '300' to '400'
Then go and read about implicit conversion
Based on the update to your question, this column is a legitimate varchar and should not be converted to a numeric data type.
However you do need to work out whether you are incorrectly storing different types of data in the same column

Create Type based on an exiting Table

As the title said : I want to create a type in oracle based on an existing Table.
I did as follow :
create or replace type MY_NEW_TYPE as object( one_row EXISTING_TABLE%rowtype);
The Aim is to be able to use this into a function which will return a table containing sample row of the table EXISTING_TABLE :
create or replace function OUTPUT_FCT() return MY_NEW_TYPE AS
...
If you only need to create a function that returns a row from your table, you could try something like the following, without creating types.
setup:
create table EXISTING_TABLE( a number, b varchar2(100));
insert into EXISTING_TABLE values (1, 'one');
function:
create or replace function OUTPUT_FCT return EXISTING_TABLE%rowtype AS
retVal EXISTING_TABLE%rowType;
begin
select *
into retVal
from EXISTING_TABLE
where rownum = 1;
--
return retVal;
end;
function call
SQL> begin
2 dbms_output.put_line(OUTPUT_FCT().a);
3 dbms_output.put_line(OUTPUT_FCT().b);
4 end;
5 /
1
one
However, I would not recommend such an approach, because things like select * can be really dangerous; I would much prefer defining a type with the fields I need, and then explicitly query my table for the needed columns.
No, you can't do that, you'll get a compilation error:
create or replace type my_new_type as object(one_row t42%rowtype);
/
Type MY_NEW_TYPE compiled
Errors: check compiler log
show errors
Errors for TYPE STACKOVERFLOW.MY_NEW_TYPE:
LINE/COL ERROR
-------- -----------------------------------------------------------------------
0/0 PL/SQL: Compilation unit analysis terminated
1/36 PLS-00329: schema-level type has illegal reference to MYSCHEMA.T42
You will need to specify each field in the object type, and you will have to specify the data types manually too - you can't use table.column%type either.
You could create the type dynamically based on column and data type information from the data dictionary, but as this will (hopefully) be a one-off task and not something you'd do at runtime, that doesn't really seem worth it.
You can create a PL/SQL table type based on your table's rowtype, but you would only be able to call a function returning that from PL/SQL, not from plain SQL - so you couldn't use it in a table collection expression for example. If you were only returning a single sample row you could return a record rather than a table, but the same applies. You can also have a function that returns a ref cursor which could match the table's structure, but you wouldn't be able to treat that as a table either.
Read more about object type creation in the documentation. Specifically the attribute and datatype sections.

Resources