Oracle to Postgres Conversion trouble shooting - plpgsql

Converted a Standalone Procedure from Oracle to Postgres but not sure why there is an run error even the code is successfully compiled
Converted the below code from Oracle to Postgres
CREATE OR REPLACE FUNCTION ssp2_pcat.pop_hoa_contracts_for_prod(
)
RETURNS void
LANGUAGE 'plpgsql'
COST 100
VOLATILE
AS $BODY$
DECLARE
C1 CURSOR for
SELECT MARKET_CODE, CONTRACT_COMBO_ID, COUNT(*) FROM
ssp2_pcat.VPF_HOA_CONTRACTS_FOR_PROD A
WHERE start_Date IN
(SELECT MAX(start_date)
FROM VPF_HOA_CONTRACTS_FOR_PROD b
WHERE A.MARKET_CODE = b.MARKET_CODE
AND A.CONTRACT_COMBO_ID = b.CONTRACT_COMBO_ID
AND A.CONTRACT_ID = B.CONTRACT_ID
AND b.start_date <= current_date
AND b.end_date > current_date )
GROUP BY MARKET_CODE, CONTRACT_COMBO_ID
ORDER BY MARKET_CODE, CONTRACT_COMBO_ID;
C2 CURSOR(iMktCode VARCHAR, iCombo integer) for
SELECT MARKET_CODE, CONTRACT_COMBO_ID, CONTRACT_ID
FROM ssp2_pcat.VPF_HOA_CONTRACTS_FOR_PROD A
WHERE start_Date IN
(SELECT MAX(start_date)
FROM ssp2_pcat.VPF_HOA_CONTRACTS_FOR_PROD b
WHERE A.MARKET_CODE = b.MARKET_CODE
AND A.CONTRACT_COMBO_ID = b.CONTRACT_COMBO_ID
AND A.CONTRACT_ID = B.CONTRACT_ID
AND b.start_date <= current_date
AND b.end_date > current_date )
AND MARKET_CODE = iMktCode
AND CONTRACT_COMBO_ID = iCombo
ORDER BY MARKET_CODE, CONTRACT_COMBO_ID, START_DATE;
Contracts VARCHAR(32000);
Contract_Val1 VARCHAR(4000) := NULL;
Contract_Val2 VARCHAR(4000) := NULL;
Contract_Val3 VARCHAR(4000) := NULL;
Contract_Val4 VARCHAR(4000) := NULL;
Contract_Val5 VARCHAR(4000) := NULL;
Contract_Val6 VARCHAR(4000) := NULL;
Contract_Val7 VARCHAR(4000) := NULL;
Contract_Val8 VARCHAR(4000) := NULL;
Num INTEGER;
Cont_Num INTEGER;
l_start TIMESTAMP := clock_timestamp();
l_end TIMESTAMP := clock_timestamp();
Time_Taken VARCHAR(20);
i record;
j record;
BEGIN
l_start := clock_timestamp();
DELETE FROM ssp2_pcat.HOA_CONTRACTS_KH;
FOR i IN C1 LOOP
BEGIN
Num := 0;
Contracts := NULL;
Cont_Num := 1;
FOR j IN C2 (i.MARKET_CODE, i.CONTRACT_COMBO_ID) LOOP
Num := Num + 1;
IF Num = 1 THEN
Contracts := '|' || j.CONTRACT_ID || '|';
ELSE
IF LENGTH(Contracts || j.CONTRACT_ID || '|') > 4000 THEN
PERFORM ssp2_pcat.Assign (Cont_Num, SUBSTRING(Contracts, 1,
LENGTH(Contracts)-1));
Num := 1;
Contracts := '|' || j.CONTRACT_ID || '|';
Cont_Num := Cont_Num + 1;
ELSE
Contracts := Contracts || j.CONTRACT_ID || '|';
END IF;
END IF;
END LOOP;
PERFORM ssp2_pcat.Assign (Cont_Num, Contracts);
IF Cont_Num > 5 THEN
raise notice'%', ('MARKET_CODE: ' || i.MARKET_CODE || ', CONTRACT_COMBO_ID: ' || i.CONTRACT_COMBO_ID || ' has more than 32K in size. These Contracts are left out: ' || Contracts);
END IF;
INSERT INTO HOA_CONTRACTS_KH
(
MARKET_CODE,
CONTRACT_COMBO_ID,
CONTRACT_ID,
CONTRACT_ID2,
CONTRACT_ID3,
CONTRACT_ID4,
CONTRACT_ID5,
LAST_UPDATED
)
VALUES
(
i.MARKET_CODE,
i.CONTRACT_COMBO_ID,
Contract_Val1,
Contract_Val2,
Contract_Val3,
Contract_Val4,
Contract_Val5,
CURRENT_TIMESTAMP::TIMESTAMP(0)
);
Contract_Val1 := NULL;
Contract_Val2 := NULL;
Contract_Val3 := NULL;
Contract_Val4 := NULL;
Contract_Val5 := NULL;
Contract_Val6 := NULL;
Contract_Val7 := NULL;
Contract_Val8 := NULL;
EXCEPTION
WHEN OTHERS THEN
ROLLBACK;
raise notice'%', ('1) POP_HOA_CONTRACTS_FOR_PROD: ' || SQLERRM);
END;
END LOOP;
RAISE NOTICE 'Function excution time Took: %', l_start;
RAISE NOTICE 'Function excution time Took: %',l_end-l_start;
SELECT l_end-l_start INTO Time_Taken;
raise notice'%',('POP_HOA_CONTRACTS_FOR_PROD Took: ' || Time_Taken );
EXCEPTION
WHEN OTHERS THEN
raise notice'%', ('2) POP_HOA_CONTRACTS_FOR_PROD: ' || SQLERRM);
END;
$BODY$;
The code is compiled successfully, but giving a run time error as follows,
NOTICE: 2) POP_HOA_CONTRACTS_FOR_PROD: cannot begin/end transactions in PL/pgSQL
Debugged the whole code and looks like still I'm unable to identify the issue, can any one help me in making me understand more about Postgres as I'm new to this Database. Found out in unit testing that its not calling the assign function mentioned in the code,

