I am currently working on an oracle database in APEX.
First of all, I do not have access to any tools. I can only use what is provided in apex.oracle.com and I am writing the script in NotePad++ before uploading it over there. It's a school assignment, so I am not allowed to use any other tools, even if there is something that would make things easier.
I am creating a long script that creates a bunch of tables in a database, creates a bunch of records in every table, and creates all the constraints. This part works fine. Now i must create several functions and procedures in the same script, after the other stuff.
Every single time I try to create one, I get this: ORA-24344: success with compilation error
Also, the other instructions in the script after this error are not executed. Everything that was before the error works fine.
Here is one of the functions that create this error:
CREATE OR REPLACE FUNCTION SP_03Recherche (titre_art VARCHAR2, nom_aut VARCHAR2, type_art VARCHAR2)
RETURN CURSOR
IS
CURSOR articles (p_titre_art VARCHAR2, p_nom_aut VARCHAR2, p_type_art VARCHAR2) IS
SELECT * FROM BI_Articles INNER JOIN (BI_ArticlesAuteurs INNER JOIN BI_Auteurs ON BI_ArticlesAuteurs.AuteurID = BI_Auteurs.AuteurID) ON BI_Articles.ISBN = BI_ArticlesAuteurs.ISBN
WHERE (Titre LIKE p_titre_art) AND ((Nom LIKE p_nom_aut) OR (Prenom LIKE p_nom_aut)) AND TypeArticle LIKE type_art;
BEGIN
RETURN articles(titre_art, nom_aut, type_art);
END;
sys_refcursor is the data type that you seemingly want to return. That's a generic type for a weak ref cursor. My guess is that you want something like
CREATE OR REPLACE FUNCTION SP_03Recherche (
p_titre_art VARCHAR2,
p_nom_aut VARCHAR2,
p_type_art VARCHAR2
)
RETURN sys_refcursor;
IS
l_rc sys_refcursor;
BEGIN
OPEN l_rc
FOR select *
from bi_articles art
inner join BI_ArticlesAuteurs art_auth
on (art.isbn = art_auth.isbn)
inner join BI_Auteurs auth
on (art_auth.auteurID = auth.auteurID)
where titre LIKE p_titre_art
and (nom LIKE p_nom_auth or
prenum LIKE p_nom_auth)
and typearticle LIKE type_art;
RETURN l_rc;
END;
Now, a few suggestions
Doing a select * is almost always a bad idea. Particularly when you are joining multiple tables. Do you really, really want the structure of the result set to change every time someone adds an additional column to any of three tables? That seems unlikely.
Use aliases everywhere. Looking at the code, I have no idea what table titre comes from. Or nom or prenum or typearticle. Use aliases to identify which table a column comes from. That makes your code clearer and makes it more robust when additional tables or columns are added in the future which may have the same column names (multiple entities might have a nom column for example).
Related
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
One of the main differences between UDF and SP is that UDF can only have select statements inside it and not insert/update/delete statements. Can someone please explain the reason behind this?The below function:
create function test(..)
...
BEGIN
insert into EMPLOYEE('22',12000,'john');
return 0;
END
is not valid. But why is this so?
The insert statement inside your function is missing the values keyword;
insert into EMPLOYEE('22',12000,'john');
should be
insert into EMPLOYEE values ('22',12000,'john');
though it's better to include the list of column names too. From the small part of the code you showed that is the only thing that is invalid. There could be other errors in the bits you have omitted. (If the first column in your table is numeric then you shouldn't be passing a string - it works but does implicit conversion and is best avoided. And if the column is a string, should it be really?)
UDF can only have select statements inside it and not insert/update/delete statements
That is not correct. You can have DML (insert/update/delete) in a function, but you can only call it from a PL/SQL context (though even in PL/SQL, it's often said that functions should query data with no side effects and only procedures should modify data; but that is not restricted by the language itself):
create table employee (id varchar2(3), salary number, name varchar2(10));
Table EMPLOYEE created.
create function test(unused number)
return number as
BEGIN
insert into EMPLOYEE (id, salary, name)
values ('22',12000,'john');
return 0;
END;
/
Function TEST compiled
declare
rc number;
begin
rc := test(42);
end;
/
PL/SQL procedure successfully completed.
select * from employee;
ID SALARY NAME
--- ---------- ----------
22 12000 john
But you cannot call it from a SQL context:
select test(42) from dual;
ORA-14551: cannot perform a DML operation inside a query
ORA-06512: at "MYSCHEMA.TEST", line 4
The documentation lists restrictions on functions called from SQL, and goes into more detail in this warning:
Because SQL is a declarative language, rather than an imperative (or procedural) one, you cannot know how many times a function invoked by a SQL statement will run—even if the function is written in PL/SQL, an imperative language.
If the function was allowed to do DML then you would have no control over how many times that DML was performed. If it was doing an insert, for instance, it might try to insert the same row twice and either duplicate data or get a constraint violation.
Just to summarise the comments, you can have DML inside a PL/SQL function.
What you can't do is call that function from SQL, because a select statement shouldn't also apply updates and deletes and so on as a hidden side effect.
For one thing, the SQL language reserves the right to execute the query any way it chooses, in any order, and with any caching it decides make use of. (It might even stop and restart during execution. That's up to the SQL engine.) Therefore your function might get called once or a hundred times, in any order, depending on the execution plan, and so the results would be unpredictable.
I am trying to create a stored procedure for ORACLE SQL but it keeps throwing the error:
Error(1,1): PLS-00410: duplicate fields in RECORD,TABLE or argument list are not permitted
I do not see any duplicate fields so I was wondering why this was happening (procedure is below). Also stored procedures only seem to allow queries to return one row, is there any way to make it return more than one row?
I saw a lot of questions pertaining to returning multiple rows but none of them were too clear. I also need multiple stored procedures so I was wondering if there could be any clashing of variables and whatnot.
CREATE OR REPLACE PROCEDURE ARTIST_CHECK(
p5_checkartist IN VARCHAR2,
p5_artist OUT TESTTABLE.artist%TYPE,
p5_thisweekpos OUT TESTTABLE.thisweekpos%TYPE,
p5_lastweekpos OUT TESTTABLE.lastweekpos%TYPE,
p5_title OUT TESTTABLE.title%TYPE,
p5_artist OUT TESTTABLE.artist%TYPE,
p5_entrydate OUT TESTTABLE.entrydate%TYPE,
p5_entrypos OUT TESTTABLE.entrypos%TYPE,
p5_peakpos OUT TESTTABLE.peakpos%TYPE,
p5_totalweek OUT TESTTABLE.totalweek%TYPE,
p5_thisweekdate OUT TESTTABLE.thisweekdate%TYPE)
IS
BEGIN
select t.THISWEEKPOS ,t.LASTWEEKPOS ,t.TITLE ,t.ARTIST ,t.ENTRYDATE ,t.ENTRYPOS ,t.PEAKPOS ,t.TOTALWEEK ,t.THISWEEKDATE
into p5_thisweekpos, p5_lastweekpos, p5_title, p5_artist, p5_entrydate, p5_entrypos, p5_peakpos, p5_totalweek, p5_thisweekdate
from(select artist as match, max(thisweekdate) as recent from testtable where upper(artist) like '%p5_checkartist%' group by artist), testtable t
where t.ARTIST = match and t.THISWEEKDATE = recent;
END;
below is there twice .. try omitting one per your code
p5_artist OUT TESTTABLE.artist%TYPE,
Remove the extra p5_artist OUT TESTTABLE.artist%TYPE,
CREATE OR REPLACE PROCEDURE ARTIST_CHECK(
p5_checkartist IN VARCHAR2,
p5_artist OUT TESTTABLE.artist%TYPE,
p5_thisweekpos OUT TESTTABLE.thisweekpos%TYPE,
p5_lastweekpos OUT TESTTABLE.lastweekpos%TYPE,
p5_title OUT TESTTABLE.title%TYPE,
p5_entrydate OUT TESTTABLE.entrydate%TYPE,
p5_entrypos OUT TESTTABLE.entrypos%TYPE,
p5_peakpos OUT TESTTABLE.peakpos%TYPE,
p5_totalweek OUT TESTTABLE.totalweek%TYPE,
p5_thisweekdate OUT TESTTABLE.thisweekdate%TYPE)
IS
BEGIN
select t.THISWEEKPOS ,t.LASTWEEKPOS ,t.TITLE ,t.ARTIST ,t.ENTRYDATE ,t.ENTRYPOS ,t.PEAKPOS ,t.TOTALWEEK ,t.THISWEEKDATE
into p5_thisweekpos, p5_lastweekpos, p5_title, p5_artist, p5_entrydate, p5_entrypos, p5_peakpos, p5_totalweek, p5_thisweekdate
from(select artist as match, max(thisweekdate) as recent from testtable where upper(artist) like '%p5_checkartist%' group by artist), testtable t
where t.ARTIST = match and t.THISWEEKDATE = recent;
END;
I just tripped over an answer to a problem I was having with a PL/SQL variable not being recognized by a function and I was hoping someone could explain to me why my solution worked and what is happening "underneath the hood".
Background
As part of an optimization project, I am trying to collect metrics on individual SQL scripts within a Stored Procedure. The Stored Proc that I am dissecting has an In-type date parameter that I need to define in order to run each individual SQL Script:
CREATE OR REPLACE myStoredProc (DATE_IN DATE, ERROR_OUT OUT VARCHAR2)
IS
BEGIN
--Truncate Temp Tables
--6 Individual SQL Scripts
EXCEPTION
--Error Handling
END;
To run each script individually, I decided to just drop each SQL statement into a PL/SQL block and feed the DATE_IN parameter in as a variable:
DECLARE
DATE_IN DATE := TO_DATE('16-JUL-2014','DD-MON-RR');
BEGIN
--Place individual script here
END;
The Problem
This approach worked fine for a couple of the queries that referred to this DATE_IN variable but one query with a reference to an outside function which takes DATE_IN as a parameter started throwing an ORA-00904 error:
DECLARE
DATE_IN DATE := TO_DATE('16-JUL-2014','DD-MON-RR');
BEGIN
insert into temp_table
SELECT table1.field1,
table1.field2,
table2.fieldA,
MyFunction(table1.field1, DATE_IN) --This was the problem line
FROM
table1,
table2
WHERE EXISTS (inner query)
AND table1.keys = table2.keys
AND table2.date <= DATE_IN
END;
Solution
At the advice of another Developer, I was able to get around this error by adding a colon (:) in front of the DATE_IN variable that I was passing into the function so that the problem line read MyFunction(table1.field1, :DATE_IN). As soon as I did that, my error disappeared and I was able to run the query without issue.
I was happy for the result but the other Developer wasn't able to explain why it was needed, only that it was necessary to call any functions or other stored procs from a PL/SQL statement. I assume this has something to do with scope but I would like to get a better explanation as to why this colon was necessary for the function to see the variable.
Questions
I've tried to do a little research looking over Oracle documentation on parameters, variables, binding/declaring and constants but my research has only given me more questions:
After reading up on variables, I now question if that is the correct term for what I have been using (since I didn't actually use the VARIABLE command and I'm passing in a date - which is not an allowable data type). If my DATE_IN DATE := statement is not a variable, then what is it?
Why were the rest of my references to DATE_IN recognized by the compiler but passing the value to the function was out of scope?
What exactly is the colon (:) doing here? Is this turning that into a bind variable?
Thanks in advance. I appreciate any guidance you can provide!
----------------------------------EDIT--------------------------------------
I was asked to provide additional information. My Db version is 11G, 11.2.0.2.0. The query that I was able to reproduce this error is below.
DECLARE
EXTRACT_DT_IN DATE := TO_DATE('16-JUL-2014','DD-MON-RR');
BEGIN
--This begins the pre-optimized query that I'm testing
insert into AELI_COV_TMP_2_OPT
SELECT /*+ ordered use_nl(CM MAMT) INDEX (CM CSMB_CSMB2_UK) INDEX (MAMT (MBAM_CSMB_FK_I) */
CM.CASE_MBR_KEY
,CM.pyrl_no
,MAMT.AMT
,MAMT.FREQ_CD
,MAMT.HOURS
,aeli$cov_pdtodt(CM.CASE_MBR_KEY, EXTRACT_DT_IN)
FROM
CASE_MEMBERS CM
,MEMBER_AMOUNTS MAMT
WHERE EXISTS (select /*+ INDEX(SDEF SLRY_BCAT_FK_I) */
'x'
from SALARY_DEF SDEF
where SDEF.CASE_KEY = CM.CASE_KEY
AND SDEF.TYP_CD = '04'
AND SDEF.SLRY_KEY = MAMT.SLRY_KEY)
AND CM.CASE_MBR_KEY = MAMT.CASE_MBR_KEY
AND MAMT.STAT_CD = '00'
AND (MAMT.xpir_dt is null or MAMT.xpir_dt > EXTRACT_DT_IN)
AND MAMT.eff_dt <= EXTRACT_DT_IN;
--This ends the pre-optimized query that I'm testing
END;
Here is the error I'm encountering when trying to run an Explain Plan on this statement. I am able to get past this error if I remove reference to line 13 or I add a colon (:) to the EXTRACT_DT_IN on that line.
----------------------EDIT 2-------------------
Here is the function signature of aeli$.cov_pdtodt. (I've replaced the owner for security reasons).
CREATE OR REPLACE function __owner__.aeli$cov_pdtodt
(CASE_MBR_KEY_IN IN NUMBER, EXTRACT_EFF_DT_IN DATE)
RETURN DATE IS
PDTODT DATE;
Your anonymous block is fine as it is, as long as you execute the whole block. If you try to execute just the insert, or its select, as a standalone command then it will indeed fail with ORA-00904.
That isn't quite a scope problem, it's a context problem. You're trying to refer to a PL/SQL variable in an SQL context, and that is never going to work.
In a PL/SQL context this would work:
declare
some_var dual.dummy%type := 'X';
begin
insert into some_table
select dummy from dual where dummy = some_var;
end;
/
... because the insert has access to the PL/SQL some_var.
In an SQL context this will error:
select * from dual where dummy = some_var;
... because it's looking for a column called SOME_VAR, and there isn't one.
If you do this instead:
select * from dual where dummy = :some_var;
... the some_var is now a client-managed bind variable. If you execute that you'll either be prompted for the bind value, or given a not-all-variables-bound error, or bind-variable-not-declared, or similar, depending on your client.
If you only do an explain plan of that though, e.g. with
set auto trace traceonly explain
select * from dual where dummy = :some_var;
... then the bind variable doesn't necessarily have to be populated for the plan to be calculated. Some clients may still complain and want a bind value, but the parser would be OK with it - enough to produce a plan anyway. Though not able to take advantage of bind variable peeking or histograms etc.
For example, SQL Developer happily produces a plan for your original sample query if both references are turned into bind variables, just the insert ... part of the block is selected, and you press Explain Plan (F10).
I'm not sure what you read, but you're mixed up on a few things here.
Your DATE_IN is a variable. You don't need to type 'VARIABLE' anywhere to declare a variable, all you need is the name of the variable and the datatype.
All of the below are legitimate variables in PL/SQL (although poorly named).
variable_1 NUMBER;
variable_2 VARCHAR2(100);
variable_3 DATE;
It's hard to tell what you're doing in your code without seeing it all. Do you have two DATE_IN variables declared within the same block? Is DATE_IN the name of a column in your table?
If you have a column named DATE_IN in table1 or table2, that's likely your problem. Oracle doesn't know if you want to use your variable or your column, and it will always default to the column name. Your function would be expecting a DATE and receiving a column, hence the error.
I am new to Oracle 10g and know that in MS SQL Server I can create a procedure such as the one below to generate an output record. Is this possible with a standard Oracle Procedure or do I need to use a package/function?
CREATE PROCEDURE SAMPLE_STORED_PROCEDURE
#USERNAME varchar(10)
AS
BEGIN
SELECT NAME as Output from Employee where Username = #USERNAME
END
GO
Thank you
Assuming that username is unique in employee (which seems like a reasonable guess to me), you probably want a function
CREATE OR REPLACE FUNCTION function_name( p_username IN employee.username%type )
RETURN employee.name%type
IS
l_name employee.name%type;
BEGIN
SELECT name
INTO l_name
FROM employee
WHERE username = p_username;
RETURN l_name;
END function_name;
You could also use a procedure with an OUT parameter
CREATE OR REPLACE PROCEDURE procedure_name( p_username IN employee.username%type,
p_name OUT employee.name%type )
AS
BEGIN
SELECT name
INTO p_name
FROM employee
WHERE username = p_username;
END procedure_name;
Generally, in PL/SQL you'd want to use a function whenever you want to write some sort of "getter" that reads data from the database and a procedure whenever you want to write some sort of "setter" that writes data to the database. So a function would make more sense here. Both functions and procedures can and should be organized into packages that allow you to group together related functionality. It would probably make sense, for example, to have a package that groups together all the functions and procedures related to adding, modifying, deleting, and reading information about employees.
If my original guess that username is not unique in employee is incorrect and you expect your select statement to return multiple rows, there are a number of different choices depending on exactly what you're going to be doing with that data. You can write a function that returns a sys_refcursor. You can write a function that returns a collection. You can write a pipelined table function. Without knowing more, however, it's impossible to know which of these options would make more sense in your case.