I need to create a table dynamically based on different values comes from distinct values of type column from tbl1. Please let me know if it is possible using cursor and function below.
Creating tbl1 with a columns id, type and value.
Creating tbl2 with a columns id ,gender.
Function retrieving values into final table using cursors. (creating temp table dual for checking what values are being passing.)
create table tbl1 (
id int not null,
type varchar not null,
value varchar
);
create table tbl2 (
id int not null,
gender varchar not null
);
commit;
insert into tbl1 values (1,'name','A'),(2,'name','B'),(1,'age','10'),(3,'name','C');
insert into tbl2 values (1,'M'),(2,'F');
commit;
--the below crosstab didn't work
SELECT id
, COALESCE(name, max(name) OVER w)
, COALESCE(age, max(age) OVER w)
FROM crosstab(
'SELECT id::text || row_number() OVER (PARTITION BY id, type ORDER BY value) * -1 AS ext_id
, id, type, value
FROM tbl1
ORDER BY ext_id, type, value'
,$$VALUES ('name'::text), ('age') $$
) AS ct (xid text, id int, name text, age int)
WINDOW w AS (PARTITION BY id);
-- FUNCTION: SELECT public.Finaltblfunc1()
-- DROP FUNCTION public.Finaltblfunc1();
CREATE OR REPLACE FUNCTION public.Finaltblfunc1()
RETURNS setof refcursor
LANGUAGE 'plpgsql'
COST 100
VOLATILE
AS $BODY$
DECLARE
/* Declare variables. */
P_id NUMERIC(10,0);
P_name VARCHAR(20);
P_age VARCHAR(3);
P_gender VARCHAR(1);
v_leng INTEGER;
v_leng1 INTEGER;
v_j bigint;
v_k VARCHAR(10);
/* Declare cursors. */
sourcerefcur1 CURSOR FOR SELECT t1.id,
(CASE WHEN t1.type = 'name' THEN t1.value ELSE '' END) AS name,
(CASE WHEN t1.type = 'age' THEN t1.value ELSE '' END) AS age,
t2.gender
FROM tbl1 t1 full outer join tbl2 t2 on t1.id = t2.id;
temprefcur1 CURSOR FOR SELECT distinct t1.type FROM tbl1 t1;
--targetrefcur2 REFCURSOR;
/* Declare SQL string variables. */
SQL_STR1 VARCHAR(200):= 'SELECT count(distinct table_name)
FROM information_schema.tables
WHERE table_schema = ''public'' and table_name = ''finaltable''';
/* Declare error handling variables. */
err_num TEXT;
err_msg TEXT;
BEGIN
/* tables exists or not */
EXECUTE SQL_STR1 INTO v_j;
RAISE INFO 'Finaltable check:%',v_j;
IF (v_j = 0) THEN
--Creating a Final Table
create table finaltable (
id NUMERIC(10,0),
name varchar(50),
age varchar(3),
gender varchar(1)
);
ELSE
--do nothing
END IF;
v_leng := 0;
--open the cursor temprefcur1
OPEN temprefcur1;
loop
--fetch next from temprefcur1 into respective parameters;
fetch next from temprefcur1 into v_k;
-- exit when no more row to fetch
EXIT WHEN NOT FOUND;
v_leng = v_leng +1;
raise notice 'v_k:%',v_k;
raise notice 'v_leng:%',v_leng;
end loop;
return next temprefcur1;
-- Close the cursor
CLOSE temprefcur1;
v_leng1 := 0;
--open the cursor sourcerefcur1
OPEN sourcerefcur1;
loop
--fetch next from sourcerefcur1 into respective parameters;
fetch next from sourcerefcur1 into P_id,P_name,P_age,P_gender;
-- exit when no more row to fetch
EXIT WHEN NOT FOUND;
v_leng1 = v_leng1 +1;
RAISE INFO 'P_id: %',P_id; --, E'\n';
RAISE INFO 'P_name: %',P_name; --, E'\n';
RAISE INFO 'P_age: %',P_age; --, E'\n';
RAISE INFO 'P_gender: %',P_gender; --, E'\n';
RAISE INFO 'length: %',v_leng1; --, E'\n';
raise notice 'step insert';
insert into finaltable values (P_id,P_name,P_age,P_gender);
insert into dual values (P_id),(P_name),(P_age),(P_gender);
insert into dual values (v_leng1);
raise notice 'after step insert';
end loop;
return next sourcerefcur1;
--close sourcerefcur1
close sourcerefcur1;
EXCEPTION
WHEN OTHERS THEN
err_num := SQLSTATE;
err_msg := SUBSTR(SQLERRM,1,100);
RAISE INFO 'Error: % %', err_num, err_msg;
END;
$BODY$;
ALTER FUNCTION public.Finaltblfunc1()
OWNER TO postgres;
You may have been (hugely) over-complicating things. This should basically do it all:
CREATE TABLE IF NOT EXISTS finaltable (
id bigint
, name text
, age int
, gender text
);
INSERT INTO finaltable(id, name, age, gender)
SELECT *
FROM crosstab(
$$SELECT id, type , value FROM tbl1
UNION ALL
SELECT id, 'gender', gender FROM tbl2
ORDER BY id$$
,$$VALUES ('name'), ('age'), ('gender')$$
) AS ct (id int, name text, age int, gender text);
Result:
id | name | age | gender
-: | :--- | ---: | :-----
1 | A | 10 | M
2 | B | null | F
3 | C | null | null
db<>fiddle here
Not sure what the added COALESCE was supposed to achieve. I stripped it.
Basics:
PostgreSQL Crosstab Query
Aside: age as table column is subject to bitrot. Store birthdays instead (or similar).
Related
I need to create an update using sql dynamic and all the updated rows have to be sent in a log table.
In microsoft, i can use OUTPUT clause and it inserts the updated rows in a table, but how can i do this in db2, using sql dynamic?
I have the following tables:
AllCustomers - contains all customers from a db
Id
Name
1
John
2
Test
gdpr_id. - contains all customers which should be updated
Id
Name
1
John
gdpr_log - should contain the output of the update stmt
Id
Name
1
John
I found the below syntax , but it just displays the results.
SELECT fields FROM FINAL TABLE
(update table set field = 'value' where id ='xyz')
I tried to create another dynamic stmt as
INSERT INTO
SELECT fields FROM FINAL TABLE
(update table set field = 'value' where id ='xyz')
and the syntax is not recognized.
How can i replace it to insert all the updated values in a log table?
I have to use sql dynamic because the tables which need to be updated are stored in a metadata table and with a cursor, i create the update script for each line from the metadata table.
UPDATE:
Metadata table looks like this:
table
column
AllCustom
Name
AllCustom
Lastname
CREATE OR REPLACE PROCEDURE sp_test ()
DYNAMIC RESULT SETS 1
P1: BEGIN
--*****************VARIABLES *****************
DECLARE EOF INT DEFAULT 0;
declare v_table nvarchar(50);
declare v_column nvarchar(50);
declare v_rowid nvarchar(50);
declare v_stmt nvarchar(8000);
declare s1 statement;
--*****************UPDATE STEP *****************
-- Declare cursor
DECLARE cursor1 CURSOR WITH HOLD WITH RETURN FOR
SELECT table,column FROM metadata_tbl;
declare c1 cursor for s1;
DECLARE CONTINUE HANDLER FOR NOT FOUND
SET EOF = 1;
OPEN cursor1;
WHILE EOF = 0 DO
FETCH FROM cursor1 INTO v_table,v_column;
SET v_stmt = 'WITH A AS
(
SELECT name
FROM FINAL TABLE
(
UPDATE ' || v_table || ' set ' || v_column || ' = ''some name'' where id in (select ID from gdpr_id )
)
)
SELECT COUNT (1) as tst
FROM FINAL TABLE
(
INSERT INTO GDPR_LOG (table,name, LOGDATE)
SELECT ''' || v_table || ''', name, current_timestamp from A
) B';
PREPARE s1 FROM v_stmt ;
open c1 using v_table,v_column;
close c1;
END WHILE;
CLOSE cursor1;
END P1
Update step works fine, insert step duplicates the rows inserted.
What should I do to have the insert step ok?
You have to use SELECT as an outermost statement and keep inner SELECTs in distinct CTEs, if you have a number of them.
Try this:
WITH A AS
(
SELECT ID, NAME
FROM FINAL TABLE
(
UPDATE GDPR
SET NAME = 'Some name'
WHERE ID = 1
)
)
SELECT COUNT (1)
FROM FINAL TABLE
(
INSERT INTO GDPR_LOG (ID, NAME)
SELECT * FROM A
) B
Update:
Using dynamic SQL.
You must enclose the whole statement with some statement termination character (say, #) different from the default one (;) if you use some tool to run this compound statement and specify this statement terminator correctly there.
BEGIN
DECLARE C1 CURSOR FOR S1;
PREPARE S1 FROM
'
WITH A AS
(
SELECT ID, NAME
FROM FINAL TABLE
(
UPDATE GDPR
SET NAME = ?
WHERE ID = ?
)
)
SELECT COUNT (1)
FROM FINAL TABLE
(
INSERT INTO GDPR_LOG (ID, NAME)
SELECT * FROM A
) B
';
OPEN C1 USING 'Name', 1;
CLOSE C1;
END
Working on a table with columns
some_id, some_parent_id, some_param.
I need to create a procedure or function that accept some_id as input and searches for its parent row.
When parent row is found, it will search again for parent row of that row... and so on...until parent row is null.
Function should returns all entries found during iteration.
So in this case if a function is called with value 3 it will return rows 2 and 1 and stop there cause some_parent_id is null
This is my attempt at storing results in temp table but all i get is NULL. What am I missing?
CREATE OR REPLACE FUNCTION my_function(
input_id int)
RETURNS TABLE(
r_id int,
r_parent_id int,
r_param int,
) AS $$
DECLARE
temp_table record;
search_id int;
begin
search_id := input_id;
for temp_table in (
select some_id, some_parent_id, some_param FROM mytable where some_id = search_id)
loop
search_id := temp_table.some_parent_id;
return next;
end loop;
return;
END;
$$ LANGUAGE plpgsql;
select * from my_function (3754);
I appreciate any help
One option uses a recursive query instead of a loop:
with recursive cte as (
select some_id, some_parent_id, some_param
from mytable
where some_id = input_id
union all
select t.some_id, t.some_parent_id, t.some_param
from mytable t
inner join cte c on t.some_id = c.some_parent_id
)
select * from cte
You can use this query in your function as follows:
create or replace function my_function(input_id int)
returns table(
r_id int,
r_parent_id int,
r_param text
) as $$
with recursive cte as (
select some_id, some_parent_id, some_param
from mytable
where some_id = input_id
union all
select t.some_id, t.some_parent_id, t.some_param
from mytable t
inner join cte c on t.some_id = c.some_parent_id
)
select * from cte
$$
language sql;
Demo on DB Fiddle with your sample data:
select * from my_function(2);
| r_id | r_parent_id | r_param |
| ---- | ----------- | ------- |
| 2 | 1 | value2 |
| 1 | | value1 |
I have two tables. They differ only columns order.
First
Table1
(
name,
surname,
age
)
Second
Table2
(
age
surname,
name
)
I want insert data to Table2 from Table1.
If tables column order is the same I can use
insert into Table2
select * from Table1
I know that I can solve this problem with
insert into table2
select age,surname,name from table1
But I don't use it because there are many column in my real table.
is there good idea for it?
The only way is with some dynamic SQL, by relying on column names; for example, say you have the tables
CREATE TABLE Table1
(
name VARCHAR2(100),
surname VARCHAR2(100),
age NUMBER
);
CREATE TABLE Table2
(
name VARCHAR2(100),
age NUMBER,
oneMoreColumn NUMBER,
surname VARCHAR2(100)
);
you can do:
declare
vSQL varchar2(1000);
vCols varchar2(1000);
begin
select listagg(tc1.column_name, ', ') within group (order by tc1.column_name)
into vCols
from user_tab_columns tc1
inner join user_tab_columns tc2
on(tc1.column_name = tc2.column_name)
where tc1.table_name = 'TABLE1'
and tc2.table_name = 'TABLE2';
--
vSQL := 'insert into table2( ' || vCols || ') select ' || vCols || ' from table1';
--
dbms_output.put_line(vSQL);
--
execute immediate vSQL;
end;
this will build and execute the statement:
insert into table2( AGE, NAME, SURNAME) select AGE, NAME, SURNAME from table1
you can do like this
create table EX_EMPLOYEE
(
NAME VARCHAR2(100),
PATH VARCHAR2(1000)
)
SET serveroutput ON size 2000
/
declare T_COL varchar2(50);
CURSOR c1 IS SELECT column_name name FROM user_tab_cols where table_name='EX_EMPLOYEE';
BEGIN
FOR rec IN c1 LOOP
if T_COL is null then
T_COL := T_COL || rec.name;
else
T_COL := T_COL ||' ,' || rec.name;
end if;
END LOOP;
dbms_output.put_line('select '|| T_COL ||' FROM EX_EMPLOYEE');
END;
/
select NAME ,PATH FROM EX_EMPLOYEE
PL/SQL procedure successfully completed
you could do something like this:
create table new_table as
select * from old_table
Morning,
I'm trying to write a script that will convert Unload tables (UNLD to HDL files) creating a flat file using PLSQL. I keep getting syntax errors trying to run it and would appreciate some help from an expert out there!
Here are the errors:
Error(53,21): PLS-00330: invalid use of type name or subtype name
Error(57,32): PLS-00222: no function with name 'UNLDTABLE' exists in this scope
Our guess is that the unldTable variable is being treated as a String, rather than a database table object (Not really expereinced in PLSQL)
CREATE OR REPLACE PROCEDURE UNLD_TO_HDL (processComponent IN VARCHAR2)
IS
fHandle UTL_FILE.FILE_TYPE;
concatData VARCHAR2(240);
concatHDLMetaTags VARCHAR2(240);
outputFileName VARCHAR2(240);
TYPE rowArrayType IS TABLE OF VARCHAR2(240);
rowArray rowArrayType;
emptyArray rowArrayType;
valExtractArray rowArrayType;
hdlFileName VARCHAR2(240);
unldTable VARCHAR2(240);
countUNLDRows Number;
dataType VARCHAR2(240);
current_table VARCHAR2(30);
value_to_char VARCHAR2(240);
BEGIN
SELECT HDL_FILE_NAME
INTO hdlFileName
FROM GNC_HDL_CREATION_PARAMS
WHERE PROCESS_COMPONENT = processComponent;
SELECT UNLD_TABLE
INTO unldTable
FROM GNC_HDL_CREATION_PARAMS
WHERE PROCESS_COMPONENT = processComponent
FETCH NEXT 1 ROWS ONLY;
SELECT LISTAGG(HDL_META_TAG,'|')
WITHIN GROUP(ORDER BY HDL_META_TAG)
INTO concatHDLMetaTags
FROM GNC_MIG_CONTROL
WHERE HDL_COMP = processComponent;
SELECT DB_FIELD
BULK COLLECT INTO valExtractArray
FROM GNC_MIG_CONTROL
WHERE HDL_COMP = processComponent
ORDER BY HDL_META_TAG;
fHandle := UTL_FILE.FOPEN('./', hdlFileName, 'W');
UTL_FILE.PUTF(fHandle, concatHDLMetaTags + '\n');
SELECT num_rows INTO countUNLDRows FROM user_tables where table_name = unldTable;
FOR row in 1..countUNLDRows LOOP
rowArray := emptyArrayType;
FOR value in 1..valExtractArray.COUNT LOOP
rowArray.extend();
SELECT data_type INTO dataType FROM all_tab_columns where table_name = unldTable AND column_name = valExtractArray(value);
IF dataType = 'VARCHAR2' THEN (SELECT valExtractArray(value) INTO value_to_char FROM current_table WHERE ROWNUM = row);
ELSIF dataType = 'DATE' THEN (SELECT TO_CHAR(valExtractArray(value),'YYYY/MM/DD') INTO value_to_char FROM current_table WHERE ROWNUM = row);
ELSIF dataType = 'NUMBER' THEN (SELECT TO_CHAR(valExtractArray(value)) INTO value_to_char FROM current_table WHERE ROWNUM = row);
ENDIF;
rowArray(value) := value_to_char;
END LOOP;
concatData := NULL;
FOR item in 1..rowArray.COUNT LOOP
IF item = rowArray.COUNT
THEN concatData := (COALESCE(concatData,'') || rowArray(item));
ELSE concatData := (COALESCE(concatData,'') || rowArray(item) || '|');
END IF;
END LOOP;
UTL_FILE.PUTF(fHandle, concatData + '/n');
END LOOP;
UTL_FILE.FCLOSE(fHandle);
END;
Thanks,
Adam
I believe it is just an overlook in your code. You define unldTable as a varchar, which is used correctly until you try to access it as if it were a varray on line 51
rowArray(value) := unldTable(row).valExtractArray(value);
Given that you have not defined it as a varray, unldTable(row) is making the interpreter believe that you are referring to a function.
EDIT
Now that you have moved on, you should resolve the problem of invoking SELECT statements on tables that are unknown at runtime. To do so you need to make use of Dynamic SQL; you can do it in several way, the most direct being an Execute immediate statement in your case:
mystatement := 'SELECT valExtractArray(value) INTO :value_to_char FROM ' || current_table || ' WHERE ROWNUM = ' || row;
execute immediate mystatement USING OUT value_to_char;
It looks like you need to generate a cursor as
select [list of columns from GNC_MIG_CONTROL.DB_FIELD]
from [table name from GNC_HDL_CREATION_PARAMS.UNLD_TABLE]
Assuming setup like this:
create table my_table (business_date date, id integer, dummy1 varchar2(1), dummy2 varchar2(20));
create table gnc_hdl_creation_params (unld_table varchar2(30), process_component varchar2(30));
create table gnc_mig_control (db_field varchar2(30), hdl_comp varchar2(30), hdl_meta_tag integer);
insert into my_table(business_date, id, dummy1, dummy2) values (date '2018-01-01', 123, 'X','Some more text');
insert into gnc_hdl_creation_params (unld_table, process_component) values ('MY_TABLE', 'XYZ');
insert into gnc_mig_control (db_field, hdl_comp, hdl_meta_tag) values ('BUSINESS_DATE', 'XYZ', '1');
insert into gnc_mig_control (db_field, hdl_comp, hdl_meta_tag) values ('ID', 'XYZ', '2');
insert into gnc_mig_control (db_field, hdl_comp, hdl_meta_tag) values ('DUMMY1', 'XYZ', '3');
insert into gnc_mig_control (db_field, hdl_comp, hdl_meta_tag) values ('DUMMY2', 'XYZ', '4');
You could build a query like this:
select unld_table, listagg(expr, q'[||'|'||]') within group (order by hdl_meta_tag) as expr_list
from ( select t.unld_table
, case tc.data_type
when 'DATE' then 'to_char('||c.db_field||',''YYYY-MM-DD'')'
else c.db_field
end as expr
, c.hdl_meta_tag
from gnc_hdl_creation_params t
join gnc_mig_control c
on c.hdl_comp = t.process_component
left join user_tab_columns tc
on tc.table_name = t.unld_table
and tc.column_name = c.db_field
where t.process_component = 'XYZ'
)
group by unld_table;
Output:
UNLD_TABLE EXPR_LIST
----------- --------------------------------------------------------------------------------
MY_TABLE to_char(BUSINESS_DATE,'YYYY-MM-DD')||'|'||ID||'|'||DUMMY1||'|'||DUMMY2
Now if you plug that logic into a PL/SQL procedure you could have something like this:
declare
processComponent constant gnc_hdl_creation_params.process_component%type := 'XYZ';
unloadSQL long;
unloadCur sys_refcursor;
text long;
begin
select 'select ' || listagg(expr, q'[||'|'||]') within group (order by hdl_meta_tag) || ' as text from ' || unld_table
into unloadSQL
from ( select t.unld_table
, case tc.data_type
when 'DATE' then 'to_char('||c.db_field||',''YYYY/MM/DD'')'
else c.db_field
end as expr
, c.hdl_meta_tag
from gnc_hdl_creation_params t
join gnc_mig_control c
on c.hdl_comp = t.process_component
left join user_tab_columns tc
on tc.table_name = t.unld_table
and tc.column_name = c.db_field
where t.process_component = processComponent
)
group by unld_table;
open unloadCur for unloadSQL;
loop
fetch unloadCur into text;
dbms_output.put_line(text);
exit when unloadCur%notfound;
end loop;
close unloadCur;
end;
Output:
2018/01/01|123|X|Some more text
2018/01/01|123|X|Some more text
Now you just have to make that into a procedure, change dbms_output to utl_file and add your meta tags etc and you're there.
I've assumed there is only one distinct unld_table per process component. If there are more you'll need a loop to work through each one.
For a slightly more generic approach, you could build a cursor-to-csv generator which could encapsulate the datatype handling, and then you'd only need to build the SQL as select [columns] from [table]. You might then write a generic cursor to file processor, where you pass in the filename and a cursor and it does the lot.
Edit: I've updated my cursor-to-csv generator to provide file output, so you just need to pass it a cursor and the file details.
For example a I have two schemas: SCHEMA_1 and SCHEMA_2. In SHEMA_1 I have a table named TABLE. This table includes two fields: FIELD_1, FIELD_2. In TABLE in FIELD_1 I have some letters: A, B, C. FIELD_2 has tables' names of SCHEMA 2: TABLE_10, TABLE_20, TABLE_30.
SCHEMA_2 includes three tables: TABLE_10, TABLE_20, TABLE_30 with some numbers.
enter image description here
I have to write query to get maximum number of each table in SCHEMA_2. How can I get this result>
enter image description here
Try this:
create table schema1.tableA
(field_1 char(1),
field_2 varchar(10));
insert into schema1.tableA
values ('A','table_10'),
('B','table_20'),
('C','table_30');
create table schema2.table_10
(field_1 dec(5,0));
insert into schema2.table_10
values (20), (30), (40);
create table schema2.table_20
(field_1 dec(5,0));
insert into schema2.table_20
values (6), (9), (12);
create table schema2.table_30
(field_1 dec(5,0));
insert into schema2.table_30
values (10), (15), (20);
with tmp (table_name, field_1) as (
select 'table_10', max(field_1) from schema2.table_10
union all
select 'table_20', max(field_1) from schema2.table_20
union all
select 'table_30', max(field_1) from schema2.table_30)
select a.field_1, b.field_1
from schema1.tableA a
join tmp b on b.table_name = a.field_2;
If you have too many tables to make the above work, you can use a user defined function like this:
create or replace function MaxNbr
(p_TableName varchar(128),
p_TableSchema varchar(128))
Returns dec(5,0)
language sql
not deterministic
no external action
reads sql data
returns null on null input
not fenced
begin
declare l_stmt varchar(1024);
declare l_table varchar(128);
declare l_schema varchar(128);
declare l_result dec(5,0);
set l_table = replace(upper(p_TableName),'"','');
set l_schema = replace(upper(p_TableSchema),'"','');
set l_stmt = 'values (select max(field_1) from "' || l_schema || '"."' ||
trim(l_table) || '") into ?';
prepare S1 from l_stmt;
allocate sql descriptor 'D1';
describe S1 using sql descriptor 'D1';
execute S1 into sql descriptor 'D1';
get sql descriptor 'D1' value 1 l_result = data;
deallocate sql descriptor 'D1';
return l_result;
end;
This is necessary because you cannot use a variable for an identifier like a table name or schema name.
To use the user defined function, you can do something like this:
set schema schema1;
set path = udf_schema;
select field_1, MaxNbr(field_2, 'schema2') as field_2
from tableA;