Select statement inside an Oracle stored procedure - sql

I am trying to self-teach myself SQL. I am working on calling a simple select statement from a stored procedure in oracle.
I have created an employee database with 2 tables; employees and department. I want a select statement which returns all employees from a certain department.
This is what I have so far and I can't figure out where I am going wrong
create or replace procedure user_empdepart (depart_name varchar(40))
AS
BEGIN
SELECT emp_name FROM employee JOIN department ON department.departmentID =
employee.departmentID
WHERE depart_name = 'research';
END;
And then I hope to call the above by;
exec user_empdepart(research);
I am using SQL Developer Oracle
I get the following error message:
Error(99,50): PLS-00103: Encountered the symbol "DEPARTMENT" when expecting one of the following: , ; for group having intersect minus order start union where connect

I think you need this -
create or replace procedure user_empdepart (depart_name varchar(40))
AS
DECLARE dept_name varchar(200);
dept_name := depart_name;
BEGIN
SELECT emp_name FROM employee JOIN department ON department.departmentID = employee.departmentID
WHERE depart_name := dept_name;
END;

In Oracle database, SQL statements executed directly in the client (like SQL-Developer or SQLPlus) are treated as plain, ordinary SQL.
But if you are going to use SQL commands in a procedure or a function, Oracle treats them as PL/SQL commands, not SQL.
For simple INSERT, DELETE and UPDATE commands the syntax in PL/SQL is the same as in SQL, however for SELECT the syntax is slightly different: PL/SQL SELECT INTO Statement
You must use SELECT expression-list INTO variables-list/record FROM ..... syntax. You cannot use SELECT expression-list FROM ....., this generates a syntax error.

Related

SQL command not ended properly at pkg_test

I have to write a stored procedure that starts copying the data from a table 'company' into a staging table 'company_stg' if no records for that date are present in it.
I have the following code :
CREATE OR REPLACE
PACKAGE BODY PKG_TEST AS
PROCEDURE SP_BILLING AS
BEGIN
EXECUTE IMMEDIATE 'SELECT * FROM COMPANY INTO COMPANY_STG
WHERE NOT EXISTS (SELECT * FROM COMPANY_STG WHERE AS_OF_DATE = "2023-02-08")';
END;
END PKG_TEST;
I AM GETTING THE ERROR "SQL COMMAND NOT PROPERLY ENDED"
company * company_stg have as_of_date as a column. rest all are same.
please help me with this
I have also tried
if not exists (SELECT * FROM COMPANY_STG WHERE AS_OF_DATE = "2023-02-08")
then
select from company into company_stg
So many things look bad in that piece of code...
First, why use dynamic SQL execute immediate? It's best to avoid dynamic SQL as much as possible because it leads to runtime errors and requires pretty much instrumentation so that it may be debugged. Generally you use dynamic SQL when you do not know beforehand the name of a table it will operate on, which is not the case for you. You definitely know you have to work with tables COMPANY and COMPANY_STG. Is it not so?
Then, it doesn't look like you have read the manual to see an insert select.
When you insert into a table, it's best to give the list of columns into which you actually insert data. If one alters that table and adds one or more than one column, the insert which does not have the list of columns will crash.
Thus, to insert into COMPANY_STG data from COMPANY, the SQL should look like below:
insert into company_stg(
... ---- here should be the list of columns you insert data into
)
select
... --- here should the source columns you are willing to insert
from company c
where not exists (
select 1
from company_stg cs
where cs.as_of_date= --- what is the condition??? I did not understand
)
;
You have not given the structures for those tables, so that I can't give you the columns to select and to insert into. Nor did I really understand what the condition for inserting data should be.
SELECT does not perform a copy and SELECT * FROM COMPANY INTO COMPANY_STG is not valid syntax. You want to use an INSERT statement to do that (and check if there is any row first):
CREATE OR REPLACE PACKAGE BODY PKG_TEST AS
PROCEDURE SP_BILLING
AS
BEGIN
DECLARE
v_staged_count NUMBER;
BEGIN
SELECT 1
INTO v_staged_count
FROM COMPANY_STG
WHERE AS_OF_DATE = DATE '2023-02-08'
FETCH FIRST ROW ONLY; -- We don't care how many rows so stop after finding
-- the first one.
-- Stop as rows have been found.
RETURN;
EXCEPTION
WHEN NO_DATA_FOUND THEN
-- Continue
NULL;
END;
INSERT INTO company_stg
SELECT *
FROM COMPANY;
END;
END PKG_TEST;
/
fiddle

Wrong number or types of arguments

