How to define a type within a with block? - sql

This code works:
WITH
FUNCTION a (a IN INTEGER)
RETURN INTEGER
IS
BEGIN
RETURN a + 1;
END;
b(v) AS (SELECT column_value FROM sys.ODCINUMBERLIST(1,2,3))
SELECT a (v) FROM b;
But I would like to define a type in this with statement. Later I want to reuse this type in order to use a pipelined function. Therefore I will need a type which is a table of a record. And the type must be defined outside of the function (not inside the function ) because the type will be returned by the function
I tried with this simple type w.
WITH
type w is record(w1 integer);
FUNCTION f (a in integer)
RETURN INTEGER
IS
ret integer;
BEGIN
return 2;
END;
B(b1) as (select 1 from dual)
select f(3) from dual;
It doesn't work:
[Error] Compilation (3: 5): ORA-00900: invalid SQL statement
Is it possible to define a type within a with statement and how can I do that?

The return data type of a function used in a SQL context (as opposed to PL/SQL) must be either a native SQL type or a schema-level user-defined type (a type defined on its own, with the create type statement, rather than defined in a package).
This restriction was not lifted when support for functions defined in the with clause was added in Oracle 12.1 - not even for the data type of a function so defined. In particular, the return data type can't be defined in the with clause of a SQL statement.
Then, records are supported only in PL/SQL; at the schema level, you will need to create an object type, rather than a record type.
NOTE: A pipelined function returning a collection of records (with the record type defined in a package) can be used in a SQL context; the reason is that Oracle defines a corresponding object type, at the schema level, behind the scenes, and takes care of the conversion. Why Oracle doesn't do the same for all functions is something only Oracle can explain. Of course, as you are finding out in another thread you started, pipelined table functions in the with clause are not supported (even though non-pipelined table functions are!)
We don't know the actual problem you are trying to solve, but it seems unlikely that you will be able to do everything you are trying to do in the with clause.

You cannot.
If you look at the SELECT documentation, particularly the syntax diagrams:
with_clause::=
plsql_declarations::=
You can see that you can only declare a function or a procedure.
If you try:
WITH FUNCTION f (a in integer)
RETURN w
IS
type w is record(w1 integer);
BEGIN
return w(2);
END;
SELECT f(3)
FROM DUAL;
You get the error:
ORA-06553: PLS-313: 'F' not declared in this scope
ORA-06552: PL/SQL: Item ignored
ORA-06553: PLS-498: illegal use of a type before its declaration
You cannot fix that error but, if you magically could (which you cannot using only local declarations), you would get a second error as a RECORD is a PL/SQL only data type and you are then trying to use it in an SQL scope.
For example, if you declared the type globally in a PL/SQL package:
CREATE PACKAGE pkg AS
type w is record(w1 integer);
END;
/
WITH FUNCTION f (a in integer)
RETURN pkg.w
IS
BEGIN
return pkg.w(2);
END;
SELECT f(3).w1
FROM DUAL;
The query gives the error:
ORA-00902: invalid datatype
You would need to use an SQL OBJECT type and declare it in the global SQL scope before running your query.
For example:
CREATE TYPE w IS OBJECT(w1 INTEGER);
WITH FUNCTION f (a in integer)
RETURN w
IS
BEGIN
return w(2);
END;
SELECT f(3).w1
FROM DUAL;
Outputs:
F(3).W1
2
To use a RECORD, you need to declare the type in a PL/SQL package or in a PL/SQL anonymous block and then use it only in PL/SQL.
For example, if you just want to run your function using a locally declared PL/SQL type then you can do it entirely in a PL/SQL anonymous block:
DECLARE
TYPE w IS record(w1 integer);
v_w w;
FUNCTION f (a in integer)
RETURN w
IS
BEGIN
return w(2);
END f;
BEGIN
v_w := f(3);
DBMS_OUTPUT.PUT_LINE(v_w.w1);
END;
/
Outputs:
2
db<>fiddle here

Your code will work if you define the type within the function like so:
WITH
FUNCTION f (a in integer)
RETURN INTEGER
IS
ret integer;
type w is record(w1 integer);
BEGIN
return 2;
END;
B(b1) as (select 1 from dual)
select f(3) from dual;

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.

warning : function created with compilation error

