How to call a Snowflake UDF in a Select query - user-defined-functions

I have created a UDF function to lookup a reference table
CREATE OR REPLACE function udf_rdm_lookup_func_test(
src_system VARCHAR,
src_code VARCHAR,
src_value varchar)
returns array
as
$$
SELECT DISTINCT DESC FROM rdm WHERE SOURCE_CODE = SRC_CODE AND SOURCE_VALUE = SRC_VALUE AND SOURCE_SYSTEM = SRC_SYSTEM
$$
;
i am able to call the UDF in a simple SELECT query
select udf_rdm_lookup_func_test('XYZ','LOBCode','001');
but i want to use this as a lookup in while reading data from another table
SELECT DISTINCT COMPANY_CODE,SRC_SYSTEM_ID,
udf_rdm_lookup_func_test(SRC_SYSTEM_ID,'LOBCode',COMPANY_CODE)[2]
FROM ABC;
Upon running that i am getting an error
SQL Error [2031] [42601]: SQL compilation error:¶Unsupported subquery
type cannot be evaluated
Want to know how to create a function that can be used in a SELECT statement while reading data from another table? Please help

I think you can get around this by enclosing the returned value with any_value(). For example:
CREATE OR REPLACE function udf_rdm_lookup_func_test(
src_system VARCHAR,
src_code VARCHAR,
src_value varchar)
returns array
as
$$
SELECT DISTINCT any_value(DESC) FROM rdm WHERE SOURCE_CODE = SRC_CODE AND SOURCE_VALUE = SRC_VALUE AND SOURCE_SYSTEM = SRC_SYSTEM
$$
;

Your query is returning more than 1 value. The suggestion to leverage a table function would handle this, but if you want to return it as a single value array, then you actually have to create the SQL statement to return that. Try using an ARRAY_AGG() function in your SELECT.
https://docs.snowflake.net/manuals/sql-reference/functions/array_agg.html

Related

Can a type table of table%rowtype be indexed by some field of table%rowtype?

Why to I asked this question?
I have a table which as key that have a lot of field. Every time, I'm making a jointure, I miss a field. Therefore I have defined a pipelined function that take the key as an argument so that I am sure that I get only one element when I'm doing a jointure.
But the query take more time now. The table a has an index on some fields but not the table type used by pipelined function. I would like to know if it is possible to created a index on some fields of the table%rowtype
code:
create table a ( a1 integer);
create package p_a
as
type t_a iS TABLE of a%ROWTYPE;
function f(i_a1 integer) return t_a pipelined;
end;
CREATE PACKAGE BODY p_a
AS
CURSOR c_A (i_a1 INTEGER)
RETURN a%ROWTYPE
IS
SELECT t.*
FROM a t
WHERE t.a1 = i_a1;
FUNCTION f (i_a1 INTEGER)
RETURN t_a
PIPELINED
IS
BEGIN
FOR c IN c_a (i_a1)
LOOP
PIPE ROW (c);
END LOOP;
END;
END;
with b as( select 1 b1 from dual) select * from b cross apply (table(p_a.f(b.b1)));
the question
I've tried to index the type table by a field of a table like this
create table a ( a1 integer);
create package p_a2
as
type t_a iS TABLE of a%ROWTYPE index by a.a1%type;
function f(i_a1 integer) return t_a pipelined;
end;
PLS-00315: Implementation restriction: unsupported table index type
Is what I want to do possible. If not how to solve the performance problems mentioned in the introduction?
code
A TYPE is NOT a table and cannot be indexed.
When you do:
create package p_a
as
type t_a iS TABLE of a%ROWTYPE;
end;
/
You are defining a type and the type is a collection data type; an instance of that type is NOT a physical table and but is more like an in-memory array.
When you create a PIPELINED function:
function f(i_a1 integer) return t_a pipelined;
It does NOT return a table; it returns the collection data type.
When you do:
type t_a iS TABLE of a%ROWTYPE index by a.a1%type;
You are NOT creating an index on a table; you are changing to a different collection data type that is an associative array (like a JavaScript object or a Python dictionary) that stores key-value pairs.
An associative array is a PL/SQL data type and (with limited exceptions in later versions for insert, update and delete statements) cannot be used in SQL statements.
When you do:
SELECT * FROM TABLE(SYS.ODCIVARCHAR2LIST('a', 'b', 'c'));
or:
SELECT * FROM TABLE(p_a.f(1));
Then you are passing a collection data type to an SQL statement and the table collection expression TABLE() is treating the collection expression as if it was a table. It is still NOT a table.
If you want to use an index on the table then use the table (without a cursor or a pipeline function):
WITH b (b1) AS (
SELECT 1 FROM DUAL
)
SELECT *
FROM b
CROSS APPLY (
SELECT a.*
FROM a
WHERE a.a1 = b.b1;
);
I think the first line of your question says it all: "key that have a lot of field". If I understand correctly, the table has a primary key that consists of a large number of columns and because of that writing queries becomes a challenge.
It sounds like you're trying to do something pretty complex that should not be an issue at all.
Take a step back and ask yourself - does this need to be the primary key of the table ? Or can you use a surrogate key (identity column, sequence), use that as the primary key and just create a unique index on the set of field that currently make up the primary key. It will (1) simplify your data model and (2) make writing the queries a lot easier.