Create or replace procedure sp_create_tables as
Lv_str varchar2(1000);
Begin
For I in (select distinct(deptno) from emp) loop
Lv_str:='create table deptno'||I||' select * from emp where
1=2';
Execute immediate lv_str;
End loop;
End;
From what I can understand, your question is probably "why the procedure throws this compilation error"
PLS-00306: wrong number or types of arguments in call to '||'
The reason is that the implicit cursor loop variable I refers to the set of records from the query and not the deptno itself. In order to refer to the deptno, you should use <loop_variable>.deptno. Also 2 other things you should change: The as keyword is missing and DISTINCT is a keyword and you are using it as a function, which works because of default parentheses, but is not the right way to use it.
Create or replace procedure sp_create_tables as
Lv_str varchar2(1000);
Begin
For I in (select distinct dept from emp) loop
Lv_str:='create table deptno'||I.deptno||' as select * from emp where
1=2';
Execute immediate lv_str;
End loop;
End;
In this line of code I is an implicit rowtype variable, with a data structure defined by the projection of the driving select statement:
For I in (select distinct(deptno) from emp) loop
But you are attempting to reference it in your dynamic SQL as though it were an attribute. You need to use a column name instead.
Create or replace procedure sp_create_tables as
Lv_str varchar2(1000);
Begin
For I in (select distinct (deptno) from emp) loop
Lv_str:='create table deptno'|| I.deptno ||
' as select * from emp where 1=2';
Execute immediate lv_str;
End loop;
End;
Incidentally there is a bug in your dynamic SQL statement. The correct syntax is CREATE TABLE ... AS SELECT .... Dynamic SQL is hard because the compiler can't validate the bits of code in strings. Consequently what should be compilation errors manifest themselves as runtime errors. You will find it helpful to instrument your code with some logging (or dbms_output.put_line()) to record the assembled statement before it runs. It makes debugging a lot easier.
" i have got a error saying -01031 insufficient priviliges"
So what this means is your authorisation to create a table was granted through a role. The Oracle security model does not allow us to build PL/SQL programs - or views - using privileges granted through a role. This includes PL/SQL executing DDL through dynamic SQL. You need a DBA user to grant CREATE TABLE to your user directly.

How to return a set of results from one Oracle subprogram to another?

This is a simplified version of what I'm trying to do, but let's say I want to select all employees whose manager is in a given department. I could have a SQL query like the following:
SELECT employees.id
FROM employees
WHERE employees.manager IN (SELECT managers.id
FROM managers
WHERE managers.dept = 12)
;
But let's say I want to abstract the manager subquery into a PL/SQL subprogram. How do I do that?
The stored procedures I've worked with (which are mostly written by other developers) tend to have out parameters that get mapped by PHP calling code into a PHP array. I don't really have any experience of calling one stored procedure from another.
What I'd like to do is to have something like this:
SELECT employees.id
FROM employees
WHERE employees.manager IN my_stored_procedure(12)
;
and then my_stored_procedure would output the set of manager IDs for the input parameter (which is 12 in this example).
It is not possible to do exactly as you have posted, but if the selection of managers are not straightforward, you could abstract it through a view or make use of a function that returns a table, like this:
SELECT employees.id
FROM employees
WHERE employees.manager IN (SELECT * from TABLE(get_managers_from_dept(12)));
In this link there is an example of that approach:
Function or Procedure for an IN clause
To Call A stored Proc from another Stored Proc you just need to call it from the Main Proc as mentioned below. This Main Proc can be called /initiated by a PHP Code.
PROCEDURE some_sp
AS
BEGIN
some_other_sp('parm1');
END;
Although less straight-forward, You can do accomplish it by using dynamic sql. This is the structure of your stored procedure. It returns a comma separated list of manager_ids for a given department.
CREATE OR REPLACE FUNCTION my_stored_procedure(
p_dept NUMBER)
RETURN VARCHAR2
IS
v_manager_list VARCHAR2(1000);
BEGIN
SELECT m.id INTO v_manager_list FROM managers m WHERE m.dept = p_dept;
RETURN '('||v_manager_list||')';
EXCEPTION
WHEN NO_DATA_FOUND THEN
RETURN '(NULL)';
END;
/
Now you cannot use this to directly query as ...IN my_stored_procedure(12),
rather you must use a dynamic fetch into a collection.
SET SERVEROUTPUT ON;
DECLARE
TYPE v_emp_type
IS
TABLE OF employees.id%TYPE;
v_emp v_emp_type;
BEGIN
EXECUTE IMMEDIATE 'SELECT employees.id
FROM employees
WHERE employees.id IN '|| my_stored_procedure(100) BULK COLLECT INTO v_emp ;
FOR i IN v_emp.FIRST..v_emp.LAST
LOOP
DBMS_OUTPUT.PUT_LINE(v_emp(i));
END LOOP;
END;
/

Function returning TABLE and the JOIN in SELECT query?

