Postgres functions how to return an array of multiple elements - postgresql

I want to create a function to return an array of multiple elements, the query work perfect but I can't execute as function because I'm getting error always, this is my function:
CREATE OR REPLACE FUNCTION public.buscarexamen(visit anyelement)
RETURNS anyelement
LANGUAGE plpgsql
AS $function$
BEGIN
return array(SELECT fn_folio_paciente(vi.clave_unilabor, vi.fecha, vi.consec) AS folio_paciente,
vi.cve_visita, gp.nombre AS nombre_grupo, gp.cve_grupo,
--
vp.cve_visita_prueba, p.cve_prueba, p.nombre AS nombre_prueba, me.nombre AS nombre_metodo,
--
gcf.visible AS visible_grupo,
cf.cve_grupo_campo_formato, gc.nombre_grupo_campo AS grupo_parametro, cf.nombre AS nombre_parametro,
cf.orden, cf.tipo_dato, cf.solo_lectura AS tipo_titulo,
cf.cve_seccion_padre, cf.tiene_elementos_seccion, cf.observacion AS observacion_parametro,
--
pr.cve_prueba_resultado, pr.tipo_formato_prueba AS tipo_formato, pr.cantidad_resultado AS cantidad_parametro,
pr.observaciones AS observacion_analista, pr.observaciones_prueba,
CASE
WHEN (TRIM(pr.observaciones) <> '') OR (TRIM(pr.observaciones_prueba) <> '') THEN 1
ELSE 0 END AS prueba_tiene_observacion,
--
rc.cve_resultado_campo, rc.resultado, rc.observacion AS observacion_resultado, rc.signo, rc.decimales, cf.xleft as cursiva,
vr.valor_referencia_min, vr.valor_referencia_max, un.nombre AS nombre_unidad,
cf.tiene_valores_referencia
FROM visitas vi
JOIN corporativo.unilabor_unidades uu ON (vi.cve_unilabor_unidad = uu.cve_unilabor_unidad)
JOIN visita_pruebas vp ON (vi.cve_visita = vp.cve_visita)
JOIN prueba_resultados pr ON (vp.cve_visita_prueba = pr.cve_visita_prueba)
JOIN prueba_metodo pm ON (pr.cve_prueba_metodo = pm.cve_prueba_metodo)
JOIN metodos me ON (pm.cve_metodo = me.cve_metodo)
JOIN pruebas p ON (pr.cve_prueba = p.cve_prueba)
JOIN grupos gh ON (p.cve_grupo = gh.cve_grupo)
JOIN grupos gp ON (gh.cve_grupo_padre = gp.cve_grupo)
JOIN pacientes pa ON (vi.cve_paciente = pa.cve_paciente)
JOIN contratos co ON (vi.cve_contrato = co.cve_contrato AND co.contrato_cerrado = 0)
JOIN resultados_campos rc ON (pr.cve_prueba_resultado = rc.cve_prueba_resultado)
JOIN unidades un ON (rc.cve_unidad = un.cve_unidad)
LEFT JOIN valor_referencia_resultado vr ON (rc.cve_resultado_campo = vr.cve_resultado_campo)
JOIN campos_formatos cf ON (rc.cve_campo_formato = cf.cve_campo_formato)
JOIN grupos_campos_formato gcf ON (cf.cve_grupo_campo_formato = gcf.cve_grupo_campo_formato)
JOIN grupos_campos gc ON (gcf.cve_grupo_campo = gc.cve_grupo_campo)
WHERE vi.cve_visita = 696787
AND cf.solo_lectura = 0
ORDER BY p.orden_prueba, pr.cve_prueba_resultado, gcf.orden_grupo, rc.orden_local, vr.orden_valor);
END;
$function$
;
I try to use:
select * from buscarexamen(123123);
On SQL Script return this:
SQL Error [42601]: ERROR: subquery must return only one column Where: PL/pgSQL function buscarexamen(anyelement) line 3 at RETURN
Really I spend a lot of time finding solutions but until now isn't working.
Any help is really apreciated.
Regards.

