Environment: Db2 for i, version 7.3
Library/table structure:
CORPORATE/TENANTS
LIB01/INVOICE
LIB02/INVOICE
LIB03/INVOICE
…
LIBxx/INVOICE
The CORPORATE/TENANTS table contains a list of libraries where information about each tenant is stored. It has this structure and data:
CREATE OR REPLACE TABLE TENANTS (
ID BIGINT GENERATED ALWAYS AS IDENTITY (START WITH 1),
TENANT CHAR(10) NOT NULL,
PRIMARY KEY(ID)
) RCDFMT TENANTSR;
RUNSQLSTM SRCFILE(HILLB/QDDLSRC) SRCMBR(TENANTS) DFTRDBCOL(CORPORATE)
+--+------+
|ID|TENANT|
+--+------+
| 1|LIB01 |
| 2|LIB02 |
|..|......|
|99|LIB99 |
+--+------+
The LIBxx/INVOICE tables are all identical to each other and have this structure:
CREATE OR REPLACE TABLE INVOICE (
ID BIGINT GENERATED ALWAYS AS IDENTITY (START WITH 1),
PAYDAT INTEGER(6,0) NOT NULL,
AMOUNT DECIMAL(15,2) NOT NULL DEFAULT 0,
PRIMARY KEY(ID)
) RCDFMT INVOICER;
+--+------+------+
|ID|PAYDAT|AMOUNT|
+--+------+------+
| 1|180701|100.00|
| 2|180801| 35.00|
|..|......|......|
+--+------+------+
I want to generate a list of invoice amounts for all tenants for a given date:
180701 LIB01 100.00
180701 LIB02 140.00
180701 LIB03 74.00
…
Conceptually what I want to do is this (yes, I know this is invalid SQL):
SELECT PAYDAT, TENANT, AMOUNT
FROM $X.INVOICE
WHERE PAYDAT = 180701;
I want to pull data from the INVOICE table for each TENANT but I know the FROM clause cannot be dynamic like this. I’m sure this kind of query has a name but I don’t know what it is so I’m unable to effectively use a search engine to find what I need.
This would be trivial to solve with an RPGLE program but I need a pure SQL solution.
Please note - the LIBxx values CANNOT be hardcoded in any way. These values can change at any time.
To do what you want, you can use a stored procedure with an EXECUTE IMMEDIATE in a loop to build a result set. Something like this:
Note: this is not a complete cut and paste solution, but you can modify it to do what you want.
CREATE OR REPLACE PROCEDURE GETINVOICEAMOUNTS ( )
DYNAMIC RESULT SETS 1
LANGUAGE SQL
NOT DETERMINISTIC
MODIFIES SQL DATA
CALLED ON NULL INPUT
SET OPTION COMMIT = *NONE
BEGIN
DECLARE STMT VARCHAR(1024);
DECLARE RECORD_FOUND INTEGER DEFAULT 1;
DECLARE LIBRARY CHAR(10);
DECLARE C1 CURSOR FOR
SELECT TENANT FROM CORPORATE/TENANT;
DECLARE C2 CURSOR WITH RETURN FOR
SELECT * FROM SESSION.TMP ;
DECLARE CONTINUE HANDLER FOR NOT FOUND
SET RECORD_FOUND = 0;
DECLARE GLOBAL TEMPORARY TABLE TMP
(PAYDAT INTEGER(6,0),
TENANT CHAR(10),
AMOUNT DECIMAL(15,2))
WITH REPLACE;
OPEN C1;
LOOP
FETCH C1 INTO LIBRARY;
IF RECORD_FOUND = 0;
LEAVE LOOP;
END IF;
SET STMT = 'INSERT INTO SESSION.TMP SELECT PAYDAT, LIBRARY, AMOUNT FROM ' || RTRIM(LIBRARY) || '.INVOICE WHERE PAYDAT = 180701';
EXECUTE IMMEDIATE STMT
END LOOP;
CLOSE C1;
OPEN C2;
END;
I gave you more than I planned to. But, one specific modification you will invariably need is to parameterize the date that you want to retrieve.
This is how it works:
A global temporary table named TMP is used to collect the records to be returned in a result set. Once all the records are collected, a cursor is opened over TMP and the procedure ends. This causes the values collected in TMP to be returned as a result set.
To collect the values the CORPORATE/TENANT file is read, and the column TENANT is retrieved into the variable LIBRARY. For each record a statement is built that concatenates LIBRARY into an INSERT statement. This statement is executed which loads the record into TMP. I am using EXECUTE IMMEDIATE because I cannot use a parameter marker to replace the table reference in the INSERT statement, so a prepared statement is just extra work.
You could use UNION ALL:
SELECT sub.PAYDAT, t.TENANT, sub.AMOUNT
FROM (SELECT * FROM LIB01.INVOICE
UNION ALL
SELECT * FROM LIB02/INVOICE
...
SELECT * FROM LIB0n/INVOICE) sub
JOIN TENANTS t
ON sub.id = t.id
WHERE SUB.PAYDAT = 180701;
It is an SELECT * FROM sales + #yymm template.
EDIT:
More secure way is to create a view:
CREATE VIEW combined_invoice
AS
SELECT * FROM LIB01.INVOICE
UNION ALL
SELECT * FROM LIB02/INVOICE
...
SELECT * FROM LIB0n/INVOICE;
And query:
SELECT sub.PAYDAT, t.TENANT, sub.AMOUNT
FROM combined_invoice sub
JOIN TENANTS t
ON sub.id = t.id
WHERE SUB.PAYDAT = 180701;
Of course view should be altered after adding/removing tables.
Related
I have table which has basically 2 rows containing the name of failure and the main table i want to write a query such that
Select main
from xyz
will return the table name like abc.
Now I want to get the data from the abc table
Select *
from
(select main
from xyz)
which returns abc.
How can I write it ?
You must use dynamic sql.
Note, that you can't use "SELECT to nowhere" in a compound statement in Db2. That is, the following code is erroneous.
BEGIN
SELECT * FROM MYTAB;
END#
This is why you need to store the result of SELECT somewhere. You may use Global Temporary Tables for that presuming, that USER TEMPORARY TABLESPASE is available to use for your user.
--#SET TERMINATOR #
BEGIN
DECLARE V_STMT VARCHAR (500);
SELECT
'DECLARE GLOBAL TEMPORARY TABLE SESSION.RESULT'
|| ' AS (SELECT * FROM '
|| MAIN
|| ') WITH DATA WITH REPLACE '
|| 'ON COMMIT PRESERVE ROWS NOT LOOGED'
INTO V_STMT
FROM XYZ
-- place your WHERE clause here if needed
FETCH FIRST 1 ROW ONLY
;
EXECUTE IMMEDIATE V_STMT;
END
#
SELECT * FROM SESSION.RESULT
#
dbfiddle link.
Here is a solution on stack that shows how to get the table names from your database
DB2 Query to retrieve all table names for a given schema
Then you could take your failure table and join into it based off of the table name, that should match your errors to the table that match on the table name. I'm not a 100% sure of your question but I think this is what you are asking.
The inner system query has schema and name. Type is T for table. See IBM link below for column reference. You could run the query wide open in the inner query to look for the tables you want. I would recommend using schema to isolate your search.
https://www.ibm.com/docs/en/db2-for-zos/11?topic=tables-systables
SELECT
ft.*
, st.*
FROM [FailureTable] as ft
INNER JOIN
(
select * from sysibm.systables
where CREATOR = 'SCHEMA'
and name like '%CUR%'
and type = 'T'
) st
ON st.[name] = ft.[tablename]
You can try
DECLARE #tableName VARCHAR(50);
SELECT #tableName = main
FROM xyx
EXEC('SELECT * FROM ' + 'dbo.' + #tableName)
Dont forget to add validation if #tableName doesnt get populated
I would like to select the information from a DB2 table, using that unique id that was generated using DB2 generate unique() function as key in a simple select query.
Using the field directly, gives an SQL code of -333, using HEX(unique id) in select gives SQL code of +100.
How can i get the information from the table?
This depends on the data type of your parameter.
If you know its hex representation, then:
DECLARE GLOBAL TEMPORARY TABLE SESSION.GEN_UNIQUE (U) AS (VALUES GENERATE_UNIQUE())
WITH DATA WITH REPLACE ON COMMIT PRESERVE ROWS NOT LOGGED;
SELECT HEX(U) AS HEX_VAL
FROM SESSION.GEN_UNIQUE
WHERE U = HEXTORAW ('20210531172108210028000000');
|HEX_VAL |
|--------------------------|
|20210531172108210028000000|
If you have it as CHAR(13) FOR BIT DATA, then it can be used as usual:
--#SET TERMINATOR #
BEGIN
DECLARE L_VAR CHAR(13) FOR BIT DATA;
DECLARE L_DUMMY INT;
DECLARE EXIT HANDLER FOR NOT FOUND
BEGIN
RESIGNAL SQLSTATE '70001' SET MESSAGE_TEXT = 'Oops!';
END;
SET L_VAR = HEXTORAW('20210531172108210028000000');
SELECT 1 INTO L_DUMMY
FROM SESSION.GEN_UNIQUE
WHERE U = L_VAR;
END
#
I'm creating an update statement that generate SHA256 for table columns based on table's name
1st Step: I created a procedure that get the table columns, concatenate it all in one columns, then format to a desired format.
-- Procedure code : Extract table's columns list, concatenate it and format it
Create procedure SHA_PREP (in inp1 nvarchar(20))
as
begin
SELECT concat(concat('hash_sha256(',STRING_AGG(A, ', ')),')') AS Names
FROM (
SELECT concat('to_varbinary(IFNULL("',concat(COLUMN_NAME,'",''0''))')) as A
FROM SYS.TABLE_COLUMNS
WHERE SCHEMA_NAME = 'SCHEMA_NAME' AND TABLE_NAME = :inp1
AND COLUMN_NAME not in ('SHA')
ORDER BY POSITION
);
end;
/* Result of this procedures :
hash_sha256(
to_varbinary("ID"),to_varbinary(IFNULL("COL1",'0')),to_varbinary(IFNULL("COL2",'0')) )
*/
-- Update Statement needed
UPDATE "SCHEMA_NAME"."TABLE_NAME"
SET "SHA" = CALL "SCHEMA_NAME"."SHA_PREP"('SCHEMA_NAME')
WHERE "ID" = 99 -- a random filter
The solution by #SonOfHarpy technically works but has several issues, namely:
unnecessary use of temporary tables
overly complicated string assignment approach
use of fixed system table schema (SYS.TABLE_COLUMNS) instead of PUBLIC synonym
wrong data type and variable name for the input parameter
An improved version of the code looks like this:
create procedure SHA_PREP (in TABLE_NAME nvarchar(256))
as
begin
declare SQL_STR nvarchar(5000);
SELECT
'UPDATE "SCHEMA_NAME"."TABLE_NAME" SET "SHA"= hash_sha256(' || STRING_AGG(A, ', ') || ')'
into SQL_STR
FROM (
SELECT
'TO_VARBINARY(IFNULL("'|| "COLUMN_NAME" ||'",''0''))' as A
FROM TABLE_COLUMNS
WHERE
"SCHEMA_NAME" = 'SCHEMA_NAME'
AND "TABLE_NAME" = :TABLE_NAME
AND "COLUMN_NAME" != 'SHA'
ORDER BY POSITION
);
-- select :sql_str from dummy; -- this is for debugging output only
EXECUTE IMMEDIATE (:SQL_STR);
end;
By changing the CONCAT functions to the shorter || (double-pipe) operator, the code becomes a lot easier to read as the formerly nested function calls are now simple chained concatenations.
By using SELECT ... INTO variable the whole nonsense with the temporary table can be avoided, again, making the code easier to understand and less prone to problems.
The input parameter name now correctly reflects its meaning and mirrors the HANA dictionary data type for TABLE_NAME (NVARCHAR(256)).
The procedure now consists of two commands (SELECT and EXECUTE IMMEDIATE) that each performs an essential task of the procedure:
Building a valid SQL update command string.
Executing the SQL command.
I removed the useless line-comments but left a debugging statement as a comment in the code, so that the SQL string can be reviewed without having to execute the command.
For that to work, obviously, the EXECUTE... line needs to be commented out and the debugging line has to be uncommented.
What's more worrying than the construction of the solution is its purpose.
It looks as if the SHA column should be used as a kind of shorthand row-data fingerprint. The UPDATE approach certainly handles this as an after-thought activity but leaves the "finger-printing" for the time when the update gets executed.
Also, it takes an essential part of the table design (that the SHA column should contain the fingerprint) away from the table definition.
An alternative to this could be a GENERATED COLUMN:
create table test (aaa int, bbb int);
alter table test add (sha varbinary (256) generated always as
hash_sha256(to_varbinary(IFNULL("AAA",'0'))
, to_varbinary(IFNULL("BBB",'0'))
)
);
insert into test (aaa, bbb) values (12, 32);
select * from test;
/*
AAA BBB SHA
12 32 B6602F58690CA41488E97CD28153671356747C951C55541B6C8D8B8493EB7143
*/
With this, the "generator" approach could be used for table definition/modification time, but all the actual data handling would be automatically done by HANA, whenever values get changed in the table.
Also, no separate calls to the procedure will ever be necessary as the fingerprints will always be current.
I find a solution that suits my need, but maybe there's other easier or more suitable approchaes :
I added the update statement to my procedure, and inserted all the generated query into a temporary table column, the excuted it using EXECUTE IMMEDIATE
Create procedure SHA_PREP (in inp1 nvarchar(20))
as
begin
/* ********************************************************** */
DECLARE SQL_STR VARCHAR(5000);
-- Create a temporary table to store a query in
create local temporary table #temp1 (QUERY varchar(5000));
-- Insert the desirable query into the QUERY column (Temp Table)
insert into #temp1(QUERY)
SELECT concat('UPDATE "SCHEMA_NAME"."TABLE_NAME" SET "SHA" =' ,concat(concat('hash_sha256(',STRING_AGG(A, ', ')),')'))
FROM (
SELECT concat('to_varbinary(IFNULL("',concat(COLUMN_NAME,'",''0''))')) as A
FROM SYS.TABLE_COLUMNS
WHERE SCHEMA_NAME = 'SCHEMA_NAME' AND TABLE_NAME = :inp1
AND COLUMN_NAME not in ('SHA')
ORDER BY POSITION
);
end;
/* QUERY : UPDATE "SCHEMA_NAME"."TABLE_NAME" SET "SHA" =
hash_sha256(to_varbinary("ID"),to_varbinary(IFNULL("COL1",'0')),to_varbinary(IFNULL("COL2",'0'))) */
SELECT QUERY into SQL_STR FROM "SCHEMA_NAME".#temp1;
--Excuting the query
EXECUTE IMMEDIATE (:SQL_STR);
-- Dropping the temporary table
DROP TABLE "SCHEMA_NAME".#temp1;
/* ********************************************************** */
end;
Any other solution or improvement are well welcomed
Thank you
Whereas I am not allowed to use either identity columns or HANA sequences, I am forced manually to generate unique autoincrementing keys for tables. Here is my unsafe and naive key generation procedure, which stores unique counters in table TABLEKEYS and increments them at every execution:
CREATE PROCEDURE NewKey
( IN SeqName NVARCHAR( 32),
OUT NewKey BIGINT
)
AS rec_exists INT;
row_num INT;
BEGIN
SELECT SUM(1) INTO rec_exists
FROM ( SELECT TOP 1 1 FROM TABLEKEYS WHERE "Name" = :SeqName ) T;
IF :rec_exists IS NULL THEN
SELECT COALESCE(SUM(1),0) INTO row_num FROM TABLEKEYS;
INSERT INTO TABLEKEYS("Code", "Name", "U_CurrentKey")
VALUES (row_num, :SeqName, -1 );
END IF;
UPDATE TABLEKEYS SET "U_CurrentKey" = "U_CurrentKey" + 1
WHERE "Name" = :SeqName;
SELECT "CurrentKey" INTO NewKey FROM TABLEKEYS
WHERE "Name" = :SeqName;
END;
How to make it reliable, so that it shall not return two identical keys under any circumstances, even when it is being called intensively from an hundred simultaneous connections? In MSSQL Server I should wrap its body in a transaction and apply locking hints to the table in the initial query, but I am not aware of their analogs in HANA. Is there a way in HANA to ensure that a table row is accessed strictly sequencially?
My procedure with corrections suggested by Lars and adapted for Business One user-defined tables:
CREATE PROCEDURE GTGetNewKeyInt
( IN TableName NVARCHAR( 32),
OUT NewKey BIGINT
)
AS cur_key INT;
row_num INT;
row_num_txt VARCHAR(8);
BEGIN
BEGIN
DECLARE EXIT HANDLER FOR SQLEXCEPTION
BEGIN
END;
SELECT "U_CurrentKey" INTO cur_key FROM "#GTTABLEKEYS"
WHERE "Name" = :TableName
FOR UPDATE;
END;
IF :cur_key IS NULL THEN
LOCK TABLE "#GTTABLEKEYS" IN EXCLUSIVE MODE;
SELECT COALESCE(SUM(1),0) INTO row_num FROM "#GTTABLEKEYS";
row_num_txt = LPAD( CAST( row_num AS varchar ), 8, '0' );
NewKey = 0;
INSERT INTO "#GTTABLEKEYS"("Code", "Name", "U_CurrentKey")
VALUES (row_num_txt, :TableName, :NewKey );
ELSE
NewKey = :cur_key + 1;
UPDATE "#GTTABLEKEYS" SET "U_CurrentKey" = :NewKey
WHERE "Name" = :TableName;
END IF;
END;
First off: not using the built-in features like sequences or the IDENTITY column seems rather not like a great idea.
Anything you build yourself here, will be inferior in one or the other regard.
But, hey, it's your code after all.
So, for selecting with locking, there is the standard SQL command
SELECT ... FOR UPDATE FROM...
(also see the documentation here)
Your program logic will be to
SELECT ... FOR UPDATE
do whatever you have to do
Update the sequence table
COMMIT or ROLLBACK
Your record will be locked as of step 1.
In order to make the whole process more efficient and to decouple the performance for managing the sequence from the amount of data in the actual data table, you may want to keep the sequence in its own table (row store might be a good idea for this one, as you deal with a single record and lots of updates). That's rather close to how sequences work as well.
I'm working on a e-learning project in which there is a table named chapter in which there is a column named question_table this is table in which the specific chapter's questions are added.
Now the problem is I want to display all the question from all the chapter for this I used following sql query
SELECT * FROM (SELECT `question_table` FROM `chapter`)
but it doesn't work and gives the error:
"Every derived table must have its own alias".
Note: I want to do it using SQL not PHP.
Firstly, I think you would be better redesigning your database. Multiple tables of the same structure holding the same data are generally not a good idea.
However what you require is possible using a MySQL procedure to build up some dynamic SQL and then execute it, returning the resulting data.
A procedure as follows could be used to do this:-
DROP PROCEDURE IF EXISTS dynamic;
delimiter //
CREATE PROCEDURE dynamic()
BEGIN
DECLARE question_table_value VARCHAR(25);
DECLARE b INT DEFAULT 0;
DECLARE c TEXT DEFAULT '';
DECLARE cur1 CURSOR FOR SELECT `question_table` FROM `chapter`;
DECLARE CONTINUE HANDLER FOR NOT FOUND SET b = 1;
OPEN cur1;
SET b = 0;
WHILE b = 0 DO
FETCH cur1 INTO question_table_value;
IF b = 0 THEN
IF c = '' THEN
SET c = CONCAT('SELECT * FROM `',question_table_value, '`');
ELSE
SET c = CONCAT(c, ' UNION SELECT * FROM `',question_table_value, '`');
END IF;
END IF;
END WHILE;
CLOSE cur1;
SET #stmt1 := c;
PREPARE stmt FROM #stmt1;
EXECUTE stmt;
END
This is creating a procedure called dynamic. This takes no parameters. It sets up a cursor to read the question_table column values from the chapter table. It looks around the results from that, building up a string which contains the SQL, which is a SELECT from each table with the results UNIONed together. This is then PREPAREd and executed. The procedure will return the result set from the SQL executed by default.
You can call this to return the results using:-
CALL dynamic()
Down side is that this isn't going to give nice results if there are no rows to return and they are not that easy to maintain or debug with the normal tools developers have. Added to which very few people have any real stored procedure skills to maintain it in future.
In MySQL you must give every subquery ("derived table") an alias:
SELECT * FROM (SELECT question_table FROM chapter) t --notice the alias "t"
The derived table here is the result of the (SELECT ...). You need to give it an alias, like so:
SELECT * FROM (SELECT question_table FROM chapter) X;
Edit, re dynamic tables
If you know all the tables in advance, you can union them, i.e.:
SELECT * FROM
(
SELECT Col1, Col2, ...
FROM Chapter1
UNION
SELECT Col1, Col2, ...
FROM Chapter2
UNION
...
) X;
SqlFiddle here
To do this solution generically, you'll need to use dynamic sql to achieve your goal.
In general however, this is indicative of a smell in your table design - your chapter data should really be in one table, and e.g. classified by the chapter id.
If you do need to shard data for scale or performance reasons, the typical mechanism for doing this is to span multiple databases, not tables in the same database. MySql can handle large numbers of rows per table, and performance won't be an issue if the table is indexed appropriately.