I want to create a local FUNCTION which returns result as NESTED TABLE in my PROCEDURE. Then, I wish to JOIN the nested table with another table in a SELECT query like this:
PROCEDURE TEST_DEPID (SOR IN OUT SYS_REFCURSOR) AS
TYPE TAB IS TABLE OF HR.EMPLOYEES.SALARY%TYPE
INDEX BY BINARY_INTEGER;
FUNCTION GET_SALARY (P_DEPARTMENT_ID NUMBER)
RETURN TAB
IS RETURN_TBL TAB;
BEGIN
SELECT SALARY
BULK COLLECT INTO RETURN_TBL
FROM HR.EMPLOYEES WHERE DEPARTMENT_ID = TRIM(P_DEPARTMENT_ID);
RETURN RETURN_TBL;
END GET_SALARY;
BEGIN
OPEN SOR FOR
SELECT * FROM HR.EMPLOYEES JOIN TABLE (GET_SALARY('60')) B ON A.SALARY = B.SALARY;
END;
And the errors I got are:
[1]:(Error): PLS-00231: function 'GET_SALARY' may not be used in SQL
[2]:(Error): PL/SQL: ORA-00904: : invalid identifier
[3]:(Error): PL/SQL: SQL Statement ignored
Please give me some advice.
A local function cannot be used inside a SELECT. Only global functions will work.
Oracle uses 2 engines running SQL and PL/SQL. Running your SELECT the SQL engine can access all functions, but only if they are public known. The local function is not known to the public.

PLS-00103: Encountered the symbol "end-of-file" when expecting one of the following: ;

I am running the following script -
BEGIN
select department_name
from egpl_department
where department_id in (select department_id
from egpl_casemgmt_activity);
END ;
And got the Error -
PLS-00103: Encountered the symbol "end-of-file" when
expecting one of the following:
;
In a PL/SQL block select statement should have an into clause:
DECLARE
v_department egpl_department.department_name%type;
BEGIN
select department_name
into v_department
from egpl_department
where department_id in (select department_id from egpl_casemgmt_activity);
-- Do something useful with v_department
END;
PLS-00103 always means the compiler has hurled because we have made a syntax error. It would be really neat if the message text said: You have made a syntax error, please check your code but alas it doesn't.
Anyway, in this case the error is that in PL/SQL select statements must populate a variable. This is different from the behaviour of say T-SQL. So you need to define a variable which matches the projection of your query and select INTO that variable.
Oracle's documentation is comprehensive and online. You can find the section on integrating SQL queries into PL/SQL here. I urge you to read it, to forestall your next question. Because once you have fixed the simple syntax bloomer you're going to hit TOO_MANY_ROWS (assuming you have more than one department).
In PL/SQL you cannot just select some data. Where is the result supposed to go?
Your options are:
Remove BEGIN and END and run the SELECT with SQL*plus or some other tool that can run a SQL statement and present the result somewhere.
Use SELECT department_name INTO dep_name to put the result into a PL/SQL variable (only works if your SELECT returns a single row)
Use SELECT department_name BULK COLLECT INTO dep_name_table to put the result into a PL/SQL table (works for several rows)
Or maybe you can describe what you're trying to achieve and in what environment you want to run the SQL or PL/SQL code.
To avoid the too_many_rows problem, you could use a cursor, something like this (I haven't tested this, but along these lines )
DECLARE
v_department egpl_department.department_name%type;
cursor c_dept IS
select department_name
into v_department
from egpl_department
where department_id in (select department_id from egpl_casemgmt_activity)
order by department_name;
BEGIN
OPEN c_dept;
FETCH c_dept INTO v_department;
CLOSE c_dept;
-- do something with v_department
END;
This will put the first value it finds in the table into v_department. Use the ORDER BY clause to make sure the row returned would be the one you required, assuming there was the possibility of 2 different values.
Most people would not consider the call to be the issue,
but here's an amusing bug in Oracle Sql Developer that may emulate the issue..
exec dbowner.sp1 ( p1, p2, p3); -- notes about the fields
Error report -
ORA-06550: line 1, column 362:
PLS-00103: Encountered the symbol "end-of-file" when expecting one of the following:
begin case declare end exception exit for goto if loop mod
null pragma raise return select update while with
<< close current delete fetch lock insert
open rollback savepoint set sql execute commit forall merge
pipe
06550. 00000 - "line %s, column %s:\n%s"
*Cause: Usually a PL/SQL compilation error.
*Action:
exec dbowner.sp1 ( p1, p2, p3);
-- notes about the fields
PL/SQL procedure successfully completed.
DECLARE is only used in anonymous PL/SQL blocks and nested PL/SQL blocks.
You do not need to use the DECLARE key word before you 'introduce' a new variable in a Procedure block, unless .... the procedure is a nested PL/SQL block.
This is an example of how you would declare a variable without the 'DECLARE' Key word below.
eg.;
CREATE OR REPLACE PROCEDURE EXAMPLE( A IN NUMBER, B OUT VARCHAR2 )
IS
num1 number;
BEGIN
num1:=1;
insert into a (year) values(7);
END;
This question/answer explains it better
create procedure in oracle