Your objective sounds like strange. However you should be able to achieve it by creating a composite type your_composite_type :
CREATE TYPE your_composite_type AS
( folio_paciente folio_paciente_data_type
, cve_visita cve_visita_data_type
, nombre_grupo nombre_grupo_data_type
, cve_grupo cve_grupo_data_type
, cve_visita_prueba cve_visita_prueba_data_type
, cve_prueba cve_prueba_data_type
, nombre_prueba nombre_prueba_data_type
, nombre_metodo nombre_metodo_data_type
, visible_grupo visible_grupo_data_type
, [...]
, nombre_unidad nombre_unidad_data_type
, tiene_valores_referencia tiene_valores_referencia_data_type
) ;
Then the function public.buscarexamen() can return an array of data type your_composite_type :
CREATE OR REPLACE FUNCTION public.buscarexamen(visit anyelement)
RETURNS your_composite_type[] -- instead of anyarray
LANGUAGE plpgsql
AS $function$
BEGIN
return array(SELECT row( fn_folio_paciente(...)
, vi.cve_visita, gp.nombre, gp.cve_grupo
, [...]
, vr.valor_referencia_min, vr.valor_referencia_max, un.nombre
, cf.tiene_valores_referencia
) :: your_composite_type
FROM visitas vi
JOIN [...]
WHERE [...]
ORDER BY p.orden_prueba, pr.cve_prueba_resultado, gcf.orden_grupo, rc.orden_local, vr.orden_valor)
) ;
END;
$function$
;

Related

How to use From clause inside PostgreSQL Update statement

I am converting SQL stored procedure to PostgreSQL stored function. In this stored function inside Update there is From clause.
I want to know how to use From inside Update? Because I am getting error table name "account" specified more than once
CREATE OR REPLACE FUNCTION ETL_Insert_ModifyData( instID numeric)
RETURNS void
LANGUAGE 'sql'
AS $$
--Account
UPDATE account
SET namount = stg_account.namount,
slocation = stg_account.sLocation,
ndept_id = stg_account.ndept_id ,
account.naccount_cpc_mapping_id = stg_account.naccount_cpc_mapping_id
FROM account
INNER JOIN stg_account ON account.naccount_id = stg_account.naccount_id
INNER JOIN department ON stg_account.ndept_id = department.ndept_id
INNER JOIN accountcpcmapping ON stg_account.naccount_cpc_mapping_id = accountcpcmapping.naccount_cpc_mapping_id
WHERE account.ninst_id = instID
AND department.ninst_id = instID
AND accountcpcmapping.ninst_id = instID
--print 'Account completed '
$$
Move the JOIN condition with account table and stg_account to WHERE clause. Also, you need not refer to account in the SET.
Further, prefer shorter aliases (like one or 2 letters) rather than using complete table names.
UPDATE account
SET
namount = stg_account.namount,
slocation = stg_account.sLocation,
ndept_id = stg_account.ndept_id ,
naccount_cpc_mapping_id = stg_account.naccount_cpc_mapping_id
FROM stg_account
INNER JOIN department ON stg_account.ndept_id = department.ndept_id
INNER JOIN accountcpcmapping ON
stg_account.naccount_cpc_mapping_id = accountcpcmapping.naccount_cpc_mapping_id
WHERE account.naccount_id = stg_account.naccount_id
AND account.ninst_id = instID
AND department.ninst_id = instID
AND accountcpcmapping.ninst_id = instID

PostgreSQL missing from clause entry for table in function