Related

Concurrent Usage of Function

I have a function called "ChallanNoGenerator" which created sequential number with combination with other columns.
This function is called from multiple other functions, the problem is that at concurrent call it generate duplicate "ChallanNo"
-- DROP FUNCTION "Fee"."ChallanNoGenerator"(uuid);
CREATE OR REPLACE FUNCTION "Fee"."ChallanNoGenerator"(
admissionformid uuid)
RETURNS text
LANGUAGE 'plpgsql'
COST 100
VOLATILE
AS $BODY$
declare "ChallanNumber" TEXT;
"CampusCode" TEXT;
"SessionCode" TEXT;
"BusinessUnitCode" TEXT;
"ChallanNumberEx" TEXT;
BEGIN
SELECT
CONCAT(bu."DigitCode", cm."DigitCode"), sn."FullName" INTO "CampusCode", "SessionCode"
FROM
"Admission"."AdmissionForm" AS af,
"Setup"."CampusProgramLink" AS cpl,
"Setup"."ProgramDetails" AS pd,
"Setup"."Campus" AS cm,
"Setup"."SubCity" AS sc,
"Setup"."City" AS ct,
"Setup"."Institution" AS ins,
"Setup"."Session" as sn,
"Setup"."BusinessUnit" AS bu
WHERE
af."CampusProgramId" = cpl."CampusProgramId"
AND pd."ProgramDetailId" = cpl."ProgramDetailId"
AND cpl."CampusId" = cm."CampusId"
AND cm."SubCityId" = sc."SubCityId"
AND sc."CityId" = ct."CityId"
AND cm."InstitutionId" = ins."InstitutionId"
AND cpl."SessionId" = sn."SessionId"
AND af."AdmissionFormId" = admissionformid;
-- SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;
SELECT ((RIGHT(COALESCE(MAX("ChallanNo"), '000000'), 6)::INT4) + 1) INTO "ChallanNumber" FROM "Fee"."StudentChallan"
WHERE "ChallanNo" LIKE "CampusCode" || "SessionCode" || '%';
SELECT ((RIGHT(COALESCE(MAX("ChallanNo"), '000000'), 6)::INT4) + 1) INTO "ChallanNumberEx" FROM "Fee"."StudentChallanEx"
WHERE "ChallanNo" LIKE "CampusCode" || "SessionCode" || '%';
IF (("ChallanNumber"::INT4) < ("ChallanNumberEx"::INT4)) THEN
"ChallanNumber" := "ChallanNumberEx";
END IF;
IF(LENGTH("ChallanNumber") = 1) THEN
"ChallanNumber" := '00000' || "ChallanNumber";
ELSIF(LENGTH("ChallanNumber") = 2) THEN
"ChallanNumber" := '0000' || "ChallanNumber";
ELSIF(LENGTH("ChallanNumber") = 3) THEN
"ChallanNumber" := '000' || "ChallanNumber";
ELSIF(LENGTH("ChallanNumber") = 4) THEN
"ChallanNumber" := '00' || "ChallanNumber";
ELSIF(LENGTH("ChallanNumber") = 5) THEN
"ChallanNumber" := '0' || "ChallanNumber";
END IF;
RETURN "CampusCode" || "SessionCode" || "ChallanNumber";
END
$BODY$;
ALTER FUNCTION "Fee"."ChallanNoGenerator"(uuid)
OWNER TO postgres;
this is the first function that I have created to generate challan no.
Now I have modify it with
-- DROP FUNCTION "Fee"."ChallanNoGenerator"(uuid, uuid);
CREATE OR REPLACE FUNCTION "Fee"."ChallanNoGenerator"(
admissionformid uuid,
studentchallanid uuid)
RETURNS text
LANGUAGE 'plpgsql'
COST 100
VOLATILE
AS $BODY$
declare "ChallanNumber" TEXT;
"CampusCode" TEXT;
"SessionCode" TEXT;
"BusinessUnitCode" TEXT;
"ChallanNumberEx" TEXT;
BEGIN
SELECT pg_advisory_xact_lock(1982);
SELECT
CONCAT(bu."DigitCode", cm."DigitCode"), sn."FullName" INTO "CampusCode", "SessionCode"
FROM
"Admission"."AdmissionForm" AS af,
"Setup"."CampusProgramLink" AS cpl,
"Setup"."ProgramDetails" AS pd,
"Setup"."Campus" AS cm,
"Setup"."SubCity" AS sc,
"Setup"."City" AS ct,
"Setup"."Institution" AS ins,
"Setup"."Session" as sn,
"Setup"."BusinessUnit" AS bu
WHERE
af."CampusProgramId" = cpl."CampusProgramId"
AND pd."ProgramDetailId" = cpl."ProgramDetailId"
AND cpl."CampusId" = cm."CampusId"
AND cm."SubCityId" = sc."SubCityId"
AND sc."CityId" = ct."CityId"
AND cm."InstitutionId" = ins."InstitutionId"
AND cpl."SessionId" = sn."SessionId"
AND af."AdmissionFormId" = admissionformid;
-- SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;
SELECT ((RIGHT(COALESCE(MAX("ChallanNo"), '000000'), 6)::INT4) + 1) INTO "ChallanNumber" FROM "Fee"."StudentChallan"
WHERE "ChallanNo" LIKE "CampusCode" || "SessionCode" || '%';
SELECT ((RIGHT(COALESCE(MAX("ChallanNo"), '000000'), 6)::INT4) + 1) INTO "ChallanNumberEx" FROM "Fee"."StudentChallanEx"
WHERE "ChallanNo" LIKE "CampusCode" || "SessionCode" || '%';
IF (("ChallanNumber"::INT4) < ("ChallanNumberEx"::INT4)) THEN
"ChallanNumber" := "ChallanNumberEx";
END IF;
SELECT CONCAT("CampusCode", "SessionCode", LPAD("ChallanNumber", 6, '000000')) INTO "ChallanNumber";
UPDATE "Fee"."StudentChallan" SET "ChallanNo" = "ChallanNumber" WHERE "StudentChallanId" = studentchallanid;
RETURN CONCAT('Challan No: ', "ChallanNumber", ' has been generated successfully');
END
$BODY$;
ALTER FUNCTION "Fee"."ChallanNoGenerator"(uuid, uuid)
OWNER TO postgres;
Can any one with good experience to achieve what I want