Table EMP has ENAME as attribute.The following function gives error:
SET SERVEROUTPUT ON
SET ECHO ON
CREATE OR REPLACE FUNCTION count_emp(e_name varchar(20))
RETURN integer IS
total integer;
BEGIN
SELECT count(*) into total
FROM DEPARTMENTS
where ENAME = e_name;
RETURN total;
END;
/
warning:function created with compilation error.
You can run show errors; to see what compilation errors are.
The parameter's datatype should be specified without length. Also, use varchar2 instead of varchar.
From Oracle site:
The VARCHAR datatype is synonymous with the VARCHAR2 datatype. To avoid possible changes in behavior, always use the VARCHAR2 datatype to store variable-length character strings.
Try this:
CREATE OR REPLACE FUNCTION count_emp(e_name varchar2) -- here
RETURN integer IS
total integer;
BEGIN
SELECT count(*) into total
FROM DEPARTMENTS
where ENAME = e_name;
RETURN total;
END;
/
If you care about table EMP, you should use it in the function.
I would write this as:
CREATE OR REPLACE FUNCTION count_emp (
in_e_name varchar2
)
RETURN integer IS
v_total integer;
BEGIN
SELECT COUNT(*) into v_total
FROM EMP e
WHERE e.ENAME = in_e_name;
RETURN v_total;
END;
Notes:
Oracle will compile functions and stored procedures even when the objects don't (yet) exist. This is considered a "feature".
Use naming conventions to distinguish parameters and variables from columns. This is using in_ for the input parameters and v_ for the local variables.
Qualify all column name references. This further reduces the possibility of collision between a variable and column name.
You don't need a length for varchar2() inputs (which is preferable to varchar(), although perhaps one day, Oracle will cave to the standard).

Malformed array literal when selecting into custom type within a postgresql function

I have a postgresql custom type, containing arrays
CREATE TYPE route_part (
nodea bigint[],
edgea bigint[],
geom geometry
);
And a function, returning this type
CREATE OR REPLACE FUNCTION net.get_route_part_dist(int8, int8, int4)
RETURNS route_part
AS
$BODY$
DECLARE routerec route_part;
BEGIN
SELECT INTO routerec
...
;
RETURN routerec;
END;
$BODY$
LANGUAGE plpgsql VOLATILE;
This function works as expected and returns route_part composite type.
I'm trying to use it inside another "wrapper" function, that looks like this:
CREATE OR REPLACE FUNCTION net.get_route(beg_ int8, end_ int8, mida int8[], dist int4)
RETURNS route_part
AS
$BODY$
DECLARE routerec route_part;
BEGIN
SELECT INTO routerec net.get_route_part_dist(beg_, end_, dist);
RETURN routerec;
END;
$BODY$
LANGUAGE plpgsql VOLATILE;
I get an error on the select query.
ERROR: malformed array literal: "(
{303513543,2289605239,...,306687989}","
{2585314,264212,...,1088633}",
0102000020110F000029000000AE47E11A81754F41C3F5280C07F25C)"
DETAIL: Array value must start with "{" or dimension information.
I don't cast types to strings or other types, so I can't figure out why the returned value considered to have a malformed array.
Any clues?
The solution is to assign decomposed values:
CREATE OR REPLACE FUNCTION net.get_route(beg_ int8, end_ int8, mida int8[], dist int4)
RETURNS route_part AS
$func$
DECLARE
routerec route_part;
BEGIN
SELECT INTO routerec * FROM net.get_route_part_dist(beg_, end_, dist);
RETURN routerec;
END
$func$ LANGUAGE plpgsql;
Since routerec is a row type (composite type), The columns of the SELECT list must match the columns of the row type. The form you had would attempt to fit the value (as a whole) returned by net.get_route_part_dist() into the first column of routerec.
Quoting the manual:
If a row or a variable list is used as target, the query's result
columns must exactly match the structure of the target as to number
and data types
Postgres tries to fit your composite type (or rather its text representation) into bigint[], the first column of the composite type routerec. The error message you quoted is the consequence.
Explanation in the manual:
If the expression's result data type doesn't match the variable's data
type, the value will be coerced as though by an assignment cast (see
Section 10.4). If no assignment cast is known for the pair of data
types involved, the PL/pgSQL interpreter will attempt to convert the
result value textually, that is by applying the result type's output
function followed by the variable type's input function. Note that
this could result in run-time errors generated by the input function,
if the string form of the result value is not acceptable to the input function.
This can be confusing and fooled the first time as well. The distinction seems necessary since INTO allows assigning a list of target variables at once.
The bottom line is this: Decompose row types with SELECT * FROM ... when assigning to a row / record / composite type with INTO. Else it will be assigned to the first target column as a whole.
Avoid these inefficient forms:
Like you commented:
SELECT INTO routerec (net.get_route_part_dist(beg_, end_, dist)).nodea
, (net.get_route_part_dist(beg_, end_, dist)).edgea
, (net.get_route_part_dist(beg_, end_, dist)).geom;
Or, less verbose, but equally inefficient:
SELECT INTO routerec (net.get_route_part_dist(beg_, end_, dist)).*;
Each would evaluate the function multiple times - as opposed to:
SELECT INTO routerec * FROM net.get_route_part_dist(beg_, end_, dist)
Related:
Use of custom return types in a FOR loop in plpgsql
Passing array of a composite type to stored procedure
Simple alternative
The simple alternative for your simple case: direct assignment (without INTO):
routerec := net.get_route_part_dist(beg_, end_, dist);
RETURN routerec;
Simple assignment only allows a single target to begin with.
Or return the result directly:
RETURN net.get_route_part_dist(beg_, end_, dist);

Understanding cast from bytea to oid

I'm using PostgreSQL 9.2.
This blog entry by Grace Batumbya provides a cast from bytea to oid.
create or replace function blob_write(lbytea bytea)
returns oid
volatile
language plpgsql as
$f$
declare
loid oid;
lfd integer;
lsize integer;
begin
if(lbytea is null) then
return null;
end if;
loid := lo_create(0);
lfd := lo_open(loid,131072);
lsize := lowrite(lfd,lbytea);
perform lo_close(lfd);
return loid;
end;
$f$;
CREATE CAST (bytea AS oid) WITH FUNCTION blob_write(bytea) AS ASSIGNMENT;
CREATE TABLE bytea_to_lo (
largeObj lo
);
I didn't understand why should we create bytea_to_lo table? How is it going to be used by PostgreSQL?
The cast is not a true cast. It's just (ab)using the convenient syntax. A large object (LO) is created in the background which is stored separately and the OID referencing it is returned.
Per documentation:
All large objects are stored in a single system table named
pg_largeobject. Each large object also has an entry in the system
table pg_largeobject_metadata. Large objects can be created, modified,
and deleted using a read/write API that is similar to standard
operations on files.
The returned OID is basically a FK to the PK of the system table pg_largeobject.
CREATE TABLE is completely independent from the function and pseudo-cast.
CREATE TABLE bytea_to_lo (
largeObj lo
);
It's just a typical use case for the assignment cast created above, which becomes apparent from the following line that you forgot to quote:
INSERT INTO bytea_to_lo VALUES (DECODE('00AB','hex'));
What happens here?
The data type lo is a domain over the base type oid, created by the additional module lo (incorrectly referenced as "lo_manage package" in the blog enty of Grace Batumbya). Per documentation:
The module also provides a data type lo, which is really just a domain
of the oid type. This is useful for differentiating database columns
that hold large object references from those that are OIDs of other things.
The function decode() returns bytea. The INSERT statement assigns the bytea value to the column largeObj, which triggers an assignment cast to its type lo, and that's where the above cast comes in.
Warning / Corrective / Update
The blog entry is sloppy and outdated by now.
Does not bother to mention that (per documentation):
To be able to create a cast, you must own the source or the target
data type and have USAGE privilege on the other type.
Effectively, you must be superuser.
Typo in CREATE TABLE: column name and type reversed.
The function definition is verbose and inefficient. This would be better (for Postgres 9.3 or older):
CREATE OR REPLACE FUNCTION blob_write(bytea)
RETURNS oid AS
$func$
DECLARE
loid oid := lo_create(0);
lfd int := lo_open(loid,131072); -- = 2^17 = x2000
-- symbolic constant defined in the header file libpq/libpq-fs.h
-- #define INV_WRITE 0x00020000
BEGIN
PERFORM lowrite(lfd, $1);
PERFORM lo_close(lfd);
RETURN loid;
END
$func$ LANGUAGE plpgsql VOLATILE STRICT;
SQL Fiddle.
There is a built-in function for this in Postgres 9.4. Use that instead:
lo_from_bytea(loid oid, string bytea)
From the release notes:
Add SQL functions to allow large object reads/writes at arbitrary offsets (Pavel Stehule)
For CREATE CAST (per documentation):
The first argument type must be identical to or binary-coercible from the cast's source type.
I suggest an overloaded variant with only a bytea parameter:
CREATE OR REPLACE FUNCTION lo_from_bytea(bytea)
RETURNS oid LANGUAGE sql AS
'SELECT lo_from_bytea(0, $1)';
CREATE CAST (bytea AS oid) WITH FUNCTION lo_from_bytea(bytea) AS ASSIGNMENT;
Since the pseudo-cast has quite a big side effect, I am not convinced to make that an ASSIGNMENT cast. I'd probably start with explicit-only:
Generate series of dates - using date type as input

Creating functions in postgresql

I am new to SQL, so please try not to be overly critical about my question, I need to create a function which would return me a table (say for example "machine") , which would have a column called "aggtablename" and the rows would be filled with values derived from a database. Here is what i tried and the following error came....so please help me in making my syntax correct, THANKS..
CREATE FUNCTION aggtable() RETURNS TABLE (machineid, serveraggtablename)
AS $table$
BEGIN
RETURN QUERY
SELECT m.machineid, m.serveraggtablename
FROM machine m
END;
$table$
LANGUAGE plpgsql;
select aggtable();
ERROR: function aggtable() does not exist LINE 1: select aggtable();
^ HINT: No function matches the given name and argument types. You might need to add explicit type casts.
The arguments of the table you're returning do not have any type.
try adding a type such as machineid int.
check this post
How can a Postgres Stored Function return a table
Try this
CREATE TYPE machineType as (machineid int, serveraggtable character varying);
CREATE FUNCTION aggtable() RETURNS SETOF machineType AS
'SELECT m.machineid, m.serveraggtablename FROM machine m;'
LANGUAGE 'sql';
SELECT * FROM aggtable();
https://wiki.postgresql.org/wiki/Return_more_than_one_row_of_data_from_PL/pgSQL_functions