I would like to extract a function from a piece of SQL-code which is used multiple times in one query. I'm looking for a functionality which is similar to the following (invented by me) syntax:
with f(x) as (return x+1)
select f(thing1), f(thing2), f(thing3) from things
thing1, thing2, thing3 are integer columns in the table "things" in the example. Also, imagine that f is more complicated than an add-one function.
How do I define a function inside a query?
Declaration of a function in the WITH clause of a query is not possible but according to the information presented at OOW it will be in 12c version. So for now you need to create a function as a schema object whether it would be a stand-alone function or part of a package. For example:
create or replace function F(p_p in number)
return number
is
begin
return p_p + 1;
end;
And then call it in a query, ensuring that the data type of a column you are passing in to the function as a parameter is of the same data type as the parameter of the function:
select f(col1)
, f(col2)
, ...
, f(coln)
from your_table
Are you trying to build a function for dynamic table and table's columns, I would do some code like this? And you cannot declare a function in the WITH clause of a query.
SELECT f ( tablename,columnname1 ),
f ( tablename,columnname2 ),
........
FROM tablename;
Create or replace function f (tableName varchar2,ColumnName varchar2)
Return somethingHere
Is
varTableName varchar2(200);
varColumnName varchar2(200);
varValue integer;
t_cid INTEGER;
t_command VARCHAR2(200);
Begin
--Get tableName
varTableName := tableName ;
--Get columnName
varColumnName := ColumnName ;
t_command := 'SELECT ' || varColumnName ||' FROM ' || varTableName;
--Here execute dynamic sql statement
DBMS_SQL.PARSE
DBMS_SQL.DEFINE_COLUMN
DBMS_SQL.EXECUTE
--fatch row values into varValue
DBMS_SQL.COLUMN_VALUE (..,..,varValue);
--then do your x+1 magic here
varValue := varValue+1
--then output your value.
End;
Related
Using Oracle SQL...
I'd like to use the replace function on all values of a hardcoded list. Something like this...
WHERE (
(field IN replace(('PREFIX-ITEM0','PREFIX-ITEM1','PREFIX-ITEM2','PREFIX-ITEM3','PREFIX-ITEM4','PREFIX-ITEM5','PREFIX-ITEM6','PREFIX-ITEM7','PREFIX-ITEM8','PREFIX-ITEM9'),'PREFIX-','NEWBIE-'))
)
Is there any way to do this?
If you have a fixed list of values - so they aren't coming from a table - then it would be much simpler to modify them externally before putting them into a query.
But if for some reason you can't do that, you could treat the list as a varray of strings, and use a function to replace them individually. In recent versions of Oracle you can do that with a local function defined in a with clause:
with
function myfunc (p_in_list sys.odcivarchar2list, p_old varchar2, p_new varchar2)
return sys.odcivarchar2list is
l_out_list sys.odcivarchar2list;
begin
l_out_list := new sys.odcivarchar2list();
l_out_list.extend(p_in_list.count);
for i in p_in_list.first..p_in_list.last loop
l_out_list(i) := replace(p_in_list(i), p_old, p_new);
end loop;
return l_out_list;
end;
select field
from t
where field in (
select * from myfunc(
sys.odcivarchar2list('PREFIX-ITEM0','PREFIX-ITEM1','PREFIX-ITEM2','PREFIX-ITEM3','PREFIX-ITEM4','PREFIX-ITEM5','PREFIX-ITEM6','PREFIX-ITEM7','PREFIX-ITEM8','PREFIX-ITEM9'),
'PREFIX-',
'NEWBIE-')
)
/
which with some dummy data with a mix of PREFIX and NEWBIE values produces:
FIELD
------------
NEWBIE-ITEM2
NEWBIE-ITEM3
NEWBIE-ITEM4
db<>fiddle
I group a series of IDs using LISTAGG.
Here is an example of a returned value-
A 1,2,3,4,5 PROC.ABC
B 6,7,8 PROC.ABC
C 2,3,4 PROC.DEF
I then try to use a cursor and pass each value into the following procedure:
PROCEDURE abc(id_list IN VARCHAR2) IS
BEGIN
UPDATE table_a SET flag = 1 WHERE id IN (id_list);
END;
This errors out ("Invalid Number" error) because id_list is being inserted as
'1,2,3,4,5'
, not
1,2,3,4,5
. How can I get this to work? I would prefer not to use dynamic SQL if possible.
If you have APEX installed in your database, you can use the APEX_STRING.SPLIT function to split your string by commas. If you know id_list is always going to be numbers, you can use APEX_STRING.SPLIT_NUMBERS as well.
PROCEDURE abc (id_list IN VARCHAR2)
IS
BEGIN
UPDATE table_a
SET flag = 1
WHERE id IN (SELECT * FROM TABLE (apex_string.split (id_list, ',')));
END;
Firstly you need to create a function that returns a list of values.
FUNCTION TVF_SPLIT (expression CLOB, delimiter CHAR) RETURN sys.ODCIVARCHAR2LIST
AS
v_TYPE_TABLE_SEPRATOR sys.ODCIVARCHAR2LIST :=sys.ODCIVARCHAR2LIST();
v_xml_data CLOB;
BEGIN
v_xml_data := '<r><n>' || replace(expression,delimiter,'</n><n>') || '</n></r>';
SELECT * BULK COLLECT INTO v_TYPE_TABLE_SEPRATOR FROM
(
SELECT * FROM
XMLTABLE ( '/r/*'
PASSING xmltype(v_xml_data)
COLUMNS
r VARCHAR2(4000) PATH '/n'
) xmlt
);
RETURN v_TYPE_TABLE_SEPRATOR;
END;
Select * from table(TVF_SPLIT('1,2,3,4,5',','));
Don't use listagg; create a collection type.
create or replace type t_vchar_tab as table of integer
Then replace listagg in the query-
CAST(COLLECT(id) AS t_vchar_tab) as id_list
Then the procedure should reference the collection w/use of MEMBER
PROCEDURE abc(id_list t_vchar_tab) IS
BEGIN
UPDATE table_a SET flag = 1 WHERE id MEMBER OF id_list;
END;
I have PL/SQL function which is dynamically creating select statement an return this statement as varchar.Because I need this statement work dynamically(each time return different column count/name).For example it can return this select
'select id,name,currency,note from tabel t where t.id in(1,2,3,4,5,6);'
And I have another function must use this select statement result.
But the second select statement return that this string and cannot execute this select statement.
How can make first function return result as sql ?
Provided the caller knows the structure of the result:
CREATE OR REPLACE PROCEDURE execute_query(query IN VARCHAR2)
TYPE cur_typ IS REF CURSOR;
c cur_typ;
ID NUMBER;
Name VARCHAR2(20);
Currency VARCHAR2(20);
Note VARCHAR2(200);
BEGIN
OPEN c FOR query;
LOOP
FETCH c INTO ID, Name, Currency, Note;
EXIT WHEN c%NOTFOUND;
....
END LOOP;
CLOSE c;
END;
/
Use
EXECUTE IMMEDIATE Statement
as said in documentation:
https://docs.oracle.com/cd/B13789_01/appdev.101/b10807/13_elems017.htm
I am familiar with MSSQL and using a parameter within the query, but I am not sure how I would do this within PL/SQL.
DECLARE
LSITEID NUMBER := 100001;
BEGIN
SELECT * from invoicehead ih
JOIN sitemaster sm on sm.SITEIID = ih.SITEIID
JOIN invoiceline il on il.invoiceIID = ih.invoiceIID
WHERE
ih.StartDate BETWEEN '2015-12-01' AND '2016-03-07'
AND SITEIID IN ( LSITEID)
END;
Right now I am testing this within Pl/SQL. But essentially I would be passing in the query with the parameter from MSSQL Linked Server OPENQuery.
How I can run the above query in PL/SQL with the parameter?
There is plenty of other resource for finding an answer, e.g. here (Tutorialspoint) or specifically here (plsql-tutorial). But perhaps I have missed your point.
To not remain on merely citing links, your query could look like this:
DECLARE
LSITEID integer;
BEGIN
LSITEID := 100001;
-- dostuff
END;
Two things to note: First, in a declare part (as I have learnt it) you should avoid assigning values. Second, if you intend to pass in different parameters you could/should use a procedure.
In PL/SQL you just use the name of the argument. In the following example, the argument is P_VALUE, note select statement says where dummy = p_value.
DECLARE
FUNCTION dummycount (p_value IN DUAL.dummy%TYPE)
RETURN INTEGER
AS
l_ret INTEGER;
BEGIN
SELECT COUNT (*) c
INTO l_ret
FROM DUAL
WHERE dummy = p_value;
RETURN l_ret;
END dummycount;
BEGIN
DBMS_OUTPUT.put_line ('A: ' || dummycount (p_value => 'A'));
DBMS_OUTPUT.put_line ('X: ' || dummycount (p_value => 'X'));
END;
This results in the following output:
A: 0
X: 1
I created a function that should return the max id from a table(parameter)
CREATE OR REPLACE FUNCTION getmaxid
(
P_TABLE IN VARCHAR2
)
RETURN NUMBER IS
v_maxId NUMBER(38);
BEGIN
SELECT MAX(id) INTO v_maxId FROM P_TABLE;
RETURN v_maxId;
END getmaxid
However, i keep getting the error message "ORA-00942: table or view does not exist" on this line:
SELECT MAX(id) INTO v_maxId FROM P_TABLE;
Like explained earlier, you need to use dynamic SQL to perform the operation. In this case, p_table is a variable. The solution to this is to build a string that will contain the SQL and dynamically execute it one you've build the query.
The example below uses, DUAL, but the table name is arbitrary.
Here is what you're looking for, take the function outside of the block, I left it like this so that you can test it..
DECLARE
FUNCTION getmaxid (p_table IN VARCHAR2)
RETURN NUMBER
IS
v_maxid NUMBER (38);
v_select VARCHAR2 (200);
cnt SYS_REFCURSOR;
BEGIN
v_select := 'SELECT COUNT(*) FROM ' || p_table;
DBMS_OUTPUT.put_line (v_select);
EXECUTE IMMEDIATE v_select INTO v_maxid;
RETURN v_maxid;
END getmaxid;
BEGIN
DBMS_OUTPUT.put_line (getmaxid ('DUAL'));
END;