How to use a Function Parameter in a Cursor that's incorporated with Dynamic SQL in Postgres Functions?

Created this Postgres Function which is working fine, but the actual requirement is to pass the input parameter in the function to the Cursor which uses the dynamic SQL as follows,
The below is the Function
CREATE OR REPLACE FUNCTION ssp2_pcat.find_shift_dates (date_to_find date)
RETURNS void
LANGUAGE 'plpgsql'
COST 100
VOLATILE
AS $BODY$
DECLARE
C1 CURSOR FOR
SELECT TABLE_NAME, 'SELECT COUNT(*) FROM ' || TABLE_NAME || ' WHERE ' ||
COLUMN_NAME || ' = '||
'CASE WHEN ' || COLUMN_NAME || ' LIKE ' || '''%START%'''||' THEN
date_to_find ELSE date_to_find-1 END;' SQL_TEXT
FROM (
SELECT TABLE_NAME, COLUMN_NAME
FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_NAME IN (SELECT TABLE_NAME FROM RESET_DATES WHERE RESET_IT =
'Y') AND
UPPER(DATA_TYPE) = 'DATE'
AND (COLUMN_NAME LIKE '%START%' OR COLUMN_NAME LIKE '%END%')
AND (COLUMN_NAME NOT LIKE '%TEST%'
AND COLUMN_NAME NOT LIKE '%PCAT%'
AND COLUMN_NAME NOT LIKE '%ORDER%'
AND COLUMN_NAME NOT LIKE '%SEASON%'
AND COLUMN_NAME NOT LIKE '%_AT')
ORDER BY 1, 2) A;
END_COUNT INTEGER := 0;
START_COUNT INTEGER := 0;
TABLENAME VARCHAR(32) := 'ALFU';
l_start TIMESTAMP;
l_end TIMESTAMP;
Time_Taken VARCHAR(20);
BEGIN
l_start := clock_timestamp();
DELETE FROM SHIFT_DATES_COUNT;
FOR I IN C1 LOOP
IF I.TABLE_NAME <> TABLENAME THEN
INSERT INTO SHIFT_DATES_COUNT VALUES (TABLENAME, START_COUNT,
END_COUNT, current_timestamp::timestamp(0));
TABLENAME := I.TABLE_NAME;
END_COUNT := 0;
START_COUNT := 0;
END IF;
IF STRPOS(I.SQL_TEXT, 'END') > 0 THEN
EXECUTE I.SQL_TEXT INTO END_COUNT;
RAISE NOTICE '% ', ('END: ' || I.SQL_TEXT);
ELSE
EXECUTE I.SQL_TEXT INTO START_COUNT;
RAISE NOTICE '% ', ('START: ' || I.SQL_TEXT);
END IF;
END LOOP;
INSERT INTO SHIFT_DATES_COUNT VALUES (TABLENAME, START_COUNT, END_COUNT,
current_timestamp::timestamp(0));
RAISE NOTICE '% ', ('INSERT INTO SHIFT_DATES_COUNT Done...');
l_end := clock_timestamp();
Time_Taken := (l_end-l_start);
RAISE NOTICE '% ', ('FIND_SHIFT_DATES Took: ' || Time_Taken );
END;
$BODY$;
Please let me know how can I use the date_to_find input parameter in the Dynamic SQL in the Cursor in the above Function.
You can use unbound cursor, clause fetch to get data from cursor, and exit when not found to finish, like:
CREATE OR REPLACE FUNCTION example (p_name text) RETURNS void LANGUAGE 'plpgsql' AS $$
DECLARE
C1 refcursor;
res record;
BEGIN
OPEN c1 FOR EXECUTE 'SELECT * FROM pg_database WHERE datname like ''%'||p_name||'%''';
LOOP
FETCH c1 INTO res;
EXIT WHEN not found;
raise notice 'value datname: %',res.datname;
END LOOP;
CLOSE c1;
RETURN;
END; $$;
--in my case
select example ('test')
NOTICE: value datname: test
NOTICE: value datname: test_msmov
NOTICE: value datname: test_resources
NOTICE: value datname: test_load_table
NOTICE: value datname: test_resources2
Total query runtime: 63 msec
1 row retrieved.
You can use EXECUTE clause for open cursor, see the documentation of PostgreSQL
https://www.postgresql.org/docs/10/plpgsql-cursors.html#PLPGSQL-CURSOR-OPENING
Example:
OPEN curs1 FOR EXECUTE format('SELECT * FROM %I WHERE col1 = $1',tabname) USING keyvalue;

