PostgreSQL - How call a procedure with a varchar parameter - sql

I am trying to create a SP in Postgresql 12:
CREATE OR REPLACE PROCEDURE trans_buy(
_name_client varchar(25),
_id_product smallint,
_mount smallint
)
LANGUAGE plpgsql
AS
$$
BEGIN
INSERT INTO invoices (cliente) VALUES(_name_client);
INSERT INTO invoices_details (id_invoice, id_product, mount) VALUES (1, _id_product, _mount, 100);
END
$$
But when I try to call this sp like so:
CALL trans_buy('james', 3, 10)
I receive this error message:
doesnt exists the stored procedure << trans_buy(unknown, integer, integer) >>
HINT: No procedure matches the name and types of arguments. It may be necessary to add explicit type conversion.

You probably need to explicitly cast the integer values to smallints:
call trans_buy('james', 3::smallint, 10::smallint);
I assume that the target columns in invoice_details are smallint as well. An alternative is to have the procedure accept ints, and cast at insert time:
CREATE OR REPLACE PROCEDURE trans_buy(
_name_client varchar(25),
_id_product int,
_mount int
) LANGUAGE plpgsql
AS $$
BEGIN
INSERT INTO invoices (cliente) VALUES(_name_client);
INSERT INTO invoices_details (id_invoice, id_product, mount) VALUES (1, _id_product::smallint, _mount::smallint);
END
$$
Note that your second insert had 4 values for 3 columns. I (attempted to) adjust that.
One should also highlight that you don't really need a subquery do this in Postgres. You can run multiple DML operations in a single query, using common-table-expressions:
with
-- CTE: query parameters
params(cliente, id_product, mount) as (values ('james', 3, 10)),
-- CTE: insert to invoices
inv as (insert into invoices (cliente) select cliente from params)
-- insert to invoice details
insert into invoice_details (id_invoice, id_product, mount)
select 1, id_product, mount from params

Related

Insert into a table using a stored procedure

I have this simple stored procedure, where it would add a column to my Orders table
create or replace PROCEDURE ADD_ORDER
(
CUSTOMER_ID IN NUMBER
, NEW_ORDER_ID OUT NUMBER
) AS
DECLARE
NEW_ORDER_ID := MAX(ORDERS.ORDER_NO) + 1;
BEGIN
INSERT INTO ORDERS(ORDER_NO, REP_NO, CUST_NO, ORDER_DT, STATUS)
VALUES( NEW_ORDER_ID, 36, CUSTOMER_ID, CURDATE(), 'C')
END ADD_ORDER;
It is saying the the declare part is not at the correct place (I think), and also it should not end there. Here is what it is saying at the error screen:
Error(6,1): PLS-00103: Encountered the symbol "DECLARE" when expecting one of the following:
begin function pragma procedure subtype type current cursor delete exists prior external language The symbol "begin was inserted before "DECLARE" to continue.
Error(11,1): PLS-00103: Encountered the symbol "END" when expecting one of the following: , ; return returning
Can anyone tell me what is going wrong here ?
As has been mentioned, it is a bad idea to select the maximum order number and then use that to insert a row. If two processes do this at the same time, they try to insert rows with the same order number.
Better use Oracle's built-in features SEQUENCE or IDENTITY.
Here is how you could create the table:
CREATE TABLE orders
(
order_no NUMBER(8) GENERATED ALWAYS AS IDENTITY,
rep_no NUMBER(3) DEFAULT 36 NOT NULL,
cust_no NUMBER(8) NOT NULL,
order_dt DATE DEFAULT SYSDATE NOT NULL,
status VARCHAR2(1) DEFAULT 'C' NOT NULL
);
And this is what your procedure would look like then:
CREATE OR REPLACE PROCEDURE add_order
(
in_cust_no IN NUMBER,
out_order_no OUT NUMBER
) AS
BEGIN
INSERT INTO ORDERS(cust_no) VALUES (in_cust_no)
RETURNING order_no INTO out_order_no;
END add_order;
Demo: https://dbfiddle.uk/?rdbms=oracle_18&fiddle=4b49723c15eb810c01077286e171bc95
There is a syntax error in your code.
NEW_ORDER_ID := MAX(ORDERS.ORDER_NO) + 1; --not be used liked it.
Use below code
create or replace PROCEDURE ADD_ORDER
(
CUSTOMER_ID IN NUMBER
, NEW_ORDER_ID OUT NUMBER
) AS
V_NEW_ORDER_ID NUMBER;
BEGIN
SELECT NVL(MAX(ORDER_NO),0)+1 INTO V_NEW_ORDER_ID FROM ORDERS;
INSERT INTO ORDERS(ORDER_NO, REP_NO, CUST_NO, ORDER_DT, STATUS)
VALUES( V_NEW_ORDER_ID, 36, CUSTOMER_ID, CURDATE(), 'C');
NEW_ORDER_ID:=V_NEW_ORDER_ID;
/*
* CURDATE() -> I am assuming it is user defined function. You can also use SYSDATE, CURRENT_DATE istead of CURDATE()
* OUT Parameter is a write-only parameter. You cannot read value from OUT Parameter
*/
END ADD_ORDER;
Few things need to be correct.
If you're expecting to write a PROCEDURE or a FUNCTION you don't have to use the DECLARE keyword. In writing a test script or something, you should use the DECLARE keyword to declare variables.
When writing a procedure,
All the parameters should be inside the brackets.
Variables should define between AS and BEGIN keywords and should give the datatype.
If you need to fetch the MAX number of ORDERS TAB you have to write a SQL query for that. Because the MAX function only can be used inside a SQL. Additionally, if you interest there is an in-built feature call SEQUENCE in ORACLE which can use for NEW_ORDER_ID. You can check with the link below.
adding a sequence for oracle plsql
I did some changes to your code. Hope it's working fine now. Please take a visit here.
CREATE or REPLACE PROCEDURE ADD_ORDER (
CUSTOMER_ID IN NUMBER
NEW_ORDER_ID OUT NUMBER
) AS
CURSOR get_max_order_no IS
SELECT MAX(order_no)
FROM ORDERS;
rec_ NUMBER := 0;
BEGIN
OPEN get_max_order_no;
FETCH get_max_order_no INTO rec_;
CLOSE get_max_order_no;
NEW_ORDER_ID := rec_ + 1;
INSERT INTO ORDERS
(ORDER_NO, REP_NO, CUST_NO, ORDER_DT, STATUS)
VALUES
(NEW_ORDER_ID, 36, CUSTOMER_ID, SYSDATE, 'C');
END ADD_ORDER;

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.)

