Select Command in a procedure in SQL (ORACLE) - sql

I have this piece of code:
CREATE OR REPLACE PROCEDURE MOSTRA_LOG (P_CODIGO_CLIENTE CLIENTE.CODIGO_CLIENTE%TYPE,N_LOG OUT, CODIGO_CLIENTE OUT, EMAIL OUT,TELEFONE OUT, NOME OUT, TIPO OUT) IS
BEGIN
SELECT (N_LOG, CODIGO_CLIENTE, EMAIL ,TELEFONE, NOME, TIPO) FROM LOG WHERE
(CODIGO_CLIENTE=P_CODIGO_CLIENTE);
END;
I know a procedure isn't the optimal thing to do but it is required in my case. It complains about an into clause that needs to be added, however I have nothing to "select into".
All I want basically is to show on the screen everything from the table LOG with the CODIGO_CLIENTE sent as a parameter.
EDIT:
After a suggestion I changed it to this:
CREATE OR REPLACE PROCEDURE MOSTRA_LOG (P_CODIGO_CLIENTE CLIENTE.CODIGO_CLIENTE%TYPE, N_LOGO OUT INTEGER, CODIGO_CLIENTEO OUT INTEGER, EMAILO OUT VARCHAR(30), TELEFONEO OUT NUMERIC(9,0), NOMEO OUT VARCHAR(50), TIPOO OUT VARCHAR(20)) IS
BEGIN
SELECT (N_LOG into N_LOGO, CODIGO_CLIENTE into CODIGO_CLIENTEO, EMAIL into EMAILO, TELEFONE into TELEFONEO, NOME into NOMEO, TIPO into TIPOO) FROM LOG WHERE (CODIGO_CLIENTE=P_CODIGO_CLIENTE);
END;
It's still going wrong saying it expects a symbol when it encountered "(".

Try this way using collections:
First you must create the collection where you will store your LOG query results:
CREATE TYPE LOG_OBJECT AS OBJECT
(CODIGO_CLIENTEO INTEGER,
EMAILO VARCHAR (30),
TELEFONEO NUMERIC (9, 0),
NOMEO VARCHAR (50),
TIPOO VARCHAR (20));
CREATE TYPE LOG_TABLE AS TABLE OF LOG_OBJECT;
Later you create your procedure like this:
CREATE OR REPLACE PROCEDURE MOSTRA_LOG (P_CODIGO_CLIENTE CLIENTE.CODIGO_CLIENTE%TYPE, LOG_T OUT LOG_TABLE) IS
BEGIN
SELECT LOG_OBJECT(N_LOG, CODIGO_CLIENTE, EMAIL ,TELEFONE, NOME, TIPO) BULK COLLECT INTO LOG_T FROM LOG WHERE
(CODIGO_CLIENTE=P_CODIGO_CLIENTE);
END;
When procedure is created you can use the collection like it was a table. For example below anonymus block prints total of rows of your query on LOG table:
DECLARE
LOG_T LOG_TABLE;
total NUMBER;
BEGIN
mostra_log (LOG_T); --invoke the procedure and fill the collection
SELECT COUNT (*) INTO total FROM TABLE (LOG_T); -- get count of collection returned
DBMS_OUTPUT.put_line (total);
END;

Related

Insert Data Into Table with Stored Procedure in Oracle SQL