Postgres colpivot function return nothing

I try to follow this tutorial http://www.anhuiyouxi.com/transposing-an-sql-result-so-that-one-column-goes-onto-multiple-columns/. I do it by run below code:
CREATE EXTENSION IF NOT EXISTS tablefunc;
create or replace function colpivot(
out_table varchar, in_query varchar,
key_cols varchar[], class_cols varchar[],
value_e varchar, col_order varchar
) returns void as $$
declare
in_table varchar;
col varchar;
ali varchar;
on_e varchar;
i integer;
rec record;
query varchar;
-- This is actually an array of arrays but postgres does not support an array of arrays type so we flatten it.
-- We could theoretically use the matrix feature but it's extremly cancerogenous and we would have to involve
-- custom aggrigates. For most intents and purposes postgres does not have a multi-dimensional array type.
clsc_cols text[] := array[]::text[];
n_clsc_cols integer;
n_class_cols integer;
begin
in_table := quote_ident('__' || out_table || '_in');
execute ('create temp table ' || in_table || ' on commit drop as ' || in_query);
-- get ordered unique columns (column combinations)
query := 'select array[';
i := 0;
foreach col in array class_cols loop
if i > 0 then
query := query || ', ';
end if;
query := query || 'quote_literal(' || quote_ident(col) || ')';
i := i + 1;
end loop;
query := query || '] x from ' || in_table;
for j in 1..2 loop
if j = 1 then
query := query || ' group by ';
else
query := query || ' order by ';
if col_order is not null then
query := query || col_order || ' ';
exit;
end if;
end if;
i := 0;
foreach col in array class_cols loop
if i > 0 then
query := query || ', ';
end if;
query := query || quote_ident(col);
i := i + 1;
end loop;
end loop;
-- raise notice '%', query;
for rec in
execute query
loop
clsc_cols := array_cat(clsc_cols, rec.x);
end loop;
n_class_cols := array_length(class_cols, 1);
n_clsc_cols := array_length(clsc_cols, 1) / n_class_cols;
-- build target query
query := 'select ';
i := 0;
foreach col in array key_cols loop
if i > 0 then
query := query || ', ';
end if;
query := query || '_key.' || quote_ident(col) || ' ';
i := i + 1;
end loop;
for j in 1..n_clsc_cols loop
query := query || ', ';
col := '';
for k in 1..n_class_cols loop
if k > 1 then
col := col || ', ';
end if;
col := col || clsc_cols[(j - 1) * n_class_cols + k];
end loop;
ali := '_clsc_' || j::text;
query := query || '(' || replace(value_e, '#', ali) || ')' || ' as ' || quote_ident(col) || ' ';
end loop;
query := query || ' from (select distinct ';
i := 0;
foreach col in array key_cols loop
if i > 0 then
query := query || ', ';
end if;
query := query || quote_ident(col) || ' ';
i := i + 1;
end loop;
query := query || ' from ' || in_table || ') _key ';
for j in 1..n_clsc_cols loop
ali := '_clsc_' || j::text;
on_e := '';
i := 0;
foreach col in array key_cols loop
if i > 0 then
on_e := on_e || ' and ';
end if;
on_e := on_e || ali || '.' || quote_ident(col) || ' = _key.' || quote_ident(col) || ' ';
i := i + 1;
end loop;
for k in 1..n_class_cols loop
on_e := on_e || ' and ';
on_e := on_e || ali || '.' || quote_ident(class_cols[k]) || ' = ' || clsc_cols[(j - 1) * n_class_cols + k];
end loop;
query := query || 'left join ' || in_table || ' as ' || ali || ' on ' || on_e || ' ';
end loop;
-- raise notice '%', query;
execute ('create temp table ' || quote_ident(out_table) || ' on commit drop as ' || query);
-- cleanup temporary in_table before we return
execute ('drop table ' || in_table)
return;
end;
$$ language plpgsql volatile;
begin;
DROP TABLE IF EXISTS qa;
create temp table qa (id int, usr int, question_id int, answer_id int);
insert into qa values
(1,1,1,1)
,(2,1,2,9)
,(3,1,3,15)
,(4,2,1,2)
,(5,2,2,12)
,(6,2,3,20);
--select * from qa;
select colpivot('_output', $$
select usr, ('q' || question_id::text) question_id, answer_id from qa
$$, array['usr'], array['question_id'], '#.answer_id', null);
select * from _output;
rollback;
After reach to the end line of the code, I got nothing.
Am I doing it wrong?
Please advise
I've changed the function a little bit, you can do a diff to see exactly what, but it seems like the function is dropping the result table at the end (i'm assuming it does a commit, so drops it). I've added a drop of the temp table incase it already exists. Also I removed the quote_ident() function calls around the table names as that was causing a problem when passing across a long table name value for out_table (on ver 9.6).
-- Copyright © 2015, Hannes Landeholm <hannes#jumpstarter.io>
-- This Source Code Form is subject to the terms of the Mozilla Public
-- License, v. 2.0. If a copy of the MPL was not distributed with this
-- file, You can obtain one at http://mozilla.org/MPL/2.0/.
-- See the README.md file distributed with this project for documentation.
create or replace function colpivot(
out_table varchar, in_query varchar,
key_cols varchar[], class_cols varchar[],
value_e varchar, col_order varchar
) returns void as $$
declare
in_table varchar;
col varchar;
ali varchar;
on_e varchar;
i integer;
rec record;
query varchar;
-- This is actually an array of arrays but postgres does not support an array of arrays type so we flatten it.
-- We could theoretically use the matrix feature but it's extremly cancerogenous and we would have to involve
-- custom aggrigates. For most intents and purposes postgres does not have a multi-dimensional array type.
clsc_cols text[] := array[]::text[];
n_clsc_cols integer;
n_class_cols integer;
begin
in_table := ('__' || out_table || '_in');
-- if the temp table already exists, drop
execute ( 'drop TABLE IF EXISTS ' || in_table );
execute ('create temp table ' || in_table || ' on commit drop as ' || in_query);
-- get ordered unique columns (column combinations)
query := 'select array[';
i := 0;
foreach col in array class_cols loop
if i > 0 then
query := query || ', ';
end if;
query := query || 'quote_literal(' || quote_ident(col) || ')';
i := i + 1;
end loop;
query := query || '] x from ' || in_table;
for j in 1..2 loop
if j = 1 then
query := query || ' group by ';
else
query := query || ' order by ';
if col_order is not null then
query := query || col_order || ' ';
exit;
end if;
end if;
i := 0;
foreach col in array class_cols loop
if i > 0 then
query := query || ', ';
end if;
query := query || quote_ident(col);
i := i + 1;
end loop;
end loop;
-- raise notice '%', query;
for rec in
execute query
loop
clsc_cols := array_cat(clsc_cols, rec.x);
end loop;
n_class_cols := array_length(class_cols, 1);
n_clsc_cols := array_length(clsc_cols, 1) / n_class_cols;
-- build target query
query := 'select ';
i := 0;
foreach col in array key_cols loop
if i > 0 then
query := query || ', ';
end if;
query := query || '_key.' || quote_ident(col) || ' ';
i := i + 1;
end loop;
for j in 1..n_clsc_cols loop
query := query || ', ';
col := '';
for k in 1..n_class_cols loop
if k > 1 then
col := col || ', ';
end if;
col := col || clsc_cols[(j - 1) * n_class_cols + k];
end loop;
ali := '_clsc_' || j::text;
query := query || '(' || replace(value_e, '#', ali) || ')' || ' as ' || quote_ident(col) || ' ';
end loop;
query := query || ' from (select distinct ';
i := 0;
foreach col in array key_cols loop
if i > 0 then
query := query || ', ';
end if;
query := query || quote_ident(col) || ' ';
i := i + 1;
end loop;
query := query || ' from ' || in_table || ') _key ';
for j in 1..n_clsc_cols loop
ali := '_clsc_' || j::text;
on_e := '';
i := 0;
foreach col in array key_cols loop
if i > 0 then
on_e := on_e || ' and ';
end if;
on_e := on_e || ali || '.' || quote_ident(col) || ' = _key.' || quote_ident(col) || ' ';
i := i + 1;
end loop;
for k in 1..n_class_cols loop
on_e := on_e || ' and ';
on_e := on_e || ali || '.' || quote_ident(class_cols[k]) || ' = ' || clsc_cols[(j - 1) * n_class_cols + k];
end loop;
query := query || 'left join ' || in_table || ' as ' || ali || ' on ' || on_e || ' ';
end loop;
-- raise notice '%', query;
execute ('create temp table ' || out_table || ' as ' || query);
-- cleanup temporary in_table before we return
execute ('drop table ' || in_table);
return;
end;
$$ language plpgsql volatile;
Now you can run it like so:
DROP TABLE IF EXISTS qa;
create temp table qa (id int, usr int, question_id int, answer_id int);
insert into qa values
(1,1,1,1)
,(2,1,2,9)
,(3,1,3,15)
,(4,2,1,2)
,(5,2,2,12)
,(6,2,3,20);
--select * from qa;
select colpivot('_output', $$
select usr, ('q' || question_id::text) question_id, answer_id from qa
$$, array['usr'], array['question_id'], '#.answer_id', null);
-- then run the select to get the result
select * from _output;

