Syntax for SQL function, in variables and return - sql

I have a sql function called task2.sql:
-- script to create function task2
CREATE OR REPLACE Function task2
(input_userid IN integer)
begin
return (select *
from tweet twt
where twt.userid = input_userid
order by publishtime desc, tweetid desc);
END;
/
exit;
I'm trying to return all the rows with the goal being to write them to a text file. I'm using a java file to call this function and to manage the results. I am messing up the syntax somewhere. Any help is appreciated.

Oracle functions can't do this in such way. For returning rows from function you should use Table Function (with pipeline may be)
http://docs.oracle.com/cd/B19306_01/appdev.102/b14289/dcitblfns.htm#CHDCIEJG
But, for wrapping query into procedure i can reccomend you return SYS_REFCURSOR
CREATE OR REPLACE PROCEDURE task2
(input_userid IN integer, out_cur OUT SYS_REFCURSOR)
begin
OPEN out_cur FOR select *
from tweet twt
where twt.userid = input_userid
order by publishtime desc, tweetid desc;
END;
Then, you should fetch rows from out_cur in your application. It's easy and google and SF full of this examples. For example read data from SYS_REFCURSOR in a Oracle stored procedure and reuse it in java

Related

Can I create a "table-valued function" in an Oracle package?

I'm relatively new to the Oracle world. Most of my experience is with SQL Server.
I am writing code that would benefit from a "parameterized view", aka a "table-valued function" (tvf) in SQL Server.
I found a good example here that I'm trying to follow: Oracle: Return a «table» from a function
But I need mine to be inside a package, and I'm having a devil of a time with it.
Here's an example of what I'm trying:
CREATE OR REPLACE PACKAGE pkg_test_oracle_tvfs IS
TYPE t_tvf_row IS RECORD(
i NUMBER,
n VARCHAR2(30));
TYPE t_tvf_tbl IS TABLE OF t_tvf_row INDEX BY BINARY_INTEGER;
FUNCTION fn_get_tvf(p_max_num_rows INTEGER) RETURN t_tvf_tbl;
END pkg_test_oracle_tvfs;
CREATE OR REPLACE PACKAGE BODY pkg_test_oracle_tvfs IS
FUNCTION fn_get_tvf(p_max_num_rows INTEGER) RETURN t_tvf_tbl IS
v_tvf_tbl t_tvf_tbl;
BEGIN
SELECT pkg_test_oracle_tvfs.t_tvf_row(rownum,
uo.object_name)
BULK COLLECT
INTO v_tvf_tbl
FROM user_objects uo
WHERE rownum <= p_max_num_rows;
RETURN v_tvf_tbl;
END;
END pkg_test_oracle_tvfs;
With the intent that I can do something like:
SELECT * FROM pkg_test_oracle_tvfs.fn_get_tvf(5);
Or
SELECT * FROM TABLE(pkg_test_oracle_tvfs.fn_get_tvf(5));
(I'm unclear if the TABLE() is required.)
But when I compile the package I get:
Compilation errors for PACKAGE BODY XXX.PKG_TEST_ORACLE_TVFS
Error: PL/SQL: ORA-00913: too many values
Line: 11
Text: FROM user_objects uo
Error: PL/SQL: SQL Statement ignored
Line: 7
Text: SELECT pkg_test_oracle_tvfs.t_tvf_row(rownum,
What am I doing wrong here? Why does this syntax seem to work fine outside of a package but not inside one?
Do I need to use the "pipeline" style of constructing the table as described in Oracle: Pipelined PL/SQL functions If so, why is this example different than the one I've been trying to follow?
Thanks!
Your initial error is because you're selecting into a record type, not an object type, so you don't need the constructor:
SELECT rownum, uo.object_name
BULK COLLECT
INTO v_tvf_tbl
fiddle, which shows it now compiles, but you can't call it from SQL for the reason's MTO already explained.
As an alternative to creating an object type, you can as you suggested use a pipelined function, if you modify the collection type:
CREATE OR REPLACE PACKAGE pkg_test_oracle_tvfs IS
TYPE t_tvf_row IS RECORD(
i NUMBER,
n VARCHAR2(30));
TYPE t_tvf_tbl IS TABLE OF t_tvf_row;
FUNCTION fn_get_tvf(p_max_num_rows INTEGER) RETURN t_tvf_tbl PIPELINED;
END pkg_test_oracle_tvfs;
/
CREATE OR REPLACE PACKAGE BODY pkg_test_oracle_tvfs IS
FUNCTION fn_get_tvf(p_max_num_rows INTEGER) RETURN t_tvf_tbl PIPELINED IS
v_tvf_tbl t_tvf_tbl;
BEGIN
SELECT rownum, uo.object_name
BULK COLLECT
INTO v_tvf_tbl
FROM user_objects uo
WHERE rownum <= p_max_num_rows;
FOR i IN 1..v_tvf_tbl.COUNT LOOP
PIPE ROW (v_tvf_tbl(i));
END LOOP;
RETURN;
END;
END pkg_test_oracle_tvfs;
/
SELECT * FROM pkg_test_oracle_tvfs.fn_get_tvf(5);
I
N
1
PKG_TEST_ORACLE_TVFS
2
PKG_TEST_ORACLE_TVFS
SELECT * FROM TABLE(pkg_test_oracle_tvfs.fn_get_tvf(5));
I
N
1
PKG_TEST_ORACLE_TVFS
2
PKG_TEST_ORACLE_TVFS
fiddle
There is a fundamental flaw; both RECORDs and associative arrays (TABLE OF ... INDEX BY ...) are PL/SQL only data types and cannot be used in SQL statements.
If you want to use a record-like and array-like data structure in an SQL statement then you will need to define it in the SQL scope which means that you cannot define it in a package and would need to use an OBJECT type and a nested-table collection type:
CREATE TYPE t_tvf_row IS OBJECT(
i NUMBER,
n VARCHAR2(30)
);
CREATE TYPE t_tvf_tbl IS TABLE OF t_tvf_row;
Then:
CREATE OR REPLACE PACKAGE pkg_test_oracle_tvfs IS
FUNCTION fn_get_tvf(
p_max_num_rows INTEGER
) RETURN t_tvf_tbl;
END pkg_test_oracle_tvfs;
/
CREATE OR REPLACE PACKAGE BODY pkg_test_oracle_tvfs IS
FUNCTION fn_get_tvf(
p_max_num_rows INTEGER
) RETURN t_tvf_tbl
IS
v_tvf_tbl t_tvf_tbl;
BEGIN
SELECT t_tvf_row(
rownum,
object_name
)
BULK COLLECT INTO v_tvf_tbl
FROM (
SELECT object_name
FROM user_objects
ORDER BY object_name
)
WHERE rownum <= p_max_num_rows;
RETURN v_tvf_tbl;
END;
END pkg_test_oracle_tvfs;
/
fiddle
why is this example different than the one I've been trying to follow?
Because you are defining data-types in a PL/SQL scope (a package) that can only be used in PL/SQL (because records and associative arrays are PL/SQL-only data types) and then trying to use them in an SQL scope (a SELECT statement). The example you are following defines the data types as an OBJECT and a non-associative array and defines them in the SQL scope (outside of a package) and then using them in an SQL statement is allowable.

PL/pgSQL function to return the output of various SELECT queries from different database

I have found this very interesting article: Refactor a PL/pgSQL function to return the output of various SELECT queries
from Erwin Brandstetter which describes how to return all columns of various tables with only one function:
CREATE OR REPLACE FUNCTION data_of(_table_name anyelement, _where_part text)
RETURNS SETOF anyelement AS
$func$
BEGIN
RETURN QUERY EXECUTE
'SELECT * FROM ' || pg_typeof(_table_name)::text || ' WHERE ' || _where_part;
END
$func$ LANGUAGE plpgsql;
Call:
SELECT * FROM data_of(NULL::tablename,'1=1 LIMIT 1');
This works pretty well. I need a very similar solution but for getting data from a table on a different database via dblink. That means the call NULL::tablename will fail since the table does not exists on the database where the call is made. I wonder how to make this work. Any try to connect inside of the function via dblink to a different database failed to get the result of NULL::tablename. It seems the polymorph function needs a polymorph parameter which creates the return type of the function implicit.
I would appreciate very much if anybody could help me.
Thanks a lot
Kind regards
Brian
it seems this request is more difficult to explain than I thought it is. Here is a second try with a test setup:
Database 1
First we create a test table with some data on database 1:
CREATE TABLE db1_test
(
id integer NOT NULL,
txt text
)
WITH (
OIDS=TRUE
);
INSERT INTO db1_test (id, txt) VALUES(1,'one');
INSERT INTO db1_test (id, txt) VALUES(2,'two');
INSERT INTO db1_test (id, txt) VALUES(3,'three');
Now we create the polymorph function on database 1:
-- create a polymorph function with a polymorph parameter "_table_name" on database 1
-- the return type is set implicit by calling the function "data_of" with the parameter "NULL::[tablename]" and a where part
CREATE OR REPLACE FUNCTION data_of(_table_name anyelement, _where_part text)
RETURNS SETOF anyelement AS
$func$
BEGIN
RETURN QUERY EXECUTE
'SELECT * FROM ' || pg_typeof(_table_name)::text || ' WHERE ' || _where_part;
END
$func$ LANGUAGE plpgsql;
Now we make test call if everything works as aspected on database 1
SELECT * FROM data_of(NULL::db1_test, 'id=2');
It works. Please notice I do NOT specify any columns of the table db1_test. Now we switch over to database 2.
Database 2
Here I need to make exactly the same call to data_of from database 1 as before and although WITHOUT knowing the columns of the selected table at call time. Unfortunatly this is not gonna work, the only call which works is something like that:
SELECT
*
FROM dblink('dbname=[database1] port=[port] user=[user] password=[password]'::text, 'SELECT * FROM data_of(NULL::db1_test, \'id=2\')'::text)
t1(id integer, txt text);
Conclusion
This call works, but as you can see, I need to specify at least once how all the columns look like from the table I want to select. I am looking for any way to bypass this and make it possible to make a call WITHOUT knowing all of the columns from the table on database 1.
Final goal
My final goal is to create a function in database 2 which looks like
SELECT * from data_of_dblink('table_name','where_part')
and which calls internaly data_of() on database1 to make it possible to select a table on a different database with a where part as parameter. It should work like a static view but with the possiblity to pass a where part as parameter.
I am extremly open for suggestions.
Thanks a lot
Brian

Oracle Stored Procedure into a View

I have a SP in oracle wich returns a cursor, before I had a query to create a viewn, but due to the complexity of the data I need, the better option to get it was a SP, but I strictly need the view with the information ( client's requirement), but now I have no idea how to put/convert the data (cursor) in the view, I was checking Global Temporary Tables, but that means I need to call the SP before accessing the view, and that's not possible. It's imperative that I call access the view with a select, Select * from view_data_sp, and obviously that the performance is not affected.
Any idea how can I achieve this?
Thanks
Consider using record type, table of records and pipe rows. Something like this would do the trick?
CREATE OR REPLACE package sysop as
type file_list_rec is record(filename varchar2(1024));
type file_list is table of file_list_rec;
function ls(v_directory varchar2) return file_list pipelined;
end;
/
CREATE OR REPLACE package body sysop as
function ls(v_directory varchar2) return file_list pipelined is
rec file_list_rec;
v_host_list varchar2(32000) := '';
begin
v_host_list := host_list(v_directory);
for file in (
select regexp_substr(v_host_list, '[^'||chr(10)||']+', 1, level)
from dual
connect by
regexp_substr(v_host_list, '[^'||chr(10)||']+', 1, level) is not null)
loop
pipe row (file);
end loop;
return;
end ls;
end sysop;
/

how to apply an aggregation function over a measure dynamically passed to a stored procedure?

I am trying to do spit by dimension on an analytic view within a stored procedure, and I want to pass the measure on which I will apply the aggregation function dynamically. So I did the following:
create procedure procHO (in currentMeasure varchar(60))
language sqlscript as
begin
data_tab = select MONTH_NAME as ID, sum(:currentMeasure) from
_SYS_BIC."schema/analyticView" GROUP BY MONTH_NAME;
end;
then I call the procedure this way:
call procHO("MARGIN");
but I am getting an error saying :
inconsistent datatype: only numeric type is available for aggregation function: line 5 col 38 (at pos 124) Could not execute 'call procHO("MARGIN")'
I also tried to do this using CE_ functions, here is what I did:
create procedure procHO1(in currentMeasure varchar(60))
language sqlscript as
begin
out1 = CE_OLAP_VIEW("schema/analyticView", ["MONTH_NAME",
SUM(:currentMeasure)]);
end;
and I call the procedure this way:
call procHO1("MARGIN");
but still, I am getting an error saying:
feature not supported: line 5 col 70 (at pos 157)
Could not execute 'call procHO1("MARGIN")'
by the way, as a workaround, it is possible to create a dynamic SQL query that would resolve the issue, here is an example:
create procedure procHO2(in currentMeasure varchar(60))
language sqlscript as
begin
exec 'select MONTH_NAME AS ID, sum('||:currentMeasure||') as SUM_MEASURE from
_SYS_BIC."schema/analyticView" GROUP BY MONTH_NAME';
end;
I call it this way
call procHO2('MARGIN');
but I don't want to create the SQL query dynamically since it's not recommended by SAP.
So what to do to pass an aggregated measure dynamically?
this is what the sample code from the documentation:
CREATE PROCEDURE my_proc_caller (IN in_client INT, IN in_currency INT, OUT outtab mytab_t) LANGUAGE SQLSCRIPT
BEGIN
outtab = SELECT * FROM CALC_VIEW (PLACEHOLDER."$$client$$" => :in_client , PLACEHOLDER."$$currency$$" => :in_currency );
END;
of course this works only on the latest release. Which revision are you running on?

Querying multiple rows from Oracle table using package

I wrote a package to query rows from a table. This select query will call other functions and returns all the rows from table. But when i write a package with all functions and sprocs , my sproc with select statement gives me an error saying i cannot execute without into statement. But if i use into then it will return only one row. How can i retrieve all rows using oracle sp?
Procedure GetData As
BEGIN
Select Jobid, JobName, JobLocation, JobCompany, X(jobid) FROM jobsTable; END GetData;
END;
I had to change it to following make the error go away:
Procedure GetData As
r_Jobid jobsTable.jobid%type;
r_JobName jobsTable.jobName%type;
r_JobLocation jobsTable.jobLocation%type;
r_temp varhar2(10);
BEGIN
Select Jobid, JobName, JobLocation, JobCompany, X(jobid)
INTO r_jobid, r_jobName, r_jobLocation, r_temp
FROM jobsTable;
END GetData;
END;
This is a better approach to returning multiple rows from a function:
FUNCTION GET_DATA()
RETURN SYS_REFCURSOR IS
results_cursor SYS_REFCURSOR;
BEGIN
OPEN results_cursor FOR
SELECT t.jobid,
t.jobName,
t.joblocation,
t.jobcompany,
X(t.jobid)
FROM JOBSTABLE t;
RETURN results_cursor;
END;
I agree with afk though that this doesn't appear to be what you really need to be using. Here's my recommendation for using a cursor:
CURSOR jobs IS
SELECT t.jobid,
t.jobName,
t.joblocation,
t.jobcompany,
X(t.jobid)
FROM JOBSTABLE t;
v_row jobs%ROWTYPE; --has to be declared AFTER the cursor to be able to reference the row type
BEGIN
OPEN jobs;
FETCH jobs INTO v_row;
IF jobs%FOUND THEN
--do stuff here, per row basis
--access columns in the row using: v_row.jobid/etc
END IF;
CLOSE jobs;
END;
Are you aware that this:
Procedure GetData As
r_Jobid jobsTable.jobid%type;
r_JobName jobsTable.jobName%type;
r_JobLocation jobsTable.jobLocation%type;
r_temp varhar2(10);
...means you defined local variables? You won't be able to get information out of the procedure. If you do, you'd need parameters, like this:
Procedure GetData(IO_R_JOBID IN OUT JOBSTABLE.JOBID%TYPE,
IO_R_JOBNAME IN OUT JOBSTABLE.JOBNAME%TYPE,
IO_R_JOBLOCATION IN OUT JOBSTABLE.JOBLOCATION%TYPE,
IO_R_TEMP IN OUT VARCHAR2(10)) AS
I use the IO_ to note which parameters are IN/OUT. I'd use IN_ or OUT_ where applicable. But the key here is to define OUT if you want to get a parameter back out.
Also - packages are just logical grouping of procedures & functions, with the ability to define constants scoped to the package. The package itself doesn't execute any SQL - it's still a function or procedure that is executing. God how I wish SQL Server had packages...
You could use a pipelined function. For this example I'm only fetching the ID columns, but you just need to add the others.
CREATE PACKAGE jobsPkg AS
TYPE jobsDataRec IS RECORD ( jobid jobsTable.jobid%type );
TYPE jobsDataTab IS TABLE OF jobsDataRec;
FUNCTION getJobsData RETURN jobsDataTab PIPELINED;
END jobsPkg;
/
CREATE PACKAGE BODY jobsPkg AS
FUNCTION getJobsData RETURN jobsDataTab PIPELINED
IS
output_record jobsDataRec;
BEGIN
FOR input_record IN ( SELECT jobid FROM jobsTable ) LOOP
output_record.jobid := input_record.jobid;
PIPE ROW (output_record);
END LOOP;
END getJobsData;
END jobsPkg;
/
This function can then be used as a row source:
SELECT * FROM TABLE( jobsPkg.getJobsData );
You need to use a Cursor for multiple return results. Take a look at this article on using Cursors for some details.
You could do something like:
DECLARE
CURSOR myCur IS
SELECT Jobid, JobName, JobLocation, JobCompany, X(jobid)
FROM jobsTable;
BEGIN
FOR resultRow in myCur
LOOP
--do your stuff here
END LOOP;
END;