PL/SQL reusable dynamic sql program for same type of task but different table and column

Thank you for reply guys. I kind of solved my problem.
I used to try to update data with ref cursor in dynamic SQL using "where current of" but I now know that won't work.
Then I tried to use %rowtype to store both 'id' and 'clob' in one variable for future updating but turns out weak ref cursor can't use that type binding either.
After that I tried to use record as return of an ref cursor and that doesn't work on weak cursor either.
On the end, I created another cursor to retrieve 'id' separately along with cursor to retrieve 'clob' on the same time then update table with that id.
I'm now working on a Oracle data cleaning task and have a requirement like below:
There are 38 tables(maybe more in the future) and every table has one or multiple column which type is Clob. I need to find different keyword in those columns and according to a logic return binary label of the column and store it in a new column.
For example, there is a table 'myTable1' which has 2 Clob columns 'clob1' and 'clob2'. I'd like to find keyword 'sky' from those columns and store '0'(if not found) or '1'(if found) in two new columns 'clob1Sky','clob2Sky'.
I know if I could write it on a static way which will provide higher efficiency but I have to modify it for those very similar tasks every time. I want save some time on this so I'm trying to write it in a reusable way and not binding to certain table.
But I met some problem when writing the program. My program is like below:
create or replace PACKAGE body LABELTARGETKEYWORD
as
/**
#param varcher tableName: the name of table I want to work on
#param varchar colName: the name of clob column
#param varchar targetWord: the word I want to find in the column
#param varchar newColName: the name of new column which store label of clob
*/
PROCEDURE mainProc(tableName varchar, colName varchar,targetWord varchar,newColName varchar2)
as
type c_RecordCur is ref cursor;
c_sRecordCur c_recordCur;
/*other variables*/
begin
/*(1) check whether column of newColName exist
(2) if not, alter add table of newColName
(3) open cursor for retrieving clob
(4) loop cursor
(5) update set the value in newColName accroding to func labelword return
(6) close cursor and commit*/
end mainProc;
function labelWord(sRecord VARCHAR2,targetWord varchar2) return boolean...
function ifColExist(tableName varchar2,newColName varchar2) return boolean...
END LABELTARGETKEYWORD;
Most DML and DDL are written in dynamic sql way.
The problem is when I write the (5) part, I notice 'Where current of' clause can not be used in a ref cursor or dynamic sql statement. So I have to change the plan.
I tried to use a record(rowid,label) to store result and alter the table later.(the table only be used by two people in my group, so there won't be problem of lock and data changes). But I find because I'm trying to use dynamic sql so actually I have to define ref cursor with return of certain %rowtype and basically all other variables, %type in dynamic sql statement. Which makes me feel my method has something wrong.
My question are:
If there a way to define %type in dynamic sql? Binding type to variable in dynamic SQL?
Could anybody give me a hint how to write that (5) part in dynamic SQL?
Should not I design my program like that?
Is it not the way how to use dynamic SQL or PLSQL?
I'm very new to PL/SQL. Thank you very much.
According to Tom Kyte's advice, to do it in one statement if it can be done in one statement, I'd try to use a single UPDATE statement first:
CREATE TABLE mytable1 (id NUMBER, clob1 CLOB,
clob2 CLOB, clob1sky NUMBER, clob2sky NUMBER )
LOB(clob1, clob2) STORE AS SECUREFILE (ENABLE STORAGE IN ROW);
INSERT INTO mytable1(id, clob1, clob2)
SELECT object_id, object_name, object_type FROM all_objects
WHERE rownum <= 10000;
CREATE OR REPLACE
PROCEDURE mainProc(tableName VARCHAR2, colName VARCHAR2, targetWord VARCHAR2, newColName VARCHAR2)
IS
stmt VARCHAR2(30000);
BEGIN
stmt := 'UPDATE '||tableName||' SET '||newColName||'=1 '||
'WHERE DBMS_LOB.INSTR('||colName||','''||targetWord||''')>1';
dbms_output.put_line(stmt);
EXECUTE IMMEDIATE stmt;
END mainProc;
/
So, calling it with mainProc('MYTABLE1', 'CLOB1', 'TAB', 'CLOB1SKY'); fires the statement
UPDATE MYTABLE1 SET CLOB1SKY=1 WHERE DBMS_LOB.INSTR(CLOB1,'TAB')>1
which seems to do the trick:
SELECT * FROM mytable1 WHERE clob1sky=1;
id clob1 clob2 clob1sky clob2skiy
33 I_TAB1 INDEX 1
88 NTAB$ TABLE 1
89 I_NTAB1 INDEX 1
90 I_NTAB2 INDEX 1
...
I am not sure with your question-
If this job is suppose to run on daily or hourly basis ,running query through it will be very costly. One thing you can do - put all your clob data in a file and save it in your server(i guess it must be linux). then you can create a shell script and schedule a job to run gerp command and fetch your required value and "if found then update your table".
I think you should approaches problem another way:
1. Find all columns that you need:
CURSOR k_clobs
select table_name, column_name from dba_tab_cols where data_type in ('CLOB','NCLOB');
Or 2 cursor(you can build you query if you have more than 1 CLOB per table:
CURSOR k_clobs_table
select DISTINCT table_name from dba_tab_cols where data_type in ('CLOB','NCLOB');
CURSOR k_clobs_columns(table_namee varchar(255)) is
select column_name from dba_tab_cols where data_type in ('CLOB','NCLOB') and table_name = table_namee;
Now you are 100% that column you are checking is clob, so you don't have to worry about data type ;)
I'm not sure what you want achieve, but i hope it may help you.

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.

SELECT from a bulk collection

Is it possible to select from a bulk collection?
Something along these lines:
DECLARE
CURSOR customer_cur IS
SELECT CustomerId,
CustomerName
FROM Customers
WHERE CustomerAreaCode = '576';
TYPE customer_table IS TABLE OF customer_cur%ROWTYPE;
my_customers customer_table;
BEGIN
OPEN customer_cur;
FETCH customer_cur
BULK COLLECT INTO my_customers;
-- This is what I would like to do
SELECT CustomerName
FROM my_customers
WHERE CustomerId IN (1, 2, 3);
END;
I don't seem to be able to select from the my_customers table.
Yes, you can. Declare yourself schema-level types as follows:
create or replace rec_customer_cur
as
object (
customerid integer, -- change to the actual type of customers.customerid
customername varchar2(100) -- change to the actual type of customers.customername
);
/
create or replace type customer_table
as
table of rec_customer_cur;
/
Then, in your PLSQL code, you can declare
CURSOR customer_cur IS
SELECT new rec_customer_cur(CustomerId, CustomerName)
FROM Customers
WHERE CustomerAreaCode = '576';
... and then use ...
SELECT CustomerName
INTO whatever
FROM table(my_customers)
WHERE CustomerId IN (1, 2, 3);
This is because schema-level types can be used in SQL context.
If you want to also display the dataset returned by the select, then just use a REF CURSOR as an OUT parameter.
The SELECT ...FROM TABLE is a SQL statement, which needs a STATIC TABLE NAME, as a database object. It throws an error since the collection name is not actually a database table as an object.
To return the dataset, use SYS_REFCURSOR as OUT parameter.
open cur as select....

how to use oracle package to get rid of Global Temp table

I have a sample query like below:
INSERT INTO my_gtt_1 (fname, lname) (select fname, lname from users)
In my effort to getting rid of temporary tables I created a package:
create or replace package fname_lname AS
Type fname_lname_rec_type is record (
fname varchar(10),
lname varchar(10)
);
fname_lname_rec fname_lname_rec_type
Type fname_lname_tbl_type is table of fname_lname_rec_type;
function fname_lname_func
(
v_fnam in varchar2,
v_lname in varchar2
)return fname_lname_tbl_type pipelined;
being new to oracle...creating this package took a long time. but now I can not figure out how to get rid of the my_gtt_1
how can i say...
INSERT INTO <newly created package> (select fnma, name from users)
You need to call the pipelined function using the TABLE() syntax:
select *
from table (select fname_lname.fname_lname_func(fnma, name)
from users
where user_id = 123 )
/
Note that the sub-query on USERS must return a single row from that table.
You don't select into packages. You could declare a variable of your table type and bulk collect into that, if you intend to use it in code. I also question your need for a pipelined function. If you're just using the global-temporary table as a springboard for another query, you could probably just use a WITH clause instead. We need a better idea of the bigger picture to recommend a particular technique. Global temporary tables are not inherently bad, either.

Resources