How to replace tablename with a variable in a DB2 cursor in an anonymous block

I want to replace the table name with a variable that is derived from another cursor, but no matter what logic I tried to use I just could not get it right, I am seeing a couple of examples for Oracle and SQL Server but I failed to interpret that code into the DB2 SQL. Please help.
Declare
v_user VarCHAR(100);
v_schema VARCHAR(1000);
V_Studio_svr VARCHAR(1000);
v_db2_schema VARCHAR(1000);
v_oracle_string varchar(5000) ;
v_db2_string varchar(5000) := '(' ;
v_sys_columns varchar(2000);
v_sys_values varchar(2000);
V_UID iNTEGER := 41;
begin
-- Main Table to Get Table Name From
FOR v In ( Select app_id,Upper(alias) ALIAS
From FREEDOM.FORMS where app_id = '5e988af8-ef0f-48c7-9794-9bc4f1134c80' ) Loop
v_schema := 'S__'||V.app_ID||'_1';
v_schema := replace(v_schema,'-','_');
v_studio_svr := 'PTU'||SUBSTR(v.alias,2,LENGTH(v.alias));
v_db2_schema := 'TF'||SUBSTR(v.alias,2,LENGTH(v.alias));
-- This is where I want to use Table Name as Variable Coming From Cursor V
For P in
(Select * from studio_svr||'.'||v_studio_svr) loop
-- Table to get Data Type Mappings
For i in
(Select * From fREEDOM.DB2_DT_MAPPING
Where Table_Name = v.alias ) Loop
IF I.DB2_DATATYPE LIKE 'DECIMAL%' THEN
v_ORACLE_STRING := Nvl(v_ORACLE_STRING,'')||'CAST('||'INTEGER('||I.STUDIO_SVR_COLUMN||') AS DECIMAL(22,6)),';
ELSE
v_ORACLE_STRING := Nvl(v_ORACLE_STRING,'')||I.STUDIO_SVR_COLUMN||',';
END IF;
v_DB2_STRING := v_DB2_STRING||I.DB2_COLUMN||',';
End Loop;
v_DB2_STRING := SUBSTR(v_DB2_STRING,1,LENGTH(v_DB2_STRING)-1)||')';
execute immediate 'Insert Into ' || v_schema || '.' || v_db2_schema || ' '|| v_db2_string ||' SELECT '|| v_oracle_string ||' FROM Studio_svr.' || v_studio_svr || 'where S__recordid ='||p.s__recordid ;
v_db2_string := '(';
v_oracle_string := '';
v_uid := v_uid + 1;
commit;
End loop;
END lOOP;
END
Obviously, you need to use dynamic SQL for that cursor, like so:
Declare
v_user VarCHAR(100);
...
V_UID iNTEGER := 41;
--->
v_cursor_studio SYS_REFCURSOR;
begin
-- Main Table to Get Table Name From
FOR v In ( Select app_id,Upper(alias) ALIAS
From FREEDOM.FORMS where app_id = '5e988af8-ef0f-48c7-9794-9bc4f1134c80' ) Loop
v_schema := 'S__'||V.app_ID||'_1';
v_schema := replace(v_schema,'-','_');
v_studio_svr := 'PTU'||SUBSTR(v.alias,2,LENGTH(v.alias));
v_db2_schema := 'TF'||SUBSTR(v.alias,2,LENGTH(v.alias));
-- This is where I want to use Table Name as Variable Coming From Cursor V
--->
OPEN v_cursor_studio for 'Select * from ' || studio_svr||'.'||v_studio_svr;
For P in v_cursor_studio
...
The code is not tested, but I hope you get the idea.

