Oracle PL SQL Function return type size VARCHAR2(32767) incorrectly? - oracle

struggling with a PL/SQL Function here (new to me) - and what makes this weird is it appears to work on Oracle XE (local) and Oracle 12c - but when applied to AWS RDS Oracle instances it does not work.
I have a function which is returning a query column value from another table, and I am trying to add a function derived column to a new table.
The database is currently empty as it is being designed.
TableA
CompanyID Integer
VALUE VARCHAR2(128)
Function - This works executing in SQL Developer
create or replace FUNCTION MyFunction
(
CompanyID IN INT
) RETURN VARCHAR2
DETERMINISTIC
IS result VARCHAR2(128);
BEGIN
SELECT "VALUE" INTO result
FROM "TableA"
WHERE "CompanyID" = CompanyID;
RETURN result;
END MyFunction;
Next - Alter TableB to add column referencing MyFunction
ALTER TABLE "TableB" ADD "TableAValue" VARCHAR2(128) AS (MyFunction("CompanyID"))
Observe error
ORA-12899: value too large for column "TableAValue" (actual: 32767, maximum: 128)
12899. 00000 -  "value too large for column %s (actual: %s, maximum: %s)"
It's working OK in Oracle XE as mentioned - but when trying to create my database on AWS RDS Oracle, it's like it cannot determine that the size of column TableA.VALUE is only 128 in size, and treating it as maximum (for extended string size settings).
Am I missing something in the function that can force the DB Engine to see that it is appropriately sized? I have tried casting the SELECT "VALUE" to VARCHAR2(128) as well but that made no difference.
A bit confused, appreciate any insight!

