How can I get this result in IBM DB2? - sql

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;

Related

How to insert updated rows in a new table on DB2 - IBM

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

How do I convert a SQL Server query to Oracle SQLPLus. I am new to Oracle and having problem understanding replacement of if exists and go statement

IF EXISTS (SELECT abc FROM OBJECTS WHERE NAME = 'BIG' AND type = 'U')
DROP TABLE FIRSTDATA
go
CREATE TABLE FIRSTDATA
(
ROLLNO varchar(50) NOT NULL,
STNAME varchar(3) NULL,
)
go
INSERT INTO FIRSTDATA (ROLLNO, STNAME)
VALUES ('13', 'Becky)
You can use USER_TABLES to check if table already exists or not as follows:
Declare
CNT NUMBER:= 0;
BEGIN
SELECT COUNT(1)
INTO CNT
FROM USER_TABLES
WHERE TABLE_NAME = 'FIRSTDATA';
IF CNT = 0 THEN
EXECUTE IMMEDIATE 'CREATE TABLE FIRSTDATA
( ROLLNO varchar(50) NOT NULL, STNAME varchar(3) NULL )' ;
END IF;
END;
/
Note the following about your example:
Assuming based on the name that "ROLLNO" is intended to be a number, why are you using a varchar2 data type?
You define STNAME as varchar2(3), then insert a value with 5 characters. This will not work.
Oracle does not have a native "if exists" clause for DDL commands,
nor can you execute DDL commands directly within a PL/SQL block -
you must use an "EXECUTE IMMEDIATE" statement.
A better example would then be:
declare
cnt number;
begin
select count(*) into cnt from user_tables where table_name = 'FIRSTDATA';
if cnt > 0 then
execute immediate 'drop table firstdata';
end if;
execute immediate 'create table firstdata (rollno number not null, stname varchar2(30) )';
execute immediate 'insert into firstdata (rollno, stname) values (:rollno,:stname)' USING 50, 'Becky';
commit;
end;
/

Same value being inserted into table with cursor

I'm trying to dynamically count the number of null values held in a table. So far I have this:
BEGIN
DECLARE STMT VARCHAR(2000);
FOR v AS CRS CURSOR FOR
SELECT NAME
FROM SESSION.TT1
DO
SET STMT = 'UPDATE SESSION.TT1 TT1
SET NULL_COUNT = (
SELECT COUNT(*) - COUNT('''||v.NAME||''') NULL_COUNT
FROM Table1
)
WHERE TT1.COLUMN_NAME = '''||v.NAME||'''';
EXECUTE IMMEDIATE STMT;
END FOR;
END
This runs fine, and does populate the temp table with data, but it updates every row with the same value (which is obviously incorrect).
Where have I gone wrong?
Note:
The temporary table, TT1, was generated by another procedure, it contains two columns; NAME, and NULL_COUNT. All values in the NULL_COUNT column are undefined at this point, and the NAME column contains column names retrieved from syscolumns.
I also tried removing the row and then inserting a new row rather than updating on the matched column name but this provides the same results.
You need double quotes (or no quotes) around the column name, otherwise you are are simply counting a literal value, rather than the column...
DROP TABLE TABLE1#
CREATE TABLE TABLE1(C INT)#
INSERT INTO TABLE1 VALUES(NULL),( NULL)#
DECLARE GLOBAL TEMPORARY TABLE TT1(NAME VARCHAR(128), NULL_COUNT BIGINT)#
INSERT INTO SESSION.TT1 VALUES ('C',null)#
BEGIN
DECLARE STMT VARCHAR(2000);
FOR v AS CRS CURSOR FOR
SELECT NAME
FROM SESSION.TT1
DO
SET STMT = 'UPDATE SESSION.TT1 TT1
SET NULL_COUNT = (
SELECT COUNT(*) - COUNT("'||v.NAME||'") NULL_COUNT
FROM TABLE1
)
WHERE TT1.NAME = '''||v.NAME||'''';
EXECUTE IMMEDIATE STMT;
END FOR;
END
#
SELECT * FROM SESSION.TT1
#
NAME NULL_COUNT
---- ----------
C 2

Errors in PLSQL -

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.

Using placeholders in Informix SQL queries

How I can use the placeholders within dynamic SQL string in case when I need to assign one of input parameters twice? Following code doesn't work.
CREATE FUNCTION somefunc( p_name VARCHAR( 32 ), p_id INT, p_weight INT ) RETURNING INT;
LET sp_id = 0;
LET somearea=12;
LET c_query ='SELECT FIRST 1 pr_id FROM sometable n WHERE n.pr_id> ? and n.pr_weight = ? ( UPPER( n.sname ) LIKE %UPPER(?))'||
' OR( UPPER(?) LIKE %UPPER( n.name)) ORDER BY 1, 2;';
PREPARE c_stmt
FROM c_query;
DECLARE c_cur CURSOR FOR c_stmt;
OPEN c_cur USING p_id, p_weight, p_name, p_name ;
FETCH c_cur INTO sp_id;
CLOSE c_cur;
FREE c_cur;
FREE c_stmt;
RETURN sp_id;
END FUNCTION;
Umm... not quite sure to fully understand the question (or the purpose of that SQL, hope it's us a test as it does in deed look weird)
Anyway, this is your SPL with the correct syntax:
D:\infx\ids12>cat 1.sql
DROP TABLE sometable;
CREATE TABLE sometable (name varchar(32), sname varchar(32), pr_id int, pr_weight int);
INSERT INTO sometable VALUES ('test','test',1,100);
INSERT INTO sometable VALUES ('tESt','tESt',2,200);
INSERT INTO sometable VALUES ('another','another',3,300);
DROP FUNCTION somefunc;
CREATE FUNCTION somefunc( p_name VARCHAR( 32 ), p_id INT, p_weight INT ) RETURNING INT;
DEFINE sp_id int;
DEFINE c_query varchar(200);
LET sp_id = 0;
LET c_query ="SELECT FIRST 1 pr_id FROM sometable n WHERE n.pr_id> ? and n.pr_weight = ? and ( UPPER( n.sname ) LIKE UPPER('%'||?||'%')) OR( UPPER('%'||?||'%') LIKE UPPER( n.name)) ORDER BY 1";
PREPARE c_stmt
FROM c_query;
DECLARE c_cur CURSOR FOR c_stmt;
OPEN c_cur USING p_id, p_weight, p_name, p_name ;
FETCH c_cur INTO sp_id;
CLOSE c_cur;
FREE c_cur;
FREE c_stmt;
RETURN sp_id;
END FUNCTION;
--SELECT FIRST 1 * FROM sometable n WHERE n.pr_id> 1 and n.pr_weight = 200 and ( UPPER( n.sname ) LIKE UPPER('%'||'TEST'||'%')) OR ( UPPER('%'||'TEST'||'%') LIKE UPPER( n.name)) ORDER BY 1;
EXECUTE FUNCTION somefunc('TEST',1,200);
D:\infx\ids12>dbaccess stores7 1.sql
Database selected.
Table dropped.
Table created.
1 row(s) inserted.
1 row(s) inserted.
1 row(s) inserted.
Routine dropped.
Routine created.
(expression)
2
1 row(s) retrieved.
Database closed.
D:\infx\ids12>
I think that what Jonathan was referring to when talking about dynamic SQL is that you don't need to build or prepare a SELECT statement using place holders (?) inside a SPL. You can execute mostly 'any' SQL statement.
The SPL above could be rewrite like:
D:\infx\ids12>cat 2.sql
DROP FUNCTION somefunc_simple;
CREATE FUNCTION somefunc_simple( p_name VARCHAR( 32 ), p_id INT, p_weight INT )
RETURNING INT;
DEFINE sp_id int;
LET sp_id=(SELECT FIRST 1 pr_id FROM sometable n WHERE n.pr_id> p_id and n.pr_weight = p_weight and ( UPPER( n.sname ) LIKE UPPER('%'||p_name||'%')) OR ( UPPER('%'||p_name||'%') LIKE UPPER( n.name)));
RETURN sp_id;
END FUNCTION;
EXECUTE FUNCTION somefunc_simple('TEST',1,200);
D:\infx\ids12>dbaccess stores7 2.sql
Database selected.
Routine dropped.
Routine created.
(expression)
2
1 row(s) retrieved.
Database closed.
D:\infx\ids12>
Have a read at the Informix SQL guide at
https://www.ibm.com/support/knowledgecenter/en/SSGU8G_12.1.0/com.ibm.sqls.doc/sqls.htm
It should give you an idea of how to write Informix SQL and SPL and what functionality is supported.
Also, Dynamic SQL is explained here:
https://www.ibm.com/support/knowledgecenter/en/SSGU8G_12.1.0/com.ibm.esqlc.doc/ids_esqlc_0528.htm