How can I return optional results from a dynamic query in a plpgsql function?

I'm playing around with plpgsql and have put together a function that assembles a dynamic query. I've tested it and it executes (i've included a test wrapper to output the assembled query).
Where I'm stumbling is capturing the output of the EXECUTE command once it's run, because I'd like to return some or all of the values, depending on the nature of the dynamic query. I've set up a type userprofile, and have the setProfileDynamic function return this type.
With the full complement of parameters, the output checks out (except for the second query, more on that in a bit). But when some of the parameters are missing (ie not all the user preferences are updated, only one eg: measuresystem) then the output is corrupted, so that measuresystem_id might appear as username in the output.
Secondly, is how to get the result of the second query (updateDefaultMealplan) into the userprofile type (where the columns mealplan_id and mealplan_name are waiting patiently). Currently this query returns into mp_id (and mp_name is filled from the _values array if 'defaultmealplan' key is present).
I'm quite new to this and I might be trying to do too much in one function, and I might be doing it completely the wrong way, so I don't mind whatever corrections might come to pass.
The userprofile type:
DROP TYPE IF EXISTS userprofile CASCADE;
CREATE TYPE userprofile AS (
username text,
measuresystem_id int,
blanksymbol_id int,
mealplan_id int,
mealplan_name text
);
The main function
DROP FUNCTION IF EXISTS setProfileDynamic (int, text, text[], text[]);
CREATE OR REPLACE FUNCTION setProfileDynamic (_userid int, _token text, _keys text[], _values text[])
RETURNS userprofile AS $$
DECLARE
_query text;
numkeys int;
i int;
_update text[];
_from text[];
_where text[];
_return text[];
_into text[];
test text[];
up userprofile;
mp_name text;
mp_id int;
u text;
f text;
w text;
r text;
c_update int := 1;
c_from int := 1;
c_where int := 3;
c_return int := 1;
runupdate boolean := false; --bc passing default mealplan through this fn too.
changedefaultmp boolean := false;
BEGIN
test[1] := 'users.id';
test[2] := 'users.token';
test[3] := _userid;
test[4] := _token;
numkeys := array_length(_keys, 1);
raise notice 'numkeys = %', numkeys;
_where[1] := test[1] || ' = ' || quote_literal(test[3]);
_where[2] := test[2] || ' = ' || quote_literal(test[4]);
--raise notice '_where[1] = %', _where[1];
--raise notice '_where[2] = %', _where[2];
for i in 1..numkeys loop
raise notice 'keys[%] = %', i, _keys[i];
CASE _keys[i]
WHEN 'email' THEN
runupdate := true;
_update[c_update] := quote_ident(_keys[i]) || ' = ' || quote_literal(_values[i]);
c_update := c_update + 1;
WHEN 'password' THEN
runupdate := true;
_update[c_update] := quote_ident(_keys[i]) || ' = ' || quote_literal(_values[i]);
c_update := c_update + 1;
WHEN 'username' THEN
runupdate := true;
_update[c_update] := quote_ident(_keys[i]) || ' = ' || quote_literal(_values[i]);
c_update := c_update + 1;
_return[c_return] := quote_ident(_keys[i]);
c_return := c_return + 1;
WHEN 'measuresystem' THEN
runupdate := true;
_update[c_update] := 'measuresystem_id = ms.id';
c_update := c_update + 1;
_from[c_from] := 'measuresystem as ms';
c_from := c_from + 1;
_where[c_where] := 'ms.name = ' || quote_literal(_values[i]);
c_where := c_where + 1;
_return[c_return] := 'ms.id';
c_return := c_return + 1;
WHEN 'blanksymbol' THEN
runupdate := true;
_update[c_update] := 'blanksymbol_id = bs.id';
c_update := c_update + 1;
_from[c_from] := 'blanksymbol as bs';
c_from := c_from + 1;
_where[c_where] := 'bs.name = ' || quote_literal(_values[i]);
c_where := c_where + 1;
_return[c_return] := 'bs.id';
c_return := c_return + 1;
ELSE
changedefaultmp := true;
mp_name := _values[i];
END CASE;
end loop;
u := 'UPDATE users SET ' || array_to_string(_update, ', ');
f := 'FROM ' || array_to_string(_from, ', '); --if a_t_s is null, the whole f is null and not included so no error
w := 'WHERE ' || array_to_string(_where, ' AND ');
r := 'RETURNING ' || array_to_string(_return, ', ');
raise notice 'u = %', u;
raise notice 'f = %', f;
raise notice 'w = %', w;
raise notice 'r = %', r;
_query = concat_ws(' ', u, f, w, r);
raise notice '_query = %', _query;
IF runupdate THEN
if r IS NULL THEN
EXECUTE _query;
ELSE
EXECUTE _query INTO up;
END IF;
END IF;
IF changedefaultmp THEN
SELECT into mp_id updateDefaultMealplan(_userid, mp_name);
END IF;
return up;
END
$$ LANGUAGE PLPGSQL;
This is the wrapper function where you can see the query generated for different inputs:
DROP FUNCTION IF EXISTS T ();
CREATE OR REPLACE FUNCTION T ()
RETURNS setof userprofile AS $$
declare
_keys text[];
_values text[];
_userid int := 1;
_token text := 'beet';
begin
_keys := ARRAY['email', 'password', 'username', 'measuresystem', 'blanksymbol', 'defaultmealplan'];
_values := ARRAY['s#p.com', 'secret', 'myname', 'metric', '?', 'new'];
--_keys := ARRAY['email', 'blanksymbol'];
--_values := ARRAY['k#d.com', '[]'];
return query
SELECT * from setProfileDynamic(_userid, _token, _keys, _values);
end
$$ LANGUAGE PLPGSQL;
I realize it's a lot of code to get through, I hope the T function helps to clarify things. 'email' and 'password' params are not returning. 'defaultmealplan' triggers the second query. Any of 'username', 'measuresystem', 'blanksymbol' or 'defaultmealplan' should return a value into the userprofile type. Thanks for any forthcoming feedback.
the basic issue is so your dynamic query doesn't returns all necessary columns, second issue - you probably expecting, but it is not valid expectation, so records are assigned with respecting field' names. But when you assign some values to some composite type, postgres dosn't check name - only order is important. So you have to use NULLs for filling gaps and return all field.
you can simplify your code with array concating
DECLARE _return_cols text[] = '{}';
BEGIN
_return_cols := _return_cols || quote_ident('some_column');
_return_cols := _return_cols || quote_ident('some_other_column');
...