I have the following function that handles a trigger on insert or update:
CREATE OR REPLACE FUNCTION ticketChangeFunc() RETURNS TRIGGER AS $$
BEGIN
INSERT INTO dw.FactSalesHeader (DateKey, LocationKey, EmployeeKey, AppointmentTypeKey, TicketStatusTypeKey, TicketID,TotalAmount, IsNewPatient, IsActive)
SELECT d.date_key, COALESCE(l.LocationKey, 0), COALESCE(e.EmployeeKey, 0), COALESCE(a.AppointmentTypeKey, 0), COALESCE(ts.TicketStatusTypeKey, 0), NEW.ticket_id,
NEW.total_amount, NEW.is_new_patient, NEW.is_active
FROM db1.tickets t
JOIN dw.DimDate d on t.ticket_date = d.db_date
LEFT JOIN dw.DimLocation l on NEW.location_id = l.LocationID
LEFT JOIN dw.DimEmployee e on NEW.counselor_id = e.EmployeeID
LEFT JOIN dw.DimAppointmentType a on NEW.office_visit_ind = a.AppointmentTypeFlagAttribute
LEFT JOIN dw.DimTicketStatus ts on NEW.ticket2_status = ts.TicketStatusTypeID
ON CONFLICT (TicketID)
DO UPDATE
SET DateKey = d.date_key,
LocationKey = COALESCE(l.LocationKey, 0),
EmployeeKey = COALESCE(e.EmployeeKey, 0),
AppointmentTypeKey = COALESCE(a.AppointmentTypeKey, 0),
TicketStatusTypeKey = COALESCE(ts.TicketStatusTypeKey, 0),
TotalAmount = NEW.total_amount,
IsNewPatient = NEW.is_new_patient,
IsActive = NEW.is_active;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
I get the error: missing FROM-clause entry for table "d"
I thought maybe it was somehow related to the db1.tickets table that I'm not technically using in the query. I've tried taking out that bit and just doing FROM dw.DimDate with WHERE d.date_key = NEW.ticket_date, and that gives the same error.
There are a lot of these types of questions on SO, but I haven't found one that addresses this particular scenario, as I feel like it has something to do with it being inside of this trigger function.
The function gets called from this:
CREATE TRIGGER trg_tickets AFTER INSERT OR UPDATE ON db1.tickets
FOR EACH ROW EXECUTE PROCEDURE ticketChangeFunc();
I'm also wondering if using the ON CONFLICT UPDATE clause updates every row when it finds a match regardless of whether the values differ? Is there a performance impact to this, and if so, is there a way to check for equality of each field and do nothing if there are no differences?
For those who may happen to run into this in the future, the solution for me was to add a conditional to the function and handle inserts separately from updates:
CREATE OR REPLACE FUNCTION ticketChangeFunc() RETURNS TRIGGER AS $$
BEGIN
IF TG_OP = 'INSERT' THEN
INSERT INTO dw.FactSalesHeader (DateKey, LocationKey, EmployeeKey, AppointmentTypeKey, TicketStatusTypeKey, TicketID, TotalAmount, IsNewPatient, IsActive)
SELECT d.date_key, COALESCE(l.LocationKey, 0), COALESCE(e.EmployeeKey, 0), COALESCE(a.AppointmentTypeKey, 0), COALESCE(ts.TicketStatusTypeKey, 0), NEW.ticket_id,
NEW.total_amount, NEW.is_new_patient, NEW.is_active
FROM db1.tickets t
JOIN dw.DimDate d ON NEW.ticket_date = d.db_date
LEFT JOIN dw.DimLocation l ON NEW.location_id = l.LocationID
LEFT JOIN dw.DimEmployee e ON NEW.counselor_id = e.EmployeeID
LEFT JOIN dw.DimAppointmentType a ON NEW.office_visit_ind = a.AppointmentTypeFlagAttribute
LEFT JOIN dw.DimTicketStatus ts ON NEW.ticket2_status = ts.TicketStatusTypeID;
ELSE
UPDATE dw.FactSalesHeader
SET DateKey = CAST(TO_CHAR(NEW.ticket_date, 'YYYYMMDD') as integer),
LocationKey = COALESCE(l.LocationKey, 0),
EmployeeKey = COALESCE(e.EmployeeKey, 0),
AppointmentTypeKey = COALESCE(a.AppointmentTypeKey, 0),
TicketStatusTypeKey = COALESCE(ts.TicketStatusTypeKey, 0),
TotalAmount = NEW.total_amount,
IsNewPatient = NEW.is_new_patient,
IsActive = NEW.is_active
FROM dw.FactSalesHeader hdr
LEFT JOIN dw.DimLocation l ON NEW.location_id = l.LocationID
LEFT JOIN dw.DimEmployee e ON NEW.counselor_id = e.EmployeeID
LEFT JOIN dw.DimAppointmentType a ON NEW.office_visit_ind = a.AppointmentTypeFlagAttribute
LEFT JOIN dw.DimTicketStatus ts ON NEW.ticket2_status = ts.TicketStatusTypeID
WHERE hdr.TicketID = NEW.ticket_id;
END IF;
RETURN NULL;
END;
$$ LANGUAGE plpgsql;

ColdFusion 2016 and stored proc throwing invalid character error

