I am using postgresql version 10.3
I am working with a Common Table Expressions inside a function. The function code is following:
CREATE OR REPLACE FUNCTION subscriptions.to_supply_patients(
character varying,
timestamp with time zone)
RETURNS void
LANGUAGE 'plpgsql'
COST 100
VOLATILE
AS $BODY$
declare
aws_sub_pro alias for $1; -- health professional aws_sub
tour_date alias for $2; -- tour date to manage patients covered accounts
number_cover int; -- the number of account to manage
sub_list integer[]; -- array list for subscribers covered
no_sub_list integer[];-- array list for no subscribers covered
date_tour timestamp with time zone;--tour date to manage patients covered accounts converted with time zone converted to 23:59:59
begin
select subscriptions.convert_date_to_datetime((select date(tour_date)),'23:59:59','0 days') into date_tour; -- function to convert time to 23:59:59
select count(*) from subscriptions.cover where aws_sub = aws_sub_pro into number_cover; -- global number of patient to cover
if number_cover > 0 then
begin
if tour_date >= current_timestamp then -- if tour date is later than today date
begin
with cover_list as (
select id_cover from subscriptions.cover where aws_sub = aws_sub_pro and cover_duration >0 and is_covered_auto = true
-- we selectionned here the patients list to cover for health pro with aws_sub = aws_sub_pro
),
sub_cover_list as (
select id_subs from subscriptions.subscribers_cover inner join cover_list on cover_list.id_cover = subscribers_cover.id_cover
-- list of subscribers covered
),
no_sub_cover_list as (
select id_free_trial from subscriptions.no_subscribers_cover inner join cover_list on cover_list.id_cover = no_subscribers_cover.id_cover
-- list of no subscribers covered
)
-----------------------------------------------------------------------------------------------------------------------------
select array( select id_subs from subscriptions.subscribers_cover inner join cover_list on cover_list.id_cover = subscribers_cover.id_cover
) into sub_list; -- convert list of subscribers covered into array list
if array_upper(sub_list,1) <>0 then -- if array list is not empty
begin
for i in 1 .. array_upper (sub_list,1) loop -- for every one in this list
if date_tour = (select sub_deadline_date from subscriptions.subscription where id_subs =sub_list[i] ) then -- if tour date is equals to
-- the deadline date
begin
update subscriptions.subscription
set
sub_expiration_date = sub_expiration_date + interval'30 days', -- add 30 days to the exp date
sub_deadline_date = sub_deadline_date + interval'30 days', -- add 30 date to deadline date
sub_source = aws_sub_pro, -- supply source is no the professional
is_sub_activ = false -- we turn off patients subscription
where id_subs = (select id_subs from subscriptions.subscription where id_subs =sub_list[i] );
end;
end if;
end loop;
end;
end if;
--------------------------------------------------------------------------------------------------------------------------------
select array(select id_free_trial from subscriptions.no_subscribers_cover inner join cover_list on cover_list.id_cover = no_subscribers_cover.id_cover
) into no_sub_list;
if array_upper(no_sub_list,1) <>0 then
begin
for i in 1 .. array_upper (no_sub_list,1) loop
if date_tour = (select expiration_date from subscriptions.free_trial where id_free_trial =no_sub_list[i] and is_free_trial_activ = true ) then
begin
update subscriptions.free_trial
set
expiration_date = expiration_date + interval'30 days'
where id_free_trial = (select id_free_trial from subscriptions.free_trial where id_free_trial =no_sub_list[i] );
end;
end if;
end loop;
end;
end if;
end;
else
raise 'tour date must be later than today''s date' using errcode='71';
end if;
end;
else
raise notice 'your cover list is empty. you don''t have any patient to cover' using errcode='70';
end if;
end;
$BODY$;
ALTER FUNCTION subscriptions.to_supply_patients(character varying, timestamp with time zone)
OWNER TO master_pgsql_hygeexv2;
When I run this function, I get the following error:
ERROR: ERREUR: la relation « cover_list » n'existe pas
LINE 1: ...rom subscriptions.no_subscribers_cover inner join cover_list...
Translated:
(the relation « cover_list » does not exist )
I tried to run only the CTE in a query window and I get the same error message.
Is there something I am missing?
The CTE is part of the SQL statement and not visible anywhere outside of it.
So you can use cover_list only in the SELECT statement with the WITH clause.
Either repeat the WITH clause in the second SELECT statement or refractor the code so you need only a single query.
An alternative would be to use a temporary table.
Related
I am writing an SQL query for creating the partitions which looks like:
DO
$$
DECLARE
table_name text := 'table_1';
start_date date := (SELECT MIN(create_date)
FROM db.table);
end_date date := (SELECT MAX(create_date)
FROM db.table);
partition_interval interval := '1 day';
partition_column_value text;
BEGIN
FOR partition_column_value IN SELECT start_date +
(generate_series * extract(day from partition_interval)::integer)::date
FROM generate_series(0, extract(day from end_date - start_date::date) /
extract(day from partition_interval))
LOOP
EXECUTE format(
'create table if not exists %1$s_%2$s partition of %1$s for values in (%2$s) partition by list (create_date)',
table_name, partition_column_value::date);
END LOOP;
END
$$;
I get an error:
[42883] ERROR: function pg_catalog.extract(unknown, integer) does not exist
Hint: No function matches the given name and argument types. You might need to add explicit type casts.
Where: PL/pgSQL function inline_code_block line 9 at FOR over SELECT rows
The immediate cause of the error msg is this:
extract(day from end_date - start_date::date)
It's nonsense to cast start_date::date, start_date being type date to begin with. More importantly, date - date yields integer (not interval like you might assume). And extract() does not operate on integer input.
I removed more confusion and noise to arrive at this:
DO
$do$
DECLARE
table_name text := 'table_1';
partition_interval integer := 1; -- given in days!!
start_date date;
end_date date;
partition_column_value text;
BEGIN
SELECT INTO start_date, end_date -- two assignments for the price of one
min(create_date), max(create_date)
FROM db.table;
FOR partition_column_value IN
SELECT start_date + g * partition_interval -- date + int → date
FROM generate_series(0, (end_date - start_date) -- date - date → int
/ partition_interval) g
LOOP
EXECUTE format(
'CREATE TABLE IF NOT EXISTS %1$I PARTITION OF %1$I
FOR VALUES IN (%3$L) PARTITION BY LIST (create_date)'
, table_name || to_char(partition_column_value, '"_"yyyymmdd') -- !
, table_name
, partition_column_value::text -- only covers single day!!
);
END LOOP;
END
$do$;
This should work.
But it only makes sense for the example interval of '1 day'. For longer intervals, concatenate the list of days per partition or switch to range partitioning ...
I am trying to query the following data:
Student_ID Site Start End Primary_or_Secondary
1 A 1/1/19 2/28/19 Primary
1 B 2/1/19 6/30/19 Secondary
1 C 3/1/19 6/30/19 Primary
and get a result that looks like the following:
Student_ID Primary Secondary Start End
1 A null 1/1/19 1/31/19
1 A B 2/1/19 2/28/19
1 C B 3/1/19 6/30/19
So basically, a site can be primary or secondary site for a student, and I want to be able to see all the time frames the student are enrolled separately instead of any time frame overlapping.
I have wracked my brain about how I might do this in PostgreSQL, and have even looked at the crosstab function, but the dates are making it hard for my brain :-)
Any help with a query or set of queries, including some CTEs would be really helpful!
This isn't trivial. A mix of crosstab with overlapping and intersecting ranges, plus corner cases (merge same start / end dates) on top. Very hard to solve with set-based operations i.e. pure SQL.
I suggest a procedural solution in PL/pgSQL instead. Should perform nicely, too, as it only needs a single (bitmap-index) scan over the table:
CREATE OR REPLACE FUNCTION f_student_xtab(VARIADIC _student_ids int[])
RETURNS TABLE (
student_id int
, "primary" text
, secondary text
, start_date date
, end_date date) AS
$func$
DECLARE
r record;
BEGIN
student_id := -1; -- init with impossible value
FOR r IN
SELECT t.student_id, t.site, t.primary_or_secondary = 'Primary' AS prim, l.range_end, l.date
FROM tbl t
CROSS JOIN LATERAL (
VALUES (false, t.start_date)
, (true , t.end_date)
) AS l(range_end, date)
WHERE t.student_id = ANY (_student_ids)
ORDER BY t.student_id, l.date, range_end -- start of range first
LOOP
IF r.student_id <> student_id THEN
student_id := r.student_id;
IF r.prim THEN "primary" := r.site;
ELSE secondary := r.site;
END IF;
start_date := r.date;
ELSIF r.range_end THEN
IF r.date < start_date THEN
-- range already reported
IF r.prim THEN "primary" := NULL;
ELSE secondary := NULL;
END IF;
start_date := NULL;
ELSE
end_date := r.date;
RETURN NEXT;
IF r.prim THEN
"primary" := NULL;
IF secondary IS NULL THEN start_date := NULL;
ELSE start_date := r.date + 1;
END IF;
ELSE
secondary := NULL;
IF "primary" IS NULL THEN start_date := NULL;
ELSE start_date := r.date + 1;
END IF;
END IF;
end_date := NULL;
END IF;
ELSE -- range starts
IF r.date > start_date THEN
-- range already running
end_date := r.date - 1;
RETURN NEXT;
END IF;
start_date := r.date;
end_date := NULL;
IF r.prim THEN "primary" := r.site;
ELSE secondary := r.site;
END IF;
END IF;
END LOOP;
END
$func$ LANGUAGE plpgsql;
Call:
SELECT * FROM f_student_xtab(1,2,3);
Or:
SELECT * FROM f_student_xtab(VARIADIC '{1,2,3}');
db<>fiddle here - with extended test case
About VARIADIC:
Pass multiple values in single parameter
Return rows matching elements of input array in plpgsql function
I'm developing a desktop application that works like Risiko!'s game, based on some database procedures.
I need to assign to each player a tot of rows which are not equal to each other assigned to other players.
I've tried for something like this, but doesn't work:
`create or replace PROCEDURE "giveterritory"
(idpartita NUMBER,nr_partecipanti number )
is
begin
DECLARE
CURSOR cur_file IS
SELECT *
FROM Player
WHERE idgame = Player.idgame ;
var_cur cur_file%ROWTYPE;
cont number ;
nr_territories_gioc_prec number ;
G1_Terr int ;
G2_Terr int ;
G3_Terr int ;
G1_ID number;
G2_ID number;
G3_ID number;
begin
cont:=0 ;
for var_cur in cur_file
loop
cont := cont + 1 ;
update territory
set Player_owner = var_cur.id_Player
where ROWNUM = MOD( MOD ( number_RANDOM(),42 ) ,ROWNUM );
G1_Terr := var_cur.number_territories_tot ; -- mi salvo i territories del primo Player
G1_ID := var_cur.id_Player ;
end if ;
if (cont=2 ) then
update territory
set Player_owner = var_cur.id_Player
where ROWNUM = MOD( MOD ( number_RANDOM(),42 -G1_Terr ) , ROWNUM ) and id_territory NOT IN ( select Player_owner from territory where ;
G2_Terr := var_cur.number_territories_tot ;
end if ;
if (cont=3 ) then
update territory
set Player_owner = var_cur.id_Player
where ROWNUM <= 42 - G1_Terr - G2_Terr;
--where ROWNUM <= 42 - var_cur.number_territories_tot * 2 ;
G3_Terr := var_cur.number_territories_tot ;
end if ;
end loop;
end;
end;`
number_random code:
create or replace FUNCTION "NUMBER_RANDOM"
return NUMBER
is
numbrndm int;
begin
select dbms_random.value(1,99999999) into numbrndm
from dual;
return numbrndm;
end;
If I understand correctly, you're trying to assign each player a random set of territories?
If so, sort the territories in random order and assign them to players round robin. Here's a MERGE statement that should do that. I apologize for any minor syntax errors: I am not in front of an Oracle database right now.
MERGE INTO territory t
USING (
WITH players AS ( SELECT rownum player#, id_player
FROM player
WHERE idgame=:idgame ),
players_count AS ( SELECT count(*) players_count FROM players )
-- Get the id_player for each assignment
SELECT assignedt.id_territory, p.id_player
FROM
(
-- Assign the randomly-ordered territories to players in round-robin format
SELECT randt.id_territory,
mod(rownum, pc.players_count)+1 player#
FROM (
-- Query the territories in a random order
SELECT t.id_territory
FROM territory t
ORDER BY dbms_random.random ) randt, players_count pc
-- Optional:
-- ... add a where clause to limit rownum <= some number to make
-- ... sure each player receives an even number of territories.
-- ... You'd need the count of territories to do that.
) assignedt inner join players p on p.player# = assignedt.player#
) u
ON ( u.id_territory = t.id_territory )
WHEN MATCHED THEN UPDATE SET t.id_player = u.id_player;
I'd put a random number column in your SQL selects for each row. Then you can order your record set by the random number directly in SQL.
Do this for both players and territory lists.
This example should work for any number of territories and players. If the territories don't divide up equally, it will be at random which players get one extra territory.
We are going to loop through the territories list once, in random order. We are going to step through the player list until we reach the end, then reload the player list again. This happens as many times as needed.
Might need to fix this for syntax or logic. I don't have an Oracle instance to test it.
CREATE OR REPLACE PROCEDURE giveterritory (pID_GAME PLS_INTEGER) IS
CURSOR c_player IS
SELECT id_player, dbms_random.value() rnd
FROM players
WHERE id_game = pID_GAME
ORDER BY 2 desc;
--
CURSOR c_territory IS
SELECT id_territory, dbms_random.value() rnd
FROM territories
WHERE id_game = pID_GAME
FOR UPDATE of id_player
ORDER BY 2 desc;
--
l_player_row c_player%ROWTYPE;
l_territory_row c_territory%ROWTYPE;
BEGIN
OPEN c_player;
FETCH c_player into l_player_row;
--
FOR r in c_territory
LOOP
UPDATE territories
SET id_player = l_player_row.id_player
WHERE CURRENT OF c_territory;
--
FETCH c_player INTO l_player_row;
IF c_player%NOTFOUND THEN
CLOSE c_player;
OPEN c_player;
FETCH c_player INTO l_player_row;
END IF;
END LOOP;
END;
I've created a PL/SQL package with functions but when the package is called by passing the input values, then I get the following message:
Error report:
ORA-06503: PL/SQL: Function returned without value
ORA-06512: at "APPDATA.PWH_RELEASE_PAYMENT_ORDER_PKG", line 107
ORA-06512: at "APPDATA.PWH_RELEASE_PAYMENT_ORDER_PKG", line 158
ORA-06512: at line 36
06503. 00000 - "PL/SQL: Function returned without value"
*Cause: A call to PL/SQL function completed, but no RETURN statement was executed.*Action: Rewrite PL/SQL function, making sure that it always returns a value of a proper type.
Here's the package call being run:
/* Type Bank ID Collection */
CREATE OR REPLACE TYPE TYPE_BANKID_COLL
FORCE AS TABLE OF VARCHAR2(35 CHAR); -- Type BankId Collection
/
/* Type processing line collection */
create or replace TYPE TYPE_PROCESSINGLINE_COLL FORCE AS TABLE OF VARCHAR2(50 CHAR);
/
/* Type processing line ID collection */
CREATE OR REPLACE TYPE TYPE_PROCESSINGLINEID_COLL FORCE AS TABLE OF NUMBER;
/
/* Type Processing line flows - Object type 1 */
CREATE OR REPLACE TYPE TYPE_PROCESSINGLINEFLOWS FORCE AS OBJECT
(
processinglineid NUMBER,
processinglineflow TYPE_PROCESSINGLINEFLOW
);
/
-- 2 data types
/* Type Processing line flows coll - Nested table of Object type 1 */
CREATE OR REPLACE TYPE TYPE_PROCESSINGLINEFLOWS_COLL FORCE AS TABLE OF TYPE_PROCESSINGLINEFLOWS;
/
-- Type Processing line flows Collection
/* Type Processing line payment - Object type 2 */
create or replace TYPE TYPE_PROCESSINGLINEPAYMENT FORCE AS OBJECT
(
processingline VARCHAR2(50 CHAR),
processingLineFlows TYPE_PROCESSINGLINEFLOW_COLL,
messages TYPE_MESSAGE_COLL,
reservationRefs TYPE_RSVDTLS_COLL
);
/
/* Type Processing line payment Collection - Nested table of Object type 2 */
CREATE OR REPLACE TYPE TYPE_PROCESSINGLINEPAYMT_COLL FORCE AS TABLE OF TYPE_PROCESSINGLINEPAYMENT;
/
/* Type Bank due payment - Object type 3 */
CREATE OR REPLACE TYPE TYPE_BANKDUEDATEPAYMENT FORCE AS OBJECT
(
bankid VARCHAR2(35 CHAR),
payments TYPE_PROCESSINGLINEPAYMT_COLL
)
/
/* Type Bank due date payment Collection - Nested table of Object type 3 */
CREATE OR REPLACE TYPE TYPE_BANKDUEDATEPAYMENT_COLL FORCE AS TABLE OF TYPE_BANKDUEDATEPAYMENT;
/
/* Type POR Due date process */
CREATE OR REPLACE TYPE TYPE_PORDUEDATEPROCESS FORCE AS OBJECT
(
duedateid NUMBER,
payments TYPE_BANKDUEDATEPAYMENT_COLL
);
/
Here's the package code:
/* Start of package header - pwh release payment order pkg */
create or replace PACKAGE pwh_release_payment_order_pkg AS
/*
======================================================================================
Constants
======================================================================================
*/
c_readyForReservationEngine CONSTANT CHAR(4) := 'RFRE'; -- ready for reservation engine
c_readyForPaymentEngine CONSTANT CHAR(4) := 'RFPE'; -- ready for payment engine
-- releaseProcessOrder
FUNCTION releaseProcessOrder (
p_processinglines TYPE_PROCESSINGLINE_COLL, -- p_processinglines : TYPE_PROCESSINGLINES
/*
create or replace TYPE TYPE_PROCESSINGLINE_COLL FORCE AS TABLE OF VARCHAR2(50 CHAR);
*/
p_portype VARCHAR2, -- Type BankID Collection
p_banksinprocess TYPE_BANKID_COLL, -- Banks in process
/*
From the types file:
CREATE OR REPLACE TYPE TYPE_BANKID_COLL
FORCE AS TABLE OF VARCHAR2(35 CHAR);
*/
p_releasedate paymentinfo.releasedate%type, -- Release Date
p_paymentstatuses TYPE_PAYMENT_STATUS_COLL, -- Payment Statuses
p_processingdate duedateprocess.processingdate%type -- Type release payment order
) RETURN TYPE_PORDUEDATEPROCESS;
/* End of the package header */
END pwh_release_payment_order_pkg;
/
/* End of package header - End of pwh release payment order pkg */
/* Start of package body */
create or replace PACKAGE BODY pwh_release_payment_order_pkg
AS
FUNCTION getRsvDtlsForTransactions( /* get Reservation Details For Transactions */
p_transactionIds TYPE_TRANSACTIONID_COLL
) RETURN TYPE_RSVDTLS_COLL /* Type reservation details collection */
IS
v_reservationDetails TYPE_RSVDTLS_COLL := TYPE_RSVDTLS_COLL(); /* v_reservationDetails */
BEGIN
SELECT TYPE_RSVDTLS( reservationreference, reservationtime, reservedamount, currencycode, fk_transactionsid ) BULK COLLECT
/*
*/
INTO v_reservationDetails
FROM reservation_details rsvd
INNER JOIN TABLE(p_transactionIds) tids /* p_transactionIds - tids */
ON rsvd.fk_transactionsid = tids.COLUMN_VALUE;
RETURN v_reservationDetails; /* return v_reservationdetails */
END;
FUNCTION getProcessingLineFlows(
p_processingLineIds IN TYPE_PROCESSINGLINEID_COLL,
v_status OUT VARCHAR2
)RETURN TYPE_PROCESSINGLINEFLOWS_COLL
AS
v_processinglineflows TYPE_PROCESSINGLINEFLOWS_COLL := TYPE_PROCESSINGLINEFLOWS_COLL();
v_cnt NUMBER;
v_processingLineIds TYPE_PROCESSINGLINEID_COLL := TYPE_PROCESSINGLINEID_COLL();
BEGIN
SELECT TYPE_PROCESSINGLINEFLOWS(fk_processinglineid, TYPE_PROCESSINGLINEFLOW( FUNCTION, component )) BULK COLLECT
INTO v_processingLineFlows
FROM processinglineflow flow
INNER JOIN TABLE (p_processingLineIds) p
ON flow.fk_processinglineid = p.COLUMN_VALUE;
SELECT COUNT(flw.processinglineflow.function)
INTO v_cnt
FROM TABLE (v_processinglineflows) flw
WHERE flw.processinglineflow.function = 'FUND_CHECK';
IF v_cnt = p_processingLineIds.count THEN
SELECT COUNT(DISTINCT flw.processinglineflow.component)
INTO v_cnt
FROM TABLE (v_processinglineflows) flw
WHERE flw.processinglineflow.function = 'FUND_CHECK';
IF v_cnt = 1 THEN
v_status := c_readyForReservationEngine;
RETURN v_processingLineFlows;
END IF;
ELSIF v_cnt = 0 THEN
SELECT COUNT(flw.processinglineflow.function)
INTO v_cnt
FROM TABLE (v_processinglineflows) flw
WHERE flw.processinglineflow.function = 'PE_SELECTION';
IF v_cnt = 1 THEN
v_status := c_readyForPaymentEngine;
RETURN v_processingLineFlows;
END IF;
ELSE
raise_application_error (-20301, 'Invalid processing lines');
END IF;
END;
FUNCTION releaseProcessOrder(
p_processinglines TYPE_PROCESSINGLINE_COLL,
p_portype VARCHAR2,
p_banksinprocess TYPE_BANKID_COLL,
p_releasedate paymentinfo.releasedate%type,
p_paymentstatuses TYPE_PAYMENT_STATUS_COLL,
p_processingdate duedateprocess.processingdate%type
)RETURN TYPE_PORDUEDATEPROCESS
AS
v_status VARCHAR2(4 CHAR);
v_functionFundsCheck CHAR(10) := 'FUND_CHECK';
v_updatedTransactions TYPE_TRANSACTIONID_COLL := TYPE_TRANSACTIONID_COLL();
v_transactionids TYPE_TRANSACTIONID_COLL := TYPE_TRANSACTIONID_COLL();
v_transactionidsforpl TYPE_TRANSACTIONID_COLL := TYPE_TRANSACTIONID_COLL();
v_processinglineids TYPE_PROCESSINGLINEID_COLL := TYPE_PROCESSINGLINEID_COLL();
v_dueDateProcessId duedateprocess.duedateprocessid%type;
v_processingLineFlows TYPE_PROCESSINGLINEFLOWS_COLL := TYPE_PROCESSINGLINEFLOWS_COLL();
v_payments TYPE_BANKDUEDATEPAYMENT_COLL := TYPE_BANKDUEDATEPAYMENT_COLL();
v_releasepaymentorder TYPE_PORDUEDATEPROCESS;
v_flw TYPE_PROCESSINGLINEFLOW_COLL := TYPE_PROCESSINGLINEFLOW_COLL();
v_duepay TYPE_PROCESSINGLINEPAYMENT;
v_duepayments TYPE_PROCESSINGLINEPAYMT_COLL := TYPE_PROCESSINGLINEPAYMT_COLL();
BEGIN
SELECT p.processinglineid BULK COLLECT
INTO v_processinglineids
FROM processingline p
INNER JOIN TABLE(p_processinglines) pl
ON p.processingline = pl.COLUMN_VALUE;
v_processingLineFlows := getProcessingLineFlows(v_processinglineids,v_status);
v_dueDateProcessId := DUEDATE_ID_SEQ.NEXTVAL;
INSERT
INTO duedateprocess
(
duedateprocessid,
processingdate,
fk_processinglineid
)
SELECT v_dueDateProcessId,
NVL(p_processingdate,SYSDATE),
p.COLUMN_VALUE
FROM TABLE (v_processinglineids) p;
SELECT TYPE_PORDUEDATEPROCESS(v_dueDateProcessId,TYPE_BANKDUEDATEPAYMENT_COLL())
INTO v_releasepaymentorder
FROM DUAL;
IF p_portype = 'PrioritizedPOR' THEN
UPDATE transactions
SET paymentstatus = v_status -- If the por type is 'PrioritizedPOR',then paymentstatus is set to 'v_status'
WHERE paymentstatus IN ('RFDD','LAFU','TERE')
AND riskStatus IS NULL
AND fk_processinglineid IN (SELECT l.COLUMN_VALUE FROM TABLE(v_processinglineids)l)
AND fk_paymentinfoid IN
(SELECT paymentinfoid -- Select paymentinfo ID INNER JOIN the message table
FROM paymentinfo p
INNER JOIN MESSAGE m
ON m.messageid = p.fk_messageid -- Message m
AND p.releasedate <= SYSDATE -- releasedate is the current date
AND m.markettype = 'BM'
AND p.prioritized = 'Y'
AND p.paymentmethod IN ('TRF') AND categorypurposecode = 'INTC'
AND (p_banksinprocess IS NULL
OR initiatorbankid IN
(SELECT b.COLUMN_VALUE FROM TABLE(p_banksinprocess) b
))
) RETURNING transactionsid BULK COLLECT -- RETURNING transactionsid BULK COLLECT into v_updatedTransactions
INTO v_updatedTransactions;
ELSIF p_portype = 'CompletePOR' THEN
UPDATE transactions
SET paymentstatus = v_status
WHERE paymentstatus IN ('RFDD','LAFU','TERE')
AND riskStatus IS NULL
AND fk_processinglineid IN (SELECT l.COLUMN_VALUE FROM TABLE(v_processinglineids)l)
AND fk_paymentinfoid IN
(SELECT paymentinfoid
FROM paymentinfo p
INNER JOIN MESSAGE m
ON m.messageid = p.fk_messageid
AND p.releasedate <= SYSDATE
AND m.markettype = 'BM'
AND (p_banksinprocess IS NULL
OR initiatorbankid IN
(SELECT b.COLUMN_VALUE FROM TABLE(p_banksinprocess) b
))
) RETURNING transactionsid BULK COLLECT
INTO v_updatedTransactions;
ELSIF p_portype IS NULL THEN
UPDATE transactions
SET paymentstatus = v_status
WHERE paymentstatus IN (SELECT COLUMN_VALUE FROM TABLE(p_paymentstatuses))
AND riskStatus IS NULL
AND fk_paymentinfoid IN (
SELECT paymentinfoid
FROM paymentinfo
WHERE releasedate <= p_releasedate
AND fk_processinglineid = (SELECT l.COLUMN_VALUE FROM TABLE(v_processinglineids)l)
) RETURNING transactionsid
BULK COLLECT INTO v_updatedTransactions;
END IF;
IF v_updatedTransactions.COUNT = 0 THEN
raise_application_error (-20302, 'There are no payments to process for processing lines');
END IF;
INSERT INTO duedateprocesstotransactions (fk_duedateprocessid, fk_transactionsid)
SELECT v_dueDateProcessId, ut.COLUMN_VALUE
FROM TABLE(v_updatedTransactions) ut;
SELECT TYPE_BANKDUEDATEPAYMENT(initiatorbankid,TYPE_PROCESSINGLINEPAYMT_COLL()) BULK COLLECT
INTO v_payments
FROM transactions t
INNER JOIN paymentinfo p
ON p.paymentinfoid = t.fk_paymentinfoid
INNER JOIN MESSAGE m
ON m.messageid = p.fk_messageid
INNER JOIN TABLE(v_updatedTransactions) tid
ON t.transactionsid = tid.COLUMN_VALUE
GROUP BY m.initiatorbankid;
FOR i IN v_payments.FIRST..v_payments.LAST
LOOP
SELECT tid.COLUMN_VALUE BULK COLLECT
INTO v_transactionids
FROM transactions t
INNER JOIN paymentinfo p
ON p.paymentinfoid = t.fk_paymentinfoid
INNER JOIN MESSAGE m
ON m.messageid = p.fk_messageid
INNER JOIN TABLE(v_updatedTransactions) tid
ON t.transactionsid = tid.COLUMN_VALUE
AND m.initiatorbankid = v_payments(i).bankid;
FOR j IN v_processinglineids.FIRST..v_processinglineids.LAST
LOOP
SELECT TYPE_PROCESSINGLINEPAYMENT(processingline,TYPE_PROCESSINGLINEFLOW_COLL(),TYPE_MESSAGE_COLL(),TYPE_RSVDTLS_COLL())
INTO v_duepay
FROM PROCESSINGLINE
WHERE processinglineid = v_processinglineids(j);
SELECT TYPE_PROCESSINGLINEFLOW(plf.function,plf.component) BULK COLLECT
INTO v_flw
FROM processinglineflow plf
WHERE fk_processinglineid IN
(SELECT fk_processinglineid
FROM transactions t
INNER JOIN TABLE(v_transactionids)ut
ON t.transactionsid = ut.COLUMN_VALUE
AND t.fk_processinglineid = v_processinglineids(j)
);
v_duepay.processinglineflows := v_flw;
SELECT ut.COLUMN_VALUE BULK COLLECT
INTO v_transactionidsforpl
FROM transactions t
INNER JOIN TABLE(v_transactionids)ut
ON t.transactionsid = ut.COLUMN_VALUE
AND t.fk_processinglineid = v_processinglineids(j);
v_duepay.reservationRefs := getRsvDtlsForTransactions(v_transactionidsforpl);
v_duepay.messages := pwh_read_pkg.read_duedate_payments(v_transactionidsforpl);
v_duepayments.EXTEND;
v_duepayments(v_duepayments.COUNT):= v_duepay;
END LOOP;
v_payments(i).payments := v_duepayments;
END LOOP;
v_releasepaymentorder.payments := v_payments;
RETURN v_releasepaymentorder;
END;
END pwh_release_payment_order_pkg;
/
Why is the error getting generated ?
A better way to do this is to have a value of the return type initialized at the start of the function and the last statement in the function should return it.
This way there is only one return statement and it is always called unless there is an exception.
There also appears to be an issue with the nesting of your IF statements. They way they are nested does not guarantee that a RETURN statement will be reached.
The issue in your code is shown here where I abbreviate your code and annotate it.
FUNCTION getProcessingLineFlows(
p_processingLineIds IN TYPE_PROCESSINGLINEID_COLL,
v_status OUT VARCHAR2)
RETURN TYPE_PROCESSINGLINEFLOWS_COLL
AS
v_processinglineflows TYPE_PROCESSINGLINEFLOWS_COLL := TYPE_PROCESSINGLINEFLOWS_COLL();
v_cnt NUMBER;
v_processingLineIds TYPE_PROCESSINGLINEID_COLL := TYPE_PROCESSINGLINEID_COLL();
BEGIN
IF v_cnt = p_processingLineIds.count THEN
SELECT COUNT(DISTINCT flw.processinglineflow.component)
INTO v_cnt
FROM TABLE (v_processinglineflows) flw
WHERE flw.processinglineflow.function = 'FUND_CHECK';
IF v_cnt = 1 THEN
v_status := c_readyForReservationEngine;
--return from middle of the function
--hard to trace and debug
--and is the RETURN value initialized?
RETURN v_processingLineFlows;
END IF;
--if v_cnt is not 1 then nothing is returned from the branch above this
--the nested if loops are hard to track
ELSIF v_cnt = 0 THEN
SELECT COUNT(flw.processinglineflow.function)
INTO v_cnt
FROM TABLE (v_processinglineflows) flw
WHERE flw.processinglineflow.function = 'PE_SELECTION';
IF v_cnt = 1 THEN
v_status := c_readyForPaymentEngine;
--v_status is initialized but not necessarily your RETURN value
RETURN v_processingLineFlows;
END IF;
ELSE
raise_application_error (-20301, 'Invalid processing lines');
--if the no suitable values are found then nothing is returned
END IF;
Refer the create function documentation to find the correct syntax of definition.
CREATE FUNCTION : ORACLE
Make sure that every possible code path in every function has a RETURN. For example, in your getProcessingLineFlows function, you have a branch that starts:
IF v_cnt = p_processingLineIds.count THEN
SELECT COUNT(DISTINCT flw.processinglineflow.component)
INTO v_cnt
FROM TABLE (v_processinglineflows) flw
WHERE flw.processinglineflow.function = 'FUND_CHECK';
IF v_cnt = 1 THEN
v_status := c_readyForReservationEngine;
RETURN v_processingLineFlows;
END IF;
ELSIF v_cnt = 0 THEN
...
It is hard to read your code as formatted, but I believe that in the number of distinct flw.processinglineflow.component values is not exactly 1, then this function will exit without returning a value. I have no idea about your data or whether it is possible for that value to be anything other than 1 (although, if it's not, why are you checking it?). But this is the sort of code structure that causes the error you are asking about.
You need to do similar checks on all your code paths and make sure there is no way for any function to end without returning.
I am stuck at a place.
There is a procedure that checks for something and inserts into an table type upon successful determination of that condition.
But i can insert only once in the table type. Is there a way to insert again and again into the table type.
PROCEDURE "hello"."helloWorld.db::sampleException" (OUT TRACE_RECORD "hello"."LogTrace" )
LANGUAGE SQLSCRIPT AS
BEGIN
DECLARE i int;
select count(*) into i from "hello"."REGION";
IF :i > 1 then
TRACE_RECORD = SELECT '1' AS "LogID", '1' AS "TraceID" FROM DUMMY;
end if;
IF :i > 2 then
TRACE_RECORD = SELECT '2' AS "LogID", '2' AS "TraceID" FROM DUMMY;
end if;
END;
What i get on executing the procedure is only the last record "2,2".
How can i insert both the records 1,1 and 2,2.
Note: I do not want to use Temporary Tables.
Any help on this..
Thanks.!
Editing the Question a bit:
-I have to use Table TYPE (till the time there is no optimal way better than it)
-I have to insert more than 20-30 records in the table type.
Do you have to write this as a procedure? A table-valued function seems more suitable:
CREATE FUNCTION f_tables4 (in_id INTEGER)
RETURNS TABLE (
"LogID" VARCHAR(400),
"TraceID" VARCHAR(400)
)
LANGUAGE SQLSCRIPT
AS
BEGIN
RETURN
SELECT t."LogID", t."TraceID"
FROM (
SELECT 1 AS i, '1' AS "LogID", '1' AS "TraceID" FROM DUMMY
UNION ALL
SELECT 2 AS i, '2' AS "LogID", '2' AS "TraceID" FROM DUMMY
) t
JOIN (SELECT count(*) AS cnt FROM "hello"."REGION") c
ON c.cnt > t.i
END