Don't use a function and don't declare non-deterministic functions as DETERMINISTIC when they are not.
Instead, create a view:
CREATE VIEW tableb_view AS
SELECT b.*,
a.value AS tableavalue
FROM tableb b
LEFT OUTER JOIN tablea a
ON b.id = a.id
If you really want to use your function (DON'T) then you can specify the size using CAST:
ALTER TABLE TableB ADD TableAValue VARCHAR2(128) AS (
CAST( MyFunction(CompanyID) AS VARCHAR2(128) )
)

Related

Oracle access varray elements in SQL

I'm playing around with array support in Oracle and hit a roadblock regarding array access within a SQL query. I'm using the following schema:
create type smallintarray as varray(10) of number(3,0);
create table tbl (
id number(19,0) not null,
the_array smallintarray,
primary key (id)
);
What I would like to do is get the id and the first element i.e. at index 1 of the array. In PostgreSQL I could write select id, the_array[1] from tbl t but I don't see how I could do that with Oracle. I read that array access by index is only possible in PL/SQL, which would be fine if I could return a "decorated cursor" to achieve the same result through JDBC, but I don't know if that's possible.
DECLARE
c1 SYS_REFCURSOR;
varr smallintarray2;
BEGIN
OPEN c1 FOR SELECT t.id, t.THE_ARRAY from tbl t;
-- SELECT t.THE_ARRAY INTO varr FROM table_with_enum_arrays2 t;
-- return a "decorated cursor" with varr(1) at select item position 1
dbms_sql.return_result(c1);
END;
You can do this in plain SQL; it's not pretty, but it does work. You would prefer that Oracle had syntax to hide this from the programmer (and perhaps it does, at least in the most recent versions; I am still stuck at 12.2).
select t.id, q.array_element
from tbl t cross apply
( select column_value as array_element,
rownum as ord
from table(the_array)
) q
where ord = 1
;
EDIT If order of generating the elements through the table operator is a concern, you could do something like this (in Oracle 12.1 and higher; otherwise the function can't be part of the query itself, but it can be defined on its own):
with
function select_element(arr smallintarray, i integer)
return number
as
begin
return arr(i);
end;
select id, select_element(the_array, 1) as the_array_1
from tbl
/
First of all, please don't do that on production. Use tables instead of storing arrays within a table.
Answer to your question is to use column as a table source
SELECT t.id, ta.*
from tbl t,
table(t.THE_ARRAY) ta
order by column_value
-- offset 1 row -- in case if sometime you'll need to skip a row
fetch first 1 row only;
UPD: as for ordering the array I can only say playing with 2asc/desc" parameters provided me with results I've expected - it has been ordered ascending or descending.
UPD2: found a cool link to description of performance issues might happen

Oracle CLOB column and LAG

I'm facing a problem when I try to use LAG function on CLOB column.
So let's assume we have a table
create table test (
id number primary key,
not_clob varchar2(255),
this_is_clob clob
);
insert into test values (1, 'test1', to_clob('clob1'));
insert into test values (2, 'test2', to_clob('clob2'));
DECLARE
x CLOB := 'C';
BEGIN
FOR i in 1..32767
LOOP
x := x||'C';
END LOOP;
INSERT INTO test(id,not_clob,this_is_clob) values(3,'test3',x);
END;
/
commit;
Now let's do a select using non-clob columns
select id, lag(not_clob) over (order by id) from test;
It works fine as expected, but when I try the same with clob column
select id, lag(this_is_clob) over (order by id) from test;
I get
ORA-00932: inconsistent datatypes: expected - got CLOB
00932. 00000 - "inconsistent datatypes: expected %s got %s"
*Cause:
*Action:
Error at Line: 1 Column: 16
Can you tell me what's the solution of this problem as I couldn't find anything on that.
The documentation says the argument for any analytic function can be any datatype but it seems unrestricted CLOB is not supported.
However, there is a workaround:
select id, lag(dbms_lob.substr(this_is_clob, 4000, 1)) over (order by id)
from test;
This is not the whole CLOB but 4k should be good enough in many cases.
I'm still wondering what is the proper way to overcome the problem
Is upgrading to 12c an option? The problem is nothing to do with CLOB as such, it's the fact that Oracle has a hard limit for strings in SQL of 4000 characters. In 12c we have the option to use extended data types (providing we can persuade our DBAs to turn it on!). Find out more.
Some of the features may not work properly in SQL when using CLOBs(like DISTINCT , ORDER BY GROUP BY etc. Looks like LAG is also one of them but, I couldn't find anywhere in docs.
If your values in the CLOB columns are always less than 4000 characters, you may use TO_CHAR
select id, lag( TO_CHAR(this_is_clob)) over (order by id) from test;
OR
convert it into an equivalent SELF JOIN ( may not be as efficient as LAG )
SELECT a.id,
b.this_is_clob AS lagging
FROM test a
LEFT JOIN test b ON b.id < a.id;
Demo
I know this is an old question, but I think I found an answer which eliminates the need to restrict the CLOB length and wanted to share it. Utilizing CTE and recursive subqueries, we can replicate the lag functionality with CLOB columns.
First, let's take a look at my "original" query:
WITH TEST_TABLE AS
(
SELECT LEVEL ORDER_BY_COL,
TO_CLOB(LEVEL) AS CLOB_COL
FROM DUAL
CONNECT BY LEVEL <= 10
)
SELECT tt.order_by_col,
tt.clob_col,
LAG(tt.clob_col) OVER (ORDER BY tt.order_by_col)
FROM test_table tt;
As expected, I get the following error:
ORA-00932: inconsistent datatypes: expected - got CLOB
Now, lets look at the modified query:
WITH TEST_TABLE AS
(
SELECT LEVEL ORDER_BY_COL,
TO_CLOB(LEVEL) AS CLOB_COL
FROM DUAL
CONNECT BY LEVEL <= 10
),
initial_pull AS
(
SELECT tt.order_by_col,
LAG(tt.order_by_col) OVER (ORDER BY tt.order_by_col) AS PREV_ROW,
tt.clob_col
FROM test_table tt
),
recursive_subquery (order_by_col, prev_row, clob_col, prev_clob_col) AS
(
SELECT ip.order_by_col, ip.prev_row, ip.clob_col, NULL
FROM initial_pull ip
WHERE ip.prev_row IS NULL
UNION ALL
SELECT ip.order_by_col, ip.prev_row, ip.clob_col, rs.clob_col
FROM initial_pull ip
INNER JOIN recursive_subquery rs ON ip.prev_row = rs.order_by_col
)
SELECT rs.order_by_col, rs.clob_col, rs.prev_clob_col
FROM recursive_subquery rs;
So here is how it works.
I create the TEST_TABLE, this really is only for the example as you should already have this table somewhere in your schema.
I create a CTE of the data I want to pull, plus a LAG function on the primary key (or a unique column) in the table partitioned and ordered in the same way I would have in my original query.
Create a recursive subquery using the initial row as the root and descending row by row joining on the lagged column. Returning both the CLOB column from the current row and the CLOB column from its parent row.

Function results column names to be used in select statement

I have function which returns column names and i am trying to use the column name as part of my select statement, but my results are coming as column name instead of values
FUNCTION returning column name:
get_col_name(input1, input2)
Can И use this query to the results of the column from table -
SELECT GET_COL_NAME(input1,input2) FROM TABLE;
There are a few ways to run dynamic SQL directly inside a SQL statement. These techniques should be avoided since they are usually complicated, slow, and buggy. Before you do this try to find another way to solve the problem.
The below solution uses DBMS_XMLGEN.GETXML to produce XML from a dynamically created SQL statement, and then uses XML table processing to extract the value.
This is the simplest way to run dynamic SQL in SQL, and it only requires built-in packages. The main limitation is that the number and type of columns is still fixed. If you need a function that returns an unknown number of columns you'll need something more powerful, like the open source program Method4. But that level of dynamic code gets even more difficult and should only be used after careful consideration.
Sample schema
--drop table table1;
create table table1(a number, b number);
insert into table1 values(1, 2);
commit;
Function that returns column name
create or replace function get_col_name(input1 number, input2 number) return varchar2 is
begin
if input1 = 0 then
return 'a';
else
return 'b';
end if;
end;
/
Sample query and result
select dynamic_column
from
(
select xmltype(dbms_xmlgen.getxml('
select '||get_col_name(0,0)||' dynamic_column from table1'
)) xml_results
from dual
)
cross join
xmltable
(
'/ROWSET/ROW'
passing xml_results
columns dynamic_column varchar2(4000) path 'DYNAMIC_COLUMN'
);
DYNAMIC_COLUMN
--------------
1
If you change the inputs to the function the new value is 2 from column B. Use this SQL Fiddle to test the code.

Error with distinct, oracle and CLOB in Grails

I have an application written in grails 2.2.5 that needs to connect with MySQL, Oracle and SQL Server depending on my customers. We have more than 1000 queries that uses distinct returning instances of classes.
Example:
import br.com.aaf.auditoria.*
def query="select distinct tipo from Atividade c join c.tipoAtividade tipo order by tipo.nome"
def ret=Atividade.executeQuery(query)
So far so good, but now I need to include some CLOBs columns in oracle to expand some fields from VarChar 4000. When I do that these queries stop working because of the problem that Oracle does not compare CLOB columns.
Error:
ORA-00932: inconsistent datatypes: expected - got CLOB
I understand that Grails/Hibernate uses all properties of the domain class to make the sql to send to the database and return as an instance of that class.
The case is that I only need to compare or group the id of the domain class to make a distinct, but I need the result to be an instance of the class and not the id, so I don´t need to change all the queries.
Any of you know a way to change the behaviour of a distinct in HQL even if I need to customize a dialect to capture what Hibernate is doing in transforming HQL in SQL?
What I´m thinking is capture the SQL, change it to return and group only the id of the instance and execute a "get" in the Domain class before return this to "executeQuery".
The solution only fits to oracle db. You have to grant some privileges to your schema. "Create types" and " execute on DBMS_CRYPTO"
create table clob_test (id number, lob clob);
insert all
into clob_test values(1,'AAAAAAAA')
into clob_test values (2,'AAAAAAAA')
into clob_test values(3,'BBBBBBBB')
into clob_test values(4,'BBBBBBBB')
select * from dual;
commit;
CREATE OR REPLACE
type wrap_lob as object(
lob clob,
MAP MEMBER FUNCTION get_hash RETURN RAW
)
;
/
CREATE OR REPLACE
TYPE BODY wrap_lob is
MAP MEMBER FUNCTION get_hash RETURN RAW is
begin
return DBMS_CRYPTO.HASH(lob,1);
end;
end;
/
select tab.dist_lob.lob from (select distinct wrap_lob(lob) dist_lob from clob_test) tab;

Insert content of table variable into table

I have the following table, two types based on it, and a function that reads from this table:
CREATE TABLE myTable (
ID RAW(16) NULL,
NAME NVARCHAR2(200) NULL,
ENTITYID RAW(16) NOT NULL
);
CREATE TYPE myRowType AS OBJECT (
NAME NVARCHAR2(200),
ENTITYID RAW(16)
);
CREATE TYPE myTableType IS TABLE OF myRowType;
CREATE FUNCTION myFunction(...) RETURN myTableType ...
As you can see, the type myRowType is similar to myTable, but not exactly.
My goal is to insert rows into myTable based on the results of myFunction.
The naive approach would be to just write:
INSERT INTO myTable(ID, NAME, ENTITYID)
SELECT sys_guid(), NAME, ENTITYID
FROM TABLE(myFunction(...));
But since myFunction reads from myTable, this leads to the following error:
ORA-04091: table myTable is mutating, trigger/function may not see it
So I have to split the myFunction call from the insert statement. I tried it like this:
DECLARE
tbl myTableType;
BEGIN
SELECT myRowType(x.NAME, x.ENTITYID)
BULK COLLECT INTO tbl
FROM TABLE(myFunction(...)) x;
INSERT INTO myTable
(ID, NAME, ENTITYID)
SELECT sys_guid(), x.NAME, x.ENTITYID
FROM tbl x;
END;
But here, Oracle doesn't seem to understand the FROM tbl clause. It shows the error
ORA-00942: table or view does not exist
How can I insert the rows in tbl into myTable?
Since you can't use a locally defined nested table as an argument for TABLE function, maybe you would consider using the FORALL bulk insert? I see you are using Oracle 11g, so you will be able to access fields of myRowType. You would then replace your INSERT from your PL/SQL block with this:
FORALL v_i IN tbl.FIRST..tbl.LAST
INSERT INTO myTable VALUES (sys_guid(), tbl(v_i).name, tbl(v_i).entityid);
I recommend this great article by Tim Hall: BULK COLLECT & FORALL

Resources