Use returned values from a query in the same procedure

I'm quite new to pgpsql. I have two tables - product and product_category - with a one to many relationship. I'm creating a procedure to insert a value into the category table if it doesn't exist and I would like to use the id for the category to insert a product.
I've tried using an alias for the first insert and then returning the id value so I can select it, but I get an error.
What's the right way for using returned in the same procedure values in pgpsql?
CREATE OR REPLACE PROCEDURE insert_product(name VARCHAR, category VARCHAR)
LANGUAGE plpgsql
as $$
BEGIN
WITH ins_category AS(
INSERT INTO product_category(name)
VALUES('laptops')
ON CONFLICT (name) DO NOTHING
RETURNING id
)
INSERT INTO product(name, category)
VALUES(name, ins_category.id); // HERE IS THE ERROR
COMMIT;
END;
$$
Use a SELECT instead of VALUES.
WITH ins_category
AS
(
...
)
INSERT INTO product (name,
category)
SELECT name,
ins_category.id
FROM ins_category;

plpgsql how to insert results of query into table?

I have a query A returning values integer, numeric, integer.
and a table B:
(id integer,
weight numeric,
price integer
)
the query returns many rows. I want to insert those rows directly to B. B doesn't have nor need a PK...
CREATE OR REPLACE FUNCTION func()
RETURNS void AS
$BODY$
begin
query A
insert to B?
continue function operation
end;
$BODY$
LANGUAGE plpgsql VOLATILE
I know it something like:
for row in query A
loop insert into B
but I can't find the proper syntax
You would do something like:
insert into b(id, weight, price)
select id, weight, price -- or whatever the column names are
from A;
The syntax of insert . . . select doesn't change because you are in a function.

oracle inline function called multiple times

When calling a function via an inline select statement, when the function is returning a custom type, Oracle seems to execute the function equal to the number of arguments +1. This seems to happen when the select is included as a CTAS or an insert/select.
Has anyone seen this before? Is this an Oracle bug? I would expect the function to be called once per row in the table.
--Inline function gets called for the number of arguments +1
--drop table t
create table t(
id number,
l_blob blob
);
insert into t values(1, utl_raw.cast_to_raw('SampleString'));
COMMIT;
create table tmp_ts (c1 timestamp);
create or replace type test_type as object(
c1 varchar2(32)
,c2 varchar2(32)
);
/
create or replace FUNCTION test_function (p_blob blob, p_date date)
RETURN test_type
IS
BEGIN
--This could also be a DBMS_OUTPUT.PUT_LINE statement
insert into tmp_ts VALUES (systimestamp);
return test_type(null,null);
END test_function;
/
--0
select count(*) from tmp_ts;
--Call function on 1 row table - function should just insert 1 row into tmp_ts
create table tst_table as
select test_function(l_blob, '25-JAN-09') as c1
from t;
--it actually inserts 3
select count(*) from tmp_ts;
Example where increasing the argument call for the type increases the number of time the function is executed
--Same example with more arguements - 6 arguements here
--Inline function gets called for the number of arguments +1
--drop table t
create table t2(
id number,
l_blob blob
);
insert into t2 values(1, utl_raw.cast_to_raw('SampleString'));
COMMIT;
create table tmp_ts2 (c1 timestamp);
create or replace type test_type2 as object(
c1 varchar2(32)
,c2 varchar2(32)
,c3 varchar2(32)
,c4 varchar2(32)
,c5 varchar2(32)
,c6 varchar2(32)
);
/
create or replace FUNCTION test_function2 (p_blob blob, p_date date)
RETURN test_type2
IS
BEGIN
insert into tmp_ts2 VALUES (systimestamp);
return test_type2(null,null,null,null,null,null);
END test_function2;
/
--0
select count(*) from tmp_ts2;
--Call function on 1 row table - function should just insert 1 row into tmp_ts
create table tst_table2 as
select test_function2(l_blob, '25-JAN-09') as c1
from t;
--it actually inserts 7
select count(*) from tmp_ts2;
Any help/feedback is greatly appreciated.
First: It is a bug that you can even perform a DML inside a function which is called in a SELECT Statement. This should raise an exception.
Otherwise Oracle makes absolutely no guarantee how often Functions in a SQL-Select are executed, it could be once per row, ten times per row or just once for the whole query (with caching) - so however often it is called, this conforms to the specifications.
In this special case it will call the function for each attribute of the returning type, since oracle will not insert the object type as one memory-structure, but use the function like a table with multiple columns and read each column individually like this:
INSERT VALUES ( myFunc(x).attribute1, myFunc(x).attribute2 );
The important part: Never make any assumptions about how often a FUNCTION is called when you use it in an SQL Statement!!! At any time the function could be called again by the optimizer, maybe for sampling or caching...
Preferred solution: Pipelined Functions - a pipelined Function can be called like a Table and will only be called once. You can pass in a cursor which the function uses for input processing and do the whole data-transformation and logging and everything in the function.