I am working through a homework assignment and am stuck on a problem with stored procedures.
I have the following tables:
create table Restaurant(rID int, name varchar2(100), address varchar2(100), cuisine varchar2(100));
create table Reviewer(vID int, name varchar2(100));
create table Rating(rID int, vID int, stars int, ratingDate date);
For my stored procedure I need to create, I get an input of a restaurant name (unique), reviewer name(unique), star rating, and review date. I need to update the Rating table with the proper information, and add a new reviewer to the Review table if they have not previously existed in the table.
In order to start my stored procedure, I wanted to start with just creating a new table called newReview to get the inputs stored in a new table, before re-writting to update the existing tables.
Here is the newReview Table
CREATE TABLE newReview(
RestaurantName VARCHAR(100),
UserName VARCHAR(100),
Stars Int,
RatingDate Date
)
This is my AddNewReview procedure, which compiles with success:
CREATE OR REPLACE PROCEDURE AddNewReview(
RESTAURANTNAME IN VARCHAR2 (100)
, USERNAME IN VARCHAR2 (100)
, Stars IN NUMBER
, RATINGDATE IN DATE
) AS
BEGIN
INSERT INTO newReview VALUES (RestaurantName, UserName, Stars, RatingDate);
END AddNewReview;
;
However, when I run the stored procedure with inputs as such,
BEGIN
AddNewReview ('Rangoli', 'Sarah M.', 4, '2020-11-21');
END;
I get the following error:
Error starting at line : 20 in command -
BEGIN
AddNewReview ('Rangoli', 'Sarah M.', 4, '2020-11-21');
END;
Error report -
ORA-06550: line 2, column 5:
PLS-00905: object TOCONN22.ADDNEWREVIEW is invalid
ORA-06550: line 2, column 5:
PL/SQL: Statement ignored
06550. 00000 - "line %s, column %s:\n%s"
*Cause: Usually a PL/SQL compilation error.
*Action:
I have tried defining the date input as DATE '2020-11-21' and also switching the single quote to double. Where am I going wrong, as this is the first stored procedure I am writing.
Try to change Stars data type from NUMBER to Int
AS:
CREATE OR REPLACE PROCEDURE AddNewReview(
RESTAURANTNAME IN VARCHAR2 (100)
, USERNAME IN VARCHAR2 (100)
, Stars IN NUMBER
, RATINGDATE IN DATE
) AS
BEGIN
INSERT INTO newReview VALUES (RestaurantName, UserName, Stars, RatingDate);
END AddNewReview;
;
to
CREATE OR REPLACE PROCEDURE AddNewReview(
RESTAURANTNAME IN VARCHAR2 (100)
, USERNAME IN VARCHAR2 (100)
, Stars IN Int
, RATINGDATE IN DATE
) AS
BEGIN
INSERT INTO newReview VALUES (RestaurantName, UserName, Stars, RatingDate);
END AddNewReview;
;
You need to look up the ids for the insertion:
CREATE OR REPLACE PROCEDURE AddNewReview (
in_RESTAURANTNAME IN VARCHAR2(100),
in_USERNAME IN VARCHAR2(100),
in_Stars IN NUMBER,
in_RATINGDATE IN DATE
) AS
BEGIN
INSERT INTO newReview (rid, vid, stars, RatingDate)
SELECT r.id, rr.id, in_stars, in_RatingDate
FROM restaurant r JOIN
reviewer rr
ON r.name = in_RestaurantName AND
rr.name = in_UserName
END AddNewReview;
This joins to the reference tables to get the ids you need. It will not insert the review if either name does not match. Your question doesn't specify what to do in that case, so that seems like reasonable behavior.
Note that the parameters are named so they don't conflict with column names. And this has listed all columns for the insert -- both are best practices.
Parameters are defined only by name and data type, they do not contain size specification. So your procedure needs:
create or replace procedure addnewreview(
restaurantname in varchar2
, username in varchar2
, stars in int
, ratingdate in date
...
You need to use To_Date function while calling the stored procedure, like below
BEGIN
AddNewReview ('Rangoli', 'Sarah M.', 4, TO_DATE('2020-11-21','YYYY-MM-DD'));
END;

Why is RETURN QUERY returning a string instead of a TABLE

This MWE is NOT how you would typically solve this problem, however, it is as simple as I can explain the problem I am encountering. I am merely trying to point out 2 things
I am doing more than simply returning the contents of a Table
What is being returned is NOT being returned as a Table but a String
Supporting SQL Statements:
DROP DATABASE IF EXISTS test;
CREATE DATABASE test;
\c test
CREATE TABLE credit_card(
id BIGSERIAL PRIMARY KEY,
balance BIGINT
);
Functions:
CREATE FUNCTION get_credit_card(
p_id BIGINT
)
RETURNS TABLE(
id BIGINT,
balance BIGINT
)
AS $$
DECLARE
BEGIN
RETURN QUERY
SELECT
credit_card.id,
credit_card.balance
FROM
credit_card
WHERE
credit_card.id = p_id;
END $$ LAnguage 'plpgsql';
CREATE FUNCTION pay_with_card(
p_id BIGINT,
p_amount BIGINT
)
RETURNS TABLE(
id BIGINT,
balance BIGINT
)
AS $$
DECLARE
v_balance BIGINT;
BEGIN
SELECT
credit_card.balance
FROM
credit_card
INTO
v_balance
WHERE
credit_card.id = p_id;
IF v_balance < p_amount
THEN
RETURN;
END IF;
UPDATE
credit_card
SET
balance = credit_card.balance - p_amount;
RETURN QUERY
SELECT get_credit_card (p_id);
END $$ LAnguage 'plpgsql';
Populate Table and Call function:
INSERT INTO credit_card
(balance)
VALUES
(100);
SELECT
pay_with_card (1, 100);
Error:
DROP DATABASE
CREATE DATABASE
You are now connected to database "test" as user "postgres".
CREATE TABLE
CREATE FUNCTION
CREATE FUNCTION
INSERT 0 1
psql:test.sql:74: ERROR: structure of query does not match function result type
DETAIL: Returned type record does not match expected type bigint in column 1.
CONTEXT: PL/pgSQL function pay_with_card(bigint,bigint) line 24 at RETURN QUERY
It took me a long time to figure out that pay_with_card is returning a String, or what appears to be a String, instead of a TABLE(id BIGINT, balance BIGINT). With the Python psycopg2 library, the returned query is
[('(1,100)'),]
So my entire code is breaking because I can't get the values (unless I hack it and use string manipulation.
Question:
How can I fix it so that it returns the correct query like so
[(1,100),]
An alternative to the hint in horse_with_no_name's comment, you can replace
RETURN QUERY
SELECT get_credit_card (p_id);
with
RETURN QUERY SELECT (get_credit_card(p_id)).*;
You need some way of expanding the returned record back into its constituent fields. (I think horse’s SELECT * … has the same effect.)

How to use in statement with nested table

Hey there I have a function, and part of the function is to make sure that the selected value is within the passed in table of varchar2s. To start I declare a varchar2 table type like so.
create or replace type Varchar2Table is table of varchar2(200)
Then I have the function which accepts the nested table parameter and has a select statement on them.
function SelectPeople(inputNames Varchar2Table) return People
begin
--stuff
select * from person_table where name in inputNames; --line of interest
--more stuff
end;
This doesn't seem to work though, I get the following error:
ORA-00932: inconsistent datatypes: expected NUMBER got
ENGSPL5.VARCHAR2TABLE
Any suggestions?
The TABLE operator allows nested tables to be used in SQL statements. The function was also missing an IS and an INTO.
create or replace type Varchar2Table is table of varchar2(200);
create table person_table(id number, name varchar2(100));
create or replace function SelectPeople(inputNames Varchar2Table) return number
is --Missing "IS".
type numberTable is table of number; --Need a collection to store results.
numbers numberTable;
begin
select id
bulk collect into numbers --Missing "INTO".
from person_table
where name in (select column_value from table(inputNames)); --Missing "TABLE".
--Alternatively a multiset condition can be used.
--where name member of inputNames;
--Dummy return value to make the function compile.
return 1;
end;
/

How to resolve the component must be declared using oracle Types

I have two oracle types like
create or replace
TYPE T_EMPLOYEE
AS TABLE OF O_EMPLOYEE;
And my O_EMPLOYEE TYPE is
create or replace
TYPE O_EMPLOYEE
AS OBJECT
(
EMP_NAME VARCHAR2(50),
EMP_ID VARCHAR2(50),
EMP_DES VARCHAR2(50)
);
I am using this as an input in a store procedure where i need to check for the validation of name,id and designation. Using following i can convert the table in a select statement.
TABLE(CAST( I_T_EMPLOYEE AS T_EMPLOYEE)) emp,
but I tried to read the value like like T_EMPLOYEE.EMP_NAME, it is saying componenet EMP_NAME must be decalred.
can any one help?
thank you shabilan. There are two ways we can retrieve the value. first one
By writing following in store procedure. you need to declare the variables for
IS
V_EMP_NAME VARCHAR2(50),
V_EMP_ID VARCHAR2(50),
V_EMP_DES VARCHAR2(50),
BEGIN
SELECT PROCESS_TYPE,REQUEST_TYPE,STATUS,EFFECTIVE_DATE_RANGE
INTO V_EMP_NAME, V_EMP_ID, V_EMP_DES
FROM TABLE(CAST(I_T_EMPLOYEE AS T_EMPLOYEE)) ITE;
--validation part
if(V_EMP_NAME is null or V_EMP_ID or V_EMP_DES ) then RAISE NULL_DATA;
END IF;
This one will fail if you pass multiple Object Types in store procedure like
T_E_I :=T_EMPLOYEE(O_EMPLOYEE('Test','1234','manager'),
O_EMPLOYEE('Test','1234','manager'));
GET_DATA_PKG.EMPLOYEE_ORDER_BY_ROLE(T_E_I, :o_error_code,
:o_error_message);
2nd approach handles the multiple inputs. this is done using cursor.you need to declare the variables for
IS
OEM O_EMPLOYEE := O_EMPLOYEE(null,null,null);
c_EMP_NAME OEM.EMP_NAME%type,
c_EMP_ID OEM.EMP_ID%type,
c_EMP_DES OEM.EMP_DES%type,
Cursor emp_role_curs is
SELECT PROCESS_TYPE,REQUEST_TYPE,STATUS,EFFECTIVE_DATE_RANGE
FROM TABLE(CAST(I_T_EMPLOYEE AS T_EMPLOYEE)) ITE;
BEGIN
OPEN emp_role_curs;
loop
fetch emp_role_curs into C_EMP_NAME, C_EMP_ID, C_EMP_DES
FROM TABLE(CAST(I_T_EMPLOYEE AS T_EMPLOYEE)) ITE;
--validation part
if(V_EMP_NAME is null or V_EMP_ID or V_EMP_DES ) then RAISE NULL_DATA;
END IF;
EXIT WHEN emp_role_curs%notfound;
END LOOP;
CLOSE emp_role_curs;
I hope this is useful if some one want to read the object type as a table.

SQL Trigger creating trouble

I have the movie executive table movieexec with the columns
(name varchar2(20), address varchar2(20), cert# number(10), networth float)
I want to create a trigger which calculates the average networth, and if it exceeds some amount say 400000 then further insertion should not be possible and a error message should be displayed.
I implemented the following code:
CREATE OR REPLACE TRIGGER pronet
AFTER INSERT
ON movieexec
FOR EACH ROW
DECLARE netavg float;
BEGIN
SELECT AVG(networth) INTO netavg FROM movieexec;
IF(netavg>400000) THEN
RAISE_APPLICATION_ERROR(-20000,'average limit reached, cannot insert');
ENDIF;
END
But the below error occurs
ERROR at line 7: PLS-00103: Encountered the symbol ";" when expecting one of the following:
if
5. DECLARE netavg float;
6. BEGIN
7. **SELECT AVG(networth) INTO netavg FROM movieexec;**
8. IF(netavg>400000) THEN
9. RAISE_APPLICATION_ERROR(-20000,'average limit reached, cannot insert');
kindly assist.
Create table as
CREATE TABLE movieexec
(
name VARCHAR2 (20),
address VARCHAR2 (20),
cert_no NUMBER (10),
networth FLOAT
);
and create trigger as
CREATE OR REPLACE TRIGGER pronet
AFTER INSERT
ON movieexec
FOR EACH ROW
DECLARE
netavg FLOAT;
BEGIN
SELECT AVG (networth)
INTO netavg
FROM movieexec;
IF (netavg > 400000)
THEN
raise_application_error (-20000,
'average limit reached, cannot insert'
);
END IF;
END;
/
There is no safe way to do this with a trigger, as there will always be a way to subvert it.
In Oracle EE you might create a fast refresh on commit materialised view to store the aggregation, and place a check constraint on it.
Also, never use the SYS or SYSTEM accounts for creating your own objects.