I am trying existing code in a CF 2016 install... I get this error
"[Macromedia][DB2 JDBC Driver][DB2]ILLEGAL SYMBOL =; VALID SYMBOLS ARE ..."
the line identified is a param of a stored proc call that looks like this:
<cfstoredproc datasource="#application.dsn#" procedure="LIVE.STOPS">
<cfprocparam type="In" cfsqltype="CF_SQL_BIGINT" dbvarname="STOPID" value="#val( variables.procstopid )#" null="no">
<cfprocparam type="In" cfsqltype="CF_SQL_INTEGER" dbvarname="TRIPID" value="#val( url.tripId )#" null="no">
</cfstoredproc>
I cannot find any mention on line of a change in stored proc tag - maybe the DB2 driver? I'm looking for any input. Thanks.
Other info;
Windows10, Apache2.4, connectiong to DB2 v10.
#pendo, Here is the stored proc - it should be noted that I abbreviated some of the sql, but the SP works and has for a long time in the app running CF10.
CREATE OR REPLACE PROCEDURE LIVE.STOP(
IN stopId BIGINT DEFAULT 0,
IN tripId INTEGER DEFAULT 0
) LANGUAGE SQL
BEGIN
DECLARE updateTripId INTEGER DEFAULT 0;
DECLARE minStopId BIGINT DEFAULT 0;
DECLARE maxStopId BIGINT DEFAULT 0;
DECLARE TripSearch_cursor CURSOR FOR
SELECT s1.fkTripsId
FROM live.paymentsTripsStops s1
JOIN live.Trips t ON s1.fkTripsId = t.Id
WHERE s1.fkStopsId = stopId
FETCH FIRST 1 ROWS ONLY;
DECLARE minMaxStop_cursor CURSOR FOR
SELECT
COALESCE(
(
SELECT s.Id
FROM live.Stops s
JOIN live.Trips t ON s.fkTripsId = t.Id
ORDER BY s.Sequence
FETCH FIRST 1 ROWS ONLY
),
0
) AS firstStopId,
COALESCE(
(
SELECT s.Id
FROM live.Stops s
JOIN live.Trips t ON s.fkTripsId = t.Id
ORDER BY s.Sequence DESC
FETCH FIRST 1 ROWS ONLY
),
0
) AS lastStopId
FROM live.Trips t
WHERE t.Id = updateTripId
FETCH FIRST 1 ROWS ONLY;
IF TripId > 0
THEN SET updateTripId = TripId;
ELSE OPEN TripSearch_cursor;
FETCH FROM TripSearch_cursor INTO updateTripId;
CLOSE TripSearch_cursor;
END IF;
IF updateTripId > 0
THEN OPEN minMaxStop_cursor;
FETCH FROM minMaxStop_cursor INTO minStopId, maxStopId;
CLOSE minMaxStop_cursor;
UPDATE live.Trips
SET fkFirstStopId = minStopId,
fkLastStopId = maxStopId
WHERE intId = updateTripId;
END IF;
END

SQL Server : error "Must Declare the Scalar Variable"

