We migrated from SQL Server to Postgres and I am trying to rewrite a stored procedure. The procedure is created correctly, but I can not call it.
This is my procedure:
CREATE OR REPLACE PROCEDURE spr_getItems (
p_kind int = NULL,
p_customerId varchar(256) = NULL,
p_resourceIds varchar(2048) = NULL,
p_referenceIds varchar(2048) = NULL
)
AS $$
BEGIN
SELECT
c.kind,
c.name AS customerName,
c.oid AS customerId,
r.name AS resourceName,
r.oid AS resourceId
o.fullObject AS fullObjectString
FROM m_customer c
JOIN m_resource r
ON r.oid = c.resourceOid
LEFT JOIN m_object o
ON o.customerOid = c.oid
AND o.customerOid = p_customerId
WHERE (c.kind = p_kind OR p_kind is NULL)
AND (c.referenceOid IN (SELECT refTemp.oid FROM tvf_commaSeperatedStringToTable(p_referenceIds) refTemp) OR p_referenceIds is NULL)
AND (r.oid IN (SELECT resTemp.oid FROM tvf_commaSeperatedStringToTable(p_resourceIds) resTemp) OR p_resourceIds is NULL);
END;
$$
LANGUAGE 'plpgsql';
the table-valued-function tvf_commaSeperatedStringToTable just takes a string, splits it and returns a table with all of the different ids and a rownumber. It works just fine and is tested, no errors inside here.
Now when I try to execute it like this
CALL public.spr_getItems (0, null, null, null)
I get this output:
ERROR: query has no destination for result data
HINT: If you want to discard the results of a SELECT, use PERFORM instead.
CONTEXT: PL/pgSQL function spr_getItems(integer,character varying,character varying,character varying) line 3 at SQL statement
SQL state: 42601
But I do NOT want to discard the result, I want to see them.
So I tried calling it with select
SELECT *
FROM CALL spr_getItems (0, null, null, null)
and then I get this syntax error:
ERROR: syntax error at or near "0"
LINE 2: 0,
^
SQL state: 42601
Character: 40
I also tried executing it in several other way eg by adding the "public." before the procedures name, but then there has been a syntax error at the ".". Or with just using select spr_getItems(0, null, null, null) or select spr_getItems(0), select * from call spr_getItems (0) and so on and so forth.
Am I doing something completely wrong and overlooked something in the documentation?
Thanks for any help!
Edit: clarification that I want to see the results
Edit2: accidentally copied a wrong function name
Edit3: added complete body as suggested
That's not how Postgres works. Procedures aren't meant to return result sets.
If you want that use a set returning function:
CREATE OR REPLACE function spr_getItems (
p_kind int = NULL,
p_customerId varchar(256) = NULL,
p_resourceIds varchar(2048) = NULL,
p_referenceIds varchar(2048) = NULL
)
returns table (kind text, customername text, customerid integer, resourcename text, resourceid integer, fullobjectstring text)
AS $$
SELECT
c.kind,
c.name AS customerName,
c.oid AS customerId,
r.name AS resourceName,
r.oid AS resourceId
o.fullObject AS fullObjectString
FROM m_customer c
JOIN m_resource r
ON r.oid = c.resourceOid
LEFT JOIN m_object o
ON o.customerOid = c.oid
AND o.customerOid = p_customerId
WHERE (c.kind = p_kind OR p_kind is NULL)
AND (c.referenceOid IN (SELECT refTemp.oid FROM tvf_commaSeperatedStringToTable(p_referenceIds) refTemp) OR p_referenceIds is NULL)
AND (r.oid IN (SELECT resTemp.oid FROM tvf_commaSeperatedStringToTable(p_resourceIds) resTemp) OR p_resourceIds is NULL);
$$
LANGUAGE sql;
You also don't need PL/pgSQL for a simple query encapsulation, language sql will do just fine.
Then use it like a table:
select *
from spr_getitems(....);
Note that I guessed the data types in the returns table (...) part, you will have to adjust that to the real types used in your tables.
You don't need the sub-selects to handle the comma separated values either.
E.g. this:
AND (c.referenceOid IN (SELECT refTemp.oid FROM tvf_commaSeperatedStringToTable(p_referenceIds) refTemp) OR p_referenceIds is NULL)
can be simplified to
AND (c.referenceOid = any (string_to_array(p_referenceIds, ',') OR p_referenceIds is NULL)
But passing multiple values as a comma separated string is bad coding style to begin with. You should declare those parameters as array and pass proper arrays to the function.
The error refers to a function call (spr_getshadowrefs) inside the public.spr_getItems procedure. Perhaps you're trying to execute the spr_getshadowrefs function without putting the result in any variable.
Try to use PERFORM when you execute the spr_getshadowrefs function inside the public.spr_getItems procedure.
Have you tried
EXEC spr_getItems p_kind = 0,
p_customerId = NULL,
p_resourceIds = NULL,
p_referenceIds = NULL
Related
I Have an SP that receive 2 parameters, P1 and P2, like this:
CREATE OR ALTER PROCEDURE MY_PROC (P1 varchar(10), P2 smallint = 1)
RETURNS (
code VARCHAR(10),
name VARCHAR(70),
state VARCHAR(2),
situation VARCHAR(20)
AS
...
...
And I need to generate the where clause based on the P2 parameter, like this:
if (P2=1) then
where (state='SP' and situation='stopped')
elseif (P2=2)
where (state='MG' and situation='moving')
How to use this type of if statement in where clause?
To me your question translates as a simple OR condition in the WHERE clause of a SQL query:
WHERE
(:P2 = 1 AND state='SP' and situation='stopped')
OR (:P2 = 2 AND state='MG' and situation='moving')
The answer of GMB will work fine for most situations, but in more complex cases it may have less desirable performance. An alternative solution would be to build a query string dynamically and execute it with execute statement:
CREATE OR ALTER PROCEDURE MY_PROC (P1 varchar(10), P2 smallint = 1)
RETURNS (
code VARCHAR(10),
name VARCHAR(70),
state VARCHAR(2),
situation VARCHAR(20)
AS
declare query varchar(2048);
begin
query = 'select ......';
if (p2 = 1) then
query = query || ' where (state=''SP'' and situation=''stopped'')';
else if (p2 = 2) then
query = query || ' where (state=''MG'' and situation=''moving'')';
-- if you expect a single result
execute statement query into code, name, state, situation;
-- OR
-- for multiple results
for execute statement query into code, name, state, situation do
suspend;
end
Again I appeal to you for your help. I am migrating processes from Oracle to postgres.
I declare this cursor to extract an information and insert it into a table:
esi_cur_fono cursor
for SELECT (select nextval('edef_seq_pr')) seq_nextval,
c_pcodigo_soc_dest,
c_ctac_correlativo,
c_transac,
c_transac||
lpad(c_tr_count, v_transaction_seq,'0')||
lpad(c_rc_count, v_record_seq,'0')||
rpad(esi.pers_codigo, v_exploitation_source_id,' ')||
rpad(translate(esi.nombre,c_cad_n,c_cad_y), v_exploitation_source_name,' ')||
rpad(esi.esty, v_exploitation_source_type,' ')||
lpad(c_tisn_cd, v_exploitation_territory_code,'0')||
lpad(c_tisn_fd, v_exploitation_territory_cvfd,'0')||
rpad(c_tisan, v_exploitation_territory_abbn,' ')||
lpad(c_tisn_fd, v_exploitation_territory_avfd,'0'),
now() fecha,
tipo_dist,
(SELECT currval('edef_seq_pr')) edef_padre,
c_edef_order,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL
FROM (SELECT DISTINCT liprt.pers_codigo,
pers.pers_nombre_completo nombre,
'10' esty,
'MEC' tipo_dist
FROM mocct a
INNER JOIN reort b ON a.reor_correlativo = b.reor_correlativo
INNER JOIN deret c ON c.dere_correlativo = b.dere_correlativo
INNER JOIN dblink('dbname = crd host=100.1.1.138 port=5432', 'select delp_correlativo, lipr_correlativo from fon_detalles_liq_productor') as delpt (delp_correlativo numeric, lipr_correlativo numeric) ON delpt.delp_correlativo = c.delp_correlativo
INNER JOIN dblink('dbname = crd host=100.1.1.138 port=5432', 'select lipr_correlativo, pers_codigo from fon_liquidaciones_productor') as liprt (lipr_correlativo numeric, pers_codigo varchar) ON liprt.lipr_correlativo = delpt.lipr_correlativo
INNER JOIN dblink('dbname = usuarios host=100.1.1.138 port=5432', 'select pers_codigo, pers_nombre_completo from unv_personas') as pers (pers_codigo varchar, pers_nombre_completo varchar) ON liprt.pers_codigo = pers.pers_codigo
WHERE a.mocc_monto != 0
AND b.pers_codigo_socadm = '312951160'
AND a.ctac_correlativo = 7344) esi;
The problem is presented in the declaration of the variables where the values returned by the cursor are saved, since not being a table, but a sub query, I get an error, so I resort to declare the variables with the type and the length maximum for each one.
/*declaration of cursor variables*/
v_seq_nextval numeric(10);
v_c_pcodigo_soc_dest varchar(10);
v_c_ctac_correlativo varchar(10);
v_c_transac varchar(10);
v_registro varchar(218);
v_fecha date;
v_tipo_dist varchar(10);
v_edef_padre numeric(10);
v_c_edef_order numeric(1);
v_null_1 varchar(10);
v_null_2 varchar(10);
v_null_3 varchar(10);
v_null_4 varchar(10);
v_null_5 varchar(10);
v_null_6 varchar(10);
v_null_7 varchar(10);
v_null_8 varchar(10);
When I execute it, it gives me the following error
ERROR: Missing "FROM or IN" at the end of the SQL expression
LINE 107: FETCH esi_cursor_fono INTO v_seq_nextval;
^
SQL state: 42601
Character: 4897
Look everywhere, and all the examples are with tables, and even I already have 2 that work without problem, but it is because the queries are direct to tables, not to sub queries.
Not sure what you're doing inside your FOR loop, but here's an example you can use and adapt to your needs:
CREATE OR REPLACE FUNCTION DCPViews.SP_DCPDeleteSchool (
pSchoolId INTEGER
)
RETURNS VOID AS $$
DECLARE
myclass RECORD;
BEGIN
-- Cascaded DELETEs (must delete from child tables first)
-- Delete all classes associated with school
FOR myclass IN
SELECT ClassId FROM DCP.Class WHERE SchoolId = pSchoolId
LOOP
PERFORM DCPViews.SP_DCPDeleteClass(myclass.ClassId); -- PERFORM runs function and discards the results
END LOOP;
-- Delete school
DELETE FROM DCP.School WHERE SchoolId = pSchoolId;
-- Ignore foreign key violation errors
EXCEPTION WHEN foreign_key_violation THEN NULL;
END;
$$ LANGUAGE 'plpgsql';
You don't need to store the values into variables, you just create a new RECORD variable which stores each current row of your FOR loop SELECT statement as it's being processed.
Let me know if that's what you're looking for or if I misread your question.
In SQL Server, I made a function that will return the sum of the column from a table which is the result of a query to another table. It's easier to understand when looking at the code:
CREATE FUNCTION [dbo].[getPatientMorphineEquivalentDose]
(
#patientID int
)
RETURNS INT
WITH SCHEMABINDING
AS
BEGIN
RETURN (SELECT SUM(j.MilligramMorphineEquivalent) FROM
(
SELECT i.Mg * i.[Morphine Equivalent (mg)] AS MilligramMorphineEquivalent
FROM
(
SELECT PatientMedication.Mg, Medication.[Morphine Equivalent (mg)]
FROM PatientMedication
INNER JOIN Medication
ON PatientMedication.MedicationID = Medication.Id
WHERE PatientMedication.PatientID = #patientID
) AS i
) AS j )
END
I have very little experience with Sql Server so I am not sure if I am doing anything wrong, but from what I researched online this should work. I tried it with a stored procedure as well and it still would not compile.
You didn't specify which error you got, but since you're defining your function with schemabinding, I expect you got an error like:
Cannot schema bind function 'dbo.getPatientMorphineEquivalentDose' because name 'PatientMedication' is invalid for schema binding. Names must be in two-part format and an object cannot reference itself.
When you use schemabinding, you are expected to prefix the object names with the owner. Notice what the documentation says (emphasis mine):
A function can be schema bound only if the following conditions are true:
The function is a Transact-SQL function.
The user-defined functions and views referenced by the function are also schema-bound.
The objects referenced by the function are referenced using a two-part name.
The function and the objects it references belong to the same database.
The user who executed the CREATE FUNCTION statement has REFERENCES permission on the database objects that the function references.
So assuming your tables are owned by dbo, make sure to prefix the 2 referenced tables in the query (dbo.PatientMedication and dbo.Medication):
CREATE FUNCTION [dbo].[getPatientMorphineEquivalentDose]
(
#patientID int
)
RETURNS INT
WITH SCHEMABINDING
AS
BEGIN
RETURN (SELECT SUM(j.MilligramMorphineEquivalent) FROM
(
SELECT i.Mg * i.[Morphine Equivalent (mg)] AS MilligramMorphineEquivalent
FROM
(
SELECT PatientMedication.Mg, Medication.[Morphine Equivalent (mg)]
FROM dbo.PatientMedication
INNER JOIN dbo.Medication
ON PatientMedication.MedicationID = Medication.Id
WHERE PatientMedication.PatientID = #patientID
) AS i
) AS j )
END
By the way, unrelated to your error, but the query can be simplified:
CREATE FUNCTION [dbo].[getPatientMorphineEquivalentDose]
(
#patientID int
)
RETURNS INT
WITH SCHEMABINDING
AS
BEGIN
RETURN (SELECT sum(pm.Mg * m.[Morphine Equivalent (mg)])
FROM dbo.PatientMedication pm
INNER JOIN dbo.Medication m
ON pm.MedicationID = m.Id
WHERE pm.PatientID = #patientID)
END
I have a stored procedure that accepts parameter and return tuple with matching values. If no parameter is passed, then return every tuple in the table
create procedure getScore
(
#clinicCode varchar = null,
)
as
begin
select * from myTable
where ClinicCode = isnull(#clinicCode, ClinicCode)
end
so I executte it
exec getScore
exec getScore 'PSH'
both of them return no tuple.
I did try select * from myTable, and they returns all tuples. Not sure why the statement from ... isnull(expression, replacement) get messed up
You need to change the declaration of
#clinicCode varchar = null,
to the actual size you require.
So something like
#clinicCode varchar(50) = null,
The reason for this is that
#clinicCode varchar
is the same as
#clinicCode varchar(1)
Which then casts your field isnull(#clinicCode, ClinicCode) to only the first letter of ClinicCode
Have a look at this example
SQL Fiddle DEMO
This parameter in SQL Server query - I copy to the Oracle query I am writing but it does not compile:
Compilation errors for PROCEDURE OGEN.DBD_NOT_GET_NOTES_DETAIL
Error: PLS-00103: Encountered the symbol "(" when expecting one of the
following:
:= . ) , # % default character
The symbol ":=" was substituted for "(" to continue. Line: 6 Text: , NOTETYPE NUMERIC(1) = 1
How can I code this in Oracle?
The complete SQL Server T-SQL query:
ALTER PROCEDURE [OEN].[DB_NOT_GET_NOTES_DETAIL]
(
#FACILITYKEY CHAR(4),
#DATEFROM DATETIME,
#DATETHRU DATETIME,
#UNITSTR VARCHAR(250),
#NOTETYPE NUMERIC(1) = 1
)
AS
BEGIN
SELECT P.FACILITY_KEY, P.PAT_NUMBER, P.PATIENT_ID,
OEN.DATEONLY(N.CREATED_ON) CREATED_ON, N.NOTE_HEADER,
N.CREATED_BY, P.LAST_NAME, P.FIRST_NAME, P.MIDDLE_NAME, P.UNIT_CODE
FROM OEN.EN_M_PATIENT_MAST P INNER JOIN OPTC.NOT_M_MAST N
ON (P.PAT_NUMBER = N.PAT_NUMBER AND N.FACILITY_KEY = #FACILITYKEY)
WHERE N.NOTE_STATUS = 0
AND (OEN.DATEONLY(N.CREATED_ON) BETWEEN OEN.DATEONLY(#DATEFROM) AND OEN.DATEONLY(#DATETHRU))
AND (#UNITSTR IS NULL OR #UNITSTR = '' OR CHARINDEX(P.UNIT_CODE, #UNITSTR) % 2 = 1)
AND #NOTETYPE = 1
END
The Oracle version:
CREATE OR REPLACE PROCEDURE OEN.DBD_NOT_GET_NOTES_DETAIL (
FACILITYKEY varchar2
, DATEFROM DATE
, DATETHRU DATE
, UNITSTR varchar2
, NOTETYPE NUMERIC(1) = 1
, OCURSOR OUT SYS_REFCURSOR
) as
BEGIN
OPEN OCURSOR FOR
SELECT P.FACILITY_KEY,
P.PAT_NUMBER,
P.PATIENT_ID,
OEN.DATEONLY(N.CREATED_ON) CREATED_ON, N.NOTE_HEADER,
N.CREATED_BY, P.LAST_NAME, P.FIRST_NAME, P.MIDDLE_NAME, P.UNIT_CODE
FROM OEN.EN_M_PATIENT_MAST P
INNER JOIN OPTC.NOT_M_MAST N ON (P.PAT_NUMBER = N.PAT_NUMBER AND N.FACILITY_KEY = FACILITYKEY)
WHERE N.NOTE_STATUS = 0
AND (OEN.DATEONLY(N.CREATED_ON) BETWEEN OEN.DATEONLY(DATEFROM) AND OEN.DATEONLY(DATETHRU))
AND CREATED_ON BETWEEN DATEFROM AND DATETHRU
AND (UNITSTR IS NULL OR P.UNIT_CODE = UNITSTR);
END;
Parameters to functions should not have length, scale, or precision. So the NOTETYPE parameter would need to be declared
NOTETYPE NUMERIC
If you want to assign a default value for a parameter, the syntax is
<<parameter declaration>> DEFAULT <<default value>>
Putting it together, your parameter declaration should be
, NOTETYPE NUMERIC DEFAULT 1
As a general stylistic matter, though this probably won't cause any errors
I would strongly suggest that parameters to procedures be anchored to an appropriate type in the data model. So, for example, FACILITYKEY OPTC.NOT_M_MAST.FACILITY_KEY%TYPE. That allows the parameter to adjust if in the future you need to do something like increase the length of a column.
I would strongly suggest adopting a naming convention for parameters that differentiates them from database columns. Prefixing the parameter names is a popular one (i.e. p_facility_key optc.not_m_mast.facility_key%type). Since you're not using the # prefix like you do in SQL Server, it is very easy to inadvertently have a parameter name that matches the name of a column in a table. Since name resolution gives preference to column names over local variables, that makes it very easy to write code that is inadvertently using a column rather than a local variable.
For example, this function will return every row in the EMP table.
CREATE OR REPLACE FUNCTION get_emps( empno IN emp.empno%type )
RETURN sys_refcursor
IS
l_rc sys_refcursor;
BEGIN
OPEN l_rc
FOR SELECT *
FROM emp e
WHERE e.empno = empno;
RETURN l_rc;
END;
Since the goal of this method is to return something to the caller, not to do a computation, it really ought to be declared as a FUNCTION rather than a PROCEDURE and ought to RETURN the sys_refcursor rather than having an OUT parameter.
My guess is you need
NOTETYPE NUMBER(1) := 1;