The following syntax successfully creates a user defined function, but does not drop it. Can anyone identify where my error is?
-- Example 1 - scalar function
USE AdventureWorks2012
GO
CREATE FUNCTION Sales.uf_MostRecentCustomerOrderDate (#CustomerID int)
RETURNS
DATETIME
AS
BEGIN;
DECLARE #MostRecentOrderDate datetime;
SELECT #MostRecentOrderDate = MAX(OrderDate)
FROM Sales.SalesOrderHeader as soh
Where CustomerID = #CustomerID
RETURN #MostRecentOrderDate
END;
GO
-- Using user defined scalar function
SELECT Sales.uf_MostRecentCustomerOrderDate(29825); -- returns 2008-04-01 00:00:00.000
-- Delete existing scalar valued function
USE AdventureWorks2012;
GO
-- determines if function exists in database
IF OBJECT_ID (N'Sales.uf_MostRecentCustomerOrderDate', N'IF') IS NOT NULL
-- deletes function
DROP FUNCTION Sales.uf_MostRecentCustomerOrderDate;
GO
That function gets created with a type of FN (not IF as you've used).
Try this code to drop it:
-- determines if function exists in database
IF OBJECT_ID (N'Sales.uf_MostRecentCustomerOrderDate', N'FN') IS NOT NULL
-- deletes function
DROP FUNCTION Sales.uf_MostRecentCustomerOrderDate;
GO
Type IF stands for an inline table-valued function - this is not the case here.
Type FN stands for a scalar function - which this is.
See the TechNet docs on sys.objects which also lists all defined types in SQL Server catalog views
Related
I'm trying to come up with a function to verify the object identifier name. Like in Oracle, if a given identifier associated with any sql object (tables, functions, views,... ) It returns the name as it is else error out. Following are few examples.
SELECT SYS.DBMS_ASSERT.SQL_OBJECT_NAME('DBMS_ASSERT.sql_object_name') FROM DUAL;
SYS.DBMS_ASSERT.SQL_OBJECT_NAME('DBMS_ASSERT.SQL_OBJECT_NAME')
DBMS_ASSERT.sql_object_name
SELECT SYS.DBMS_ASSERT.SQL_OBJECT_NAME('unknown') FROM DUAL;
ORA-44002: invalid object name
For tables, views, sequences, you'd typically cast to regclass:
select 'some_table_I_will_create_later'::regclass;
ERROR: relation "some_table_I_will_create_later" does not exist`.
LINE 1: select 'some_table_I_will_create_later'::regclass;
^
For procedures and functions, it'd be a cast to regproc instead, so to get a function equivalent to DBMS_ASSERT.sql_object_name() you'd have to go through the full list of what the argument could be cast to:
create or replace function assert_sql_object_name(arg text)
returns text language sql as $function_body$
select coalesce(
to_regclass(arg)::text,
to_regcollation(arg)::text,
to_regoper(arg)::text,
to_regproc(arg)::text,
to_regtype(arg)::text,
to_regrole(quote_ident(arg))::text,
to_regnamespace(quote_ident(arg))::text )
$function_body$;
These functions work the same as a plain cast, except they return null instead of throwing an exception. coalesce() works the same in PostgreSQL as it does in Oracle, returning the first non-null argument it gets.
Note that unknown is a pseudo-type in PostgreSQL, so it doesn't make a good test.
select assert_sql_object_name('unknown');
-- assert_sql_object_name
-- ------------------------
-- unknown
select assert_sql_object_name('some_table_I_will_create_later');
-- assert_sql_object_name
-- ------------------------
-- null
create table some_table_I_will_create_later(id int);
select assert_sql_object_name('some_table_I_will_create_later');
-- assert_sql_object_name
-- --------------------------------
-- some_table_i_will_create_later
select assert_sql_object_name('different_schema.some_table_I_will_create_later');
-- assert_sql_object_name
-- ------------------------
-- null
create schema different_schema;
alter table some_table_i_will_create_later set schema different_schema;
select assert_sql_object_name('different_schema.some_table_I_will_create_later');
-- assert_sql_object_name
-- -------------------------------------------------
-- different_schema.some_table_i_will_create_later
Online demo
There is no direct equivalent, but if you know the expected type of the object, you can cast the name to one of the Object Identifier Types
For tables, views and other objects that have an entry in pg_class, you can cast it to to regclass:
select 'pg_catalog.pg_class'::regclass;
select 'public.some_table'::regclass;
The cast will result in an error if the object does not exist.
For functions or procedures you need to cast the name to regproc:
select 'my_schema.some_function'::regproc;
However, if that is an overloaded function (i.e. multiple entries exist in pg_catalog.pg_proc, then it would result in an error more than one function named "some_function". In that case you need to provide the full signature you want to test using the type regprocedureregprocedure instead, e.g.:
select 'my_schema.some_function(int4)'::regprocedure;
You can create a wrapper function in PL/pgSQL that tries the different casts to mimic the behaviour of the Oracle function.
The orafce extensions provides an implementation of dbms_assert.object_name
I have this pgsql function that checks if a person exists based on given personid. This function returns bool accordingly. From my query, I get the following:
However, I want the resulting column to have an alias so it's more readable among our developers. So instead of getting func_util__check_basicinfo, I want it to be person_exists.
Also the reason why the function is named this way is for our convention purpose.
Here's my query for this function:
CREATE OR REPLACE FUNCTION profile.func_util__check_basicinfo(person_id integer)
RETURNS boolean
LANGUAGE plpgsql
AS $function$
begin
return exists (
select 1
from profile.person_basicinfo pb
where pb.personid = person_id
);
END;
$function$
;
A function's definition doesn't and can't define an alias. You will need to use the alias in the query where you use the function:
select func_util_check_basicinfo(...) as person_exists
from ...
By default a column will be named after the source in the query. So the column name always defaults to the function's name (this is true for all functions in Postgres). If you don't want to specify the column alias manually in every query, your only option is to rename the function to person_exists() (which I would find a much better name than the existing one to begin with)
I have created very simple function in DB2oC as below, which has one UPDATE sql statement and one SELECT sql statement along with MODIFIES SQL DATA. But still I get the below error, though I have specified MODIFIES SQL DATA. I did GRANT ALL on that TEST table to my user id and also did GRANT EXECUTE ON FUNCTION to my user id on safe side. Can you please help to explain on what could be the issue?
I have simply invoked the function using SELECT statement like below:
SELECT TESTSCHEMA.MAIN_FUNCTION() FROM TABLE(VALUES(1));
SQL Error [38002]: User defined routine "TESTSCHEMA.MAIN_FUNCTION"
(specific name "SQL201211013006981") attempted to modify data but was
not defined as MODIFIES SQL DATA.. SQLCODE=-577, SQLSTATE=38002,
DRIVER=4.27.25
CREATE OR REPLACE FUNCTION MAIN_FUNCTION()
RETURNS VARCHAR(20)
LANGUAGE SQL
MODIFIES SQL DATA
BEGIN
DECLARE val VARCHAR(20);
UPDATE TEST t SET t.CONTENT_TEXT = 'test value' WHERE t.ID = 1;
select CONTENT_TEXT into val from TEST where ID = 1;
return val;
end;
Appreciate your help.
For the modifies SQL data clause , the usage of the function is restricted on Db2-LUW.
These restrictions do not apply for user defined functions that do not modify data.
For your specific example, that UDF will operate when used as the sole expression on the right hand side of an assignment statement in a compound-SQL compiled statemnent.
For example:
create or replace variable my_result varchar(20) default null;
begin
set my_result = main_function();
end#
Consider using stored procedures to modify table contents, instead of user defined functions.
You could avoid using a function, and just use a single "change data statement"
SELECT CONTENT_TEXT
FROM NEW TABLE(
UPDATE TEST t
SET t.CONTENT_TEXT = 'test value'
WHERE t.ID = 1
)
I am developing a framework that dynamically creates tables for contents storage on PostgreSQL 9.1. One of the API functions allows caller to save a new contents entry by specifying all fields within a given object (say, web form). In order to receive a set of fields framework creates a composite type.
Consider the following code:
CREATE SEQUENCE seq_contents MINVALUE 10000;
CREATE TABLE contents (
content_id int8 not null,
is_edited boolean not null default false,
is_published boolean not null default false,
"Input1" varchar(60),
"CheckBox1" int2,
"TheBox" varchar(60),
"Slider1" varchar(60)
);
CREATE TYPE "contentsType" AS (
"Input1" varchar(60),
"CheckBox1" int2,
"TheBox" varchar(60),
"Slider1" varchar(60)
);
CREATE OR REPLACE FUNCTION push(in_all anyelement) RETURNS int8 AS $push$
DECLARE
_c_id int8;
BEGIN
SELECT nextval('seq_contents') INTO _c_id;
EXECUTE $$INSERT INTO contents
SELECT a.*, b.*
FROM (SELECT $1, true, false) AS a,
(SELECT $2.*) AS b$$ USING _c_id, in_all;
RETURN _c_id;
END;
$push$ LANGUAGE plpgsql;
Now, in order to call this function I have to add explicit cast, like this:
SELECT push(('input1',1,'thebox','slider1')::"contentsType");
Is there a way to avoid explicit cast? As I would like external callers not to deal with casts, i.e. hide the logic behind the PostgreSQL functions. Currently I have such error:
SELECT push(('input1',1,'thebox','slider1'));
ERROR: PL/pgSQL functions cannot accept type record
CONTEXT: compilation of PL/pgSQL function "push" near line 1
Have you considered passing the record variable as its text representation?
In theory, every record variable can be cast to and from text with the normal CAST operator.
Here is the function modified so that in_all has type text and gets casted to "contentsType" in the USING clause:
CREATE OR REPLACE FUNCTION push(in_all text) RETURNS int8 AS $push$
DECLARE
_c_id int8;
BEGIN
SELECT nextval('seq_contents') INTO _c_id;
EXECUTE $$INSERT INTO contents
SELECT a.*, b.*
FROM (SELECT $1, true, false) AS a,
(SELECT $2.*) AS b$$ USING _c_id, in_all::"contentsType";
RETURN _c_id;
END;
$push$ LANGUAGE plpgsql;
Then it can be called like this (no explicit reference to the type)
select push( '(input1,1,thebox,slider1)' );
or like that (explicit record casted to text)
SELECT push(('input1',1,'thebox','slider1')::"contentsType"::text);
That would work not just with "contentsType", but any other record type, assuming the function is able to convert it back to that type.
Also in plpgsql, I assume this should work as well:
ret := push(r::text);
when r is a record variable.
Since you're hard-coding the table name into which you want to insert, and you have a fixed number and type of parameters it needs, I'm not clear on why you need the "contentsType" type at all. Why not eliminate the extra level of parentheses from the function calling, and just pass the four parameters directly? That keeps everything simpler.
CREATE OR REPLACE FUNCTION push(
"Input1" varchar(60),
"CheckBox1" int2,
"TheBox" varchar(60),
"Slider1" varchar(60)
) RETURNS int8 AS $push$
DECLARE
_c_id int8;
BEGIN
SELECT nextval('seq_contents') INTO _c_id;
EXECUTE $$INSERT INTO contents
VALUES ($1, true, false, $2, %3, %4, $5)
$$ USING _c_id, "Input1", "CheckBox1", "TheBox", "Slider1");
RETURN _c_id;
END;
$push$ LANGUAGE plpgsql;
That makes calling the function look like this:
SELECT push('input1',1,'thebox','slider1');
If you're looking to generalized the push() function so that it works for all tables, you'll hit other problems if you get past this one. You won't be able to get past the fact that the function will need to know the table name during execution. If you want to overload the function so that you can have a separate push() for each record type, you need to provide information on the record type somehow. So, if you're looking to do something like this, the short answer to your question is "No."
On the other hand, you may be making this a little harder than it needs to be. I hope you are aware that there is automatically a type created for every table, by the same name as the table. You could probably leverage that to both avoid declaring the type explicitly and to pass a record with the same name as your table -- with dummy entries for the values that the function will fill. I think you could make one totally generic push function, although it might be hard to get past the strong typing issues in plpgsql; writing the function in C might be easier if you're familiar with it.
here's the stored procedure i wrote.In this proc "p_subjectid" is an array of numbers passed from the front end.
PROCEDURE getsubjects(p_subjectid subjectid_tab,p_subjects out refCursor)
as
BEGIN
open p_subjects for select * from empsubject where subject_id in
(select column_value from table(p_subjectid));
--select * from table(cast(p_subjectid as packg.subjectid_tab))
END getsubjects;
This is the error i am getting.
Oracle error ORA-22905: cannot access rows from a non-nested table item OR
as i have seen in different post,i tried casting "cast(p_subjectid as packg.subjectid_tab)" inside table function as given in the comment below.But i am getting another error: ORA-00902: invalid datatype.
And this is the definition of the "subjectid_tab".
type subjectid_tab is table of number index by binary_integer;
Can anyone please tell me what's the error.Is anything wrong with the my procedure.
You have to declare the type on "the database level" as ammoQ suggested:
CREATE TYPE subjectid_tab AS TABLE OF NUMBER INDEX BY binary_integer;
instead of declaring the type within PL/SQL. If you declare the type just in the PL/SQL block, it won't be available to the SQL "engine".
Oracle has two execution scopes: SQL and PL/SQL. When you use a SELECT/INSERT/UPDATE (etc) statement you are working in the SQL scope and, in Oracle 11g and below, you cannot reference types that are defined in the PL/SQL scope. (Note: Oracle 12 changed this so you can reference PL/SQL types.)
TYPE subjectid_tab IS TABLE OF NUMBER INDEX BY BINARY_INTEGER;
Is an associative array and can only be defined in the PL/SQL scope so cannot be used in SQL statements.
What you want is to define a collection (not an associative array) in the SQL scope using:
CREATE TYPE subjectid_tab IS TABLE OF NUMBER;
(Note: you do not need the INDEX BY clause for a collection.)
Then you can do:
OPEN p_subjects FOR
SELECT *
FROM empsubject
WHERE subject_id MEMBER OF p_subjectid;
or
OPEN p_subjects FOR
SELECT *
FROM empsubject
WHERE subject_id IN ( SELECT COLUMN_VALUE FROM TABLE( p_subjectid ) );
This is the good solution.
You cannot use a table(cast()) if the type that you cast is in the DECLARE part of the pl/sql block.
You REALLY need to use CREATE TYPE my_type [...]. Otherwise, it will throw the "cannot fetch row[...]" exception.
I just had this problem yesterday.
DECLARE
TYPE number_table IS TABLE OF NUMBER;
result_ids number_table := number_table();
BEGIN
/* .. bunch of code that uses my type successfully */
OPEN ? AS
SELECT *
FROM TABLE(CAST(result_ids AS number_table)); /* BOOM! */
END;
This fails in both of the ways you described earlier when called from a java routine. I discovered this was due to the fact that the type number_table is not defined in an exportable manner than can be shipped off the database. The type works great internally to the routine. But as soon as you try to execute a returnable recordset that references it in any way (including IN clauses?!?) you get a datatype not defined.
So the solution really is CREATE TYPE myschema.number_table IS TABLE OF NUMBER; Then drop the type declaration from your block and use the schema level declaration. Use the schema qualifier to reference the type just to be sure you are using the right one.
you have to cast the results of the pipelined query so:
If your pipelined function returns a rowtype of varchar2 then define a type (for example )
CREATE OR REPLACE TYPE char_array_t is VARRAY(32) of varchar2(255);
select * from table(cast(fn(x) as user_type_t ) );
will now work.