Trying to insert into a table from other two tables with a loop
DECLARE #RowCount INT
SET #RowCount = (SELECT Max(FogTopicsID) FROM FSB_FogTopics )
DECLARE #I INT
SET #I = 1
WHILE (#I <= #RowCount)
BEGIN
DECLARE #FogID INT, #StudentID INT, #TopicID INT, #ProcessStudentId INT
SELECT #FogID = FogID, #StudentID = StudentID, #TopicID = TopicsID
FROM FSB_FogTopics
WHERE FogTopicsID = #I
SELECT #ProcessStudentId = ProStudentId
FROM FSB_ProcessStudents
WHERE ProcessId = #FogID AND StudentId = #StudentID
INSERT INTO FSB_ProcessTopics( [ProcessStudentId], [TopicId])
VALUES (#ProcessStudentId, #TopicID)
SET #I = #I + 1
END
but I get an error
Must Declare the Scalar Variable #ProcessStudentId
As pointed out by forklift's comment - You can use proper set based solution instead of horrible loop like so;
INSERT FSB_ProcessTopics( [ProcessStudentId], [TopicId])
SELECT
s.ProStudentId,
f.TopicsId
FROM FSB_FogTopics f
INNER JOIN FSB_ProcessStudents s
ON f.FogId = s.ProcessId
AND f.StudentId = s.StudentId
While I realise this doesn't answer your question per-say, this is a better way to do it and should eliminate the need to solve your problem...
You probably have non-continuous Ids - So you have 1,2,4 as Ids but your code is trying to dind 1,2,3,4
You don't need loops to do this (you should almost never need to use loops in SQL for anything). You can do your INSERT in a single statement:
Insert FSB_ProcessTopics
(ProcessStudentId, TopicId)
Select P.ProStudentId, T.TopicsId
From FSB_FogTopics T
Join FSB_ProcessStudents P On P.ProcessId = T.FogId
And P.StudentId = T.StudentId
Do this as a single statement:
INSERT FSB_ProcessTopics(ProcessStudentId, TopicId)
SELECT ProStudentId, TopicsID
FROM FSB_FogTopics ft JOIN
FSB_ProcessStudents ps
ON ft.StudentID = ps.StudentId AND sps.ProcessId = ft.FogiId;
This should replace the cursor, the loop, everything.

Pl/Sql Storing multiple result of select query in single object in oracle

I have a scenario,a procedure in which there is for loop in which there is a select query which returns different result each time it run. I have to store each and every result in single variable or array or cursor.Please suggest!!.Below provided is the procedure written in a package:
Package Spec:
TYPE t_product_id IS TABLE OF products.productnr%TYPE
INDEX BY PLS_INTEGER;
TYPE t_product_desc IS TABLE OF varchar(100)
INDEX BY PLS_INTEGER;
Type t_product_psnr IS TABLE OF prodsubs.PSNR%TYPE
INDEX BY PLS_INTEGER;
TYPE t_prod_contractperiod IS TABLE OF prodrul.CONTRPEROFPSNR%TYPE
INDEX BY PLS_INTEGER;
Type t_output IS TABLE OF prodrul.ALLOWEDCUSTTYPES%TYPE
INDEX BY PLS_INTEGER;
Package body:
PROCEDURE x_proc_Validprodcusttypeacctyp (
i_prodnr IN t_product_id,
i_invoiceprd IN prodrul.ALLOWEDINVPERIOD%TYPE,
i_psnr IN t_product_psnr,
i_contactprd IN t_prod_contractperiod,
i_result OUT t_output
)
IS
BEGIN
FOR i IN i_prodnr.FIRST .. i_prodnr.LAST
`LOOP
BEGIN
select PRODUCTNR,PRODUCTUSERKEY,ALLOWEDCUSTTYPES,ALLOWEDACCNTTYPES
into i_result (i)`
from prodrul rul join products prods on rul.PRODUCTNROFPSNR = prods.productnr
where prods.productnr = i_prodnr (i) and ALLOWEDINVPERIOD= i_invoiceprd and CONTRPEROFPSNR= i_contactprd (i)
and prods.productnr not in(select productnr from products where SMARTCARDYN = 1)
and rul.PRODUCTNROFPSNR not in (select PSPRODUCTNR from prodsubs join decoders on prodsubs.PSNR = decoders.DECSCPSNR where prodsubs.PSNR= i_psnr (i));
END;
END LOOP;
END x_proc_Validprodcusttypeacctyp;
You can use sys_refcursor as a collection object to return multiple columns instead of using arrays.
E.g.
CREATE OR REPLACE PROCEDURE x_proc_validprodcusttypeacctyp (
i_prodnr IN t_product_id,
i_invoiceprd IN prodrul.allowedinvperiod%TYPE,
i_psnr IN t_product_psnr,
i_contactprd IN t_prod_contractperiod,
i_result OUT t_output,
o_cursor OUT SYS_REFCURSOR
)
IS
BEGIN
OPEN o_cursor FOR
SELECT productnr, productuserkey, allowedcusttypes, allowedaccnttypes
FROM prodrul rul
JOIN
products prods
ON rul.productnrofpsnr = prods.productnr
WHERE prods.productnr = i_prodnr
AND allowedinvperiod = i_invoiceprd
AND contrperofpsnr = i_contactprd
AND prods.productnr NOT IN (SELECT productnr
FROM products
WHERE smartcardyn = 1)
AND rul.productnrofpsnr NOT IN
(SELECT psproductnr
FROM prodsubs
JOIN
decoders
ON prodsubs.psnr =
decoders.decscpsnr
WHERE prodsubs.psnr = i_psnr);
END;
/