i want to use the result of a query as an input in another query.
What might make it difficult: The variable is the schema in the database.
CREATE or replace VARIABLE myschema varchar(15) ;
set myschema = (select owner from syscat.tables where tabname = 'xyz');
select count(name) as result from myschema.USR02 where USTYP = 'A';
DROP VARIABLE myschema;
This is my last try, after i failed using declare.
But i get an error, because "myschema" is used as a string, and of course there is no schema with name "myschema". The result of the first query is not used.
If I just run the first two lines, i get the schemaname as result. Do i have to mark the variable in a special way? The goal is just the result of the query in line 3 by using the dynamic value of "myschema".
Unfortunately, you have to use dynamic SQL (forming a custom SQL query through string manipulation) if you want to deal with table, schema, or column names dynamically:
This is the basic idea:
execute immediate 'select * from ' || myschema || '.USR02';
However, you can't just run a bare select in dynamic SQL; you have to put the result in something. And the whole thing must be in a compound SQL block. So the full example would look something like this (simplified query for space).
This query assumes that a table called "result" exists to store the result you are returning.
begin
declare myschema varchar(100) default '';
set myschema = (select owner from syscat.tables where tabname = 'xyz');
execute immediate 'insert into result select count(*) from ' || myschema || '.USR02';
end
select * from result;
Note that within the block, you can simply declare a variable (as shown in my example). So you don't have to declare a global variable for this purpose, unless you want it to persist beyond this one statement.
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 am wondering if/how i can run a CTAS type statement via a scheduled query but wrangle the #run_date or #run_time param to be the yyyymmdd suffix of the table i want to create or replace.
So the scheduled query would look like:
CREATE OR REPLACE TABLE foo_{#run_date|"%Y%m%d"} AS
select 'hello'
Such that if i was to run it on date of '2021-07-01' i would create the table called foo_20210701
I'm just not quite sure for to wrangle the default #run_date param to include it in my table name.
As mentioned in a answer to a similar question and in the documentation:
Parameters cannot be used as substitutes for identifiers, column names, table names, or other parts of the query.
There is however a workaround. You could use EXECUTE IMMEDIATE to run a SQL script defined as a string. Eg.
EXECUTE IMMEDIATE "SELECT CURRENT_DATE()"
Although it is very hacky it could be used like this achieve what you need.
EXECUTE IMMEDIATE CONCAT('CREATE TABLE `some_project.some_dataset.foo_', CURRENT_DATE(), '` AS SELECT "hello" as column_name' );
And below an approach using a DECLARE statement to keep the table name as a variable
DECLARE table_name STRING DEFAULT CAST(CURRENT_DATE() AS STRING);
EXECUTE IMMEDIATE
CONCAT('CREATE TABLE `some_project.some_dataset.foo_', table_name, '` AS SELECT "hello" as column_name' );
I'm writing a procedure to count rows in every table in my database. It so far looks like this:
create or replace procedure count_database_rows()
dynamic result sets 1
P1: begin atomic
DECLARE stmt CHAR(40);--
FOR v1 AS
c1 CURSOR FOR
SELECT TABLE_SCHEMA, TABLE_NAME FROM sysibm.tables
DO
SET stmt = 'SELECT COUNT(*) FROM '||TABLE_SCHEMA||'.'||TABLE_NAME;--
PREPARE s FROM stmt;--
EXECUTE s;--
END FOR;--
end P1
~
however, when I run it:
db2 -ntd~ -f script.sql > dump.csv
all I'm getting is:
DB20000I The SQL command completed successfully.
how can I print all results instead?
Just for demonstration. I assume, that it's some educational task, and it's Db2 for LUW.
For non-DPF Db2 for LUW systems only
--#SET TERMINATOR #
CREATE OR REPLACE FUNCTION COUNT_DATABASE_ROWS()
RETURNS TABLE (P_TABSCHEMA VARCHAR(128), P_TABNAME VARCHAR(128), P_ROWS BIGINT)
BEGIN
DECLARE L_STMT VARCHAR(256);
DECLARE L_ROWS BIGINT;
FOR V1 AS
SELECT TABSCHEMA, TABNAME
FROM SYSCAT.TABLES
WHERE TYPE IN ('T', 'S')
FETCH FIRST 10 ROWS ONLY
DO
SET L_STMT = 'SET ? = (SELECT COUNT(*) FROM "'||V1.TABSCHEMA||'"."'||V1.TABNAME||'")';
PREPARE S FROM L_STMT;
EXECUTE S INTO L_ROWS;
PIPE(V1.TABSCHEMA, V1.TABNAME, L_ROWS);
END FOR;
RETURN;
END#
SELECT * FROM TABLE(COUNT_DATABASE_ROWS())#
For any Db2 for LUW systems
A little bit tricky for DPF systems, but doable as well. We have to wrap the code which is not allowed in the inlined compound statement into the stored procedure.
--#SET TERMINATOR #
CREATE OR REPLACE PROCEDURE COUNT_DATABASE_ROWS_DPF(OUT P_DOC XML)
READS SQL DATA
BEGIN
DECLARE L_STMT VARCHAR(256);
DECLARE L_ROWS BIGINT;
DECLARE L_NODE XML;
SET P_DOC = XMLELEMENT(NAME "DOC");
FOR V1 AS
SELECT TABSCHEMA, TABNAME
FROM SYSCAT.TABLES
WHERE TYPE IN ('T', 'S')
FETCH FIRST 10 ROWS ONLY
DO
SET L_STMT = 'SET ? = (SELECT COUNT(*) FROM "'||V1.TABSCHEMA||'"."'||V1.TABNAME||'")';
PREPARE S FROM L_STMT;
EXECUTE S INTO L_ROWS;
SET L_NODE = XMLELEMENT
(
NAME "NODE"
, XMLELEMENT(NAME "TABSCHEMA", V1.TABSCHEMA)
, XMLELEMENT(NAME "TABNAME", V1.TABNAME)
, XMLELEMENT(NAME "ROWS", L_ROWS)
);
SET P_DOC = XMLQUERY
(
'transform copy $mydoc := $doc modify do insert $node as last into $mydoc return $mydoc'
passing P_DOC as "doc", L_NODE as "node"
);
END FOR;
END#
CREATE OR REPLACE FUNCTION COUNT_DATABASE_ROWS_DPF()
RETURNS TABLE (P_TABSCHEMA VARCHAR(128), P_TABNAME VARCHAR(128), P_ROWS BIGINT)
BEGIN ATOMIC
DECLARE L_DOC XML;
CALL COUNT_DATABASE_ROWS_DPF(L_DOC);
RETURN
SELECT *
FROM XMLTABLE ('$D/NODE' PASSING L_DOC AS "D" COLUMNS
TYPESCHEMA VARCHAR(128) PATH 'TABSCHEMA'
, TABNAME VARCHAR(128) PATH 'TABNAME'
, LENGTH BIGINT PATH 'ROWS'
);
END#
-- Usage. Either CALL or SELECT:
CALL COUNT_DATABASE_ROWS_DPF(?)#
SELECT * FROM TABLE(COUNT_DATABASE_ROWS_DPF())#
If your Db2-server runs on Linux/Unix/Windows then you can use the DBMS_OUT.PUT_LINE function to send diagnostic output from SQL routines to the console. The idea is that in your routine, you assign to a variable some text (example, the table name and its count), then call DBMS_OUTPUT.PUT_LINE(...) to cause that text to appear on the console. The disadvantage of this approach is that the output will only appear once the routine has completed. This is often not what you want, sometimes you want to see the row-counts as they become available, so consider instead alternative approaches, as shown below.
To see DBMS_OUTPUT.PUT_LINE output with the Db2 CLP (or db2cmd.exe) you first need to use set serveroutput on before calling the procedure.
But for simple stuff like this, a stored procedure might be unsuitable, because you can use the CLP to do the work in two steps after connecting to the database. This is often more convenient for scripting purposes. The idea is that you make a file to generate the queries, which when you run with CLP creates a second file, and you execute the second file to get the desired results.
Example
Create file gen_counts.sql containing the query that generates the real queries, for example gen_counts.sql might contain
select 'select count(*) from '||rtrim(tabschema)||'.'||rtrim(tabname)||' with ur;'
from syscat.tables;
Then you can do these steps:
db2 connect to $database
db2 -txf gen_counts.sql > count_queries.sql
db2 -tvf count_queries.sql > count_results.txt
Note that the output file (in this case count_results.txt) is readable via another shell session while the script continues to run. You can also pipe the output to concurrent jobs if required.
However, experienced DBAs might avoid row-counting all tables in this manner, and might choose instead to ensure that the runstats are always up-to-date for all tables, and accept recent estimates of row counts, which are visible in SYSCAT.TABLES.CARD once runstats are completed. If the stats are up to date, the CARD count is often good enough for many purposes. If exact counts are required, they are often valid only for a specific timestamp if the database is live.
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
I have a table, which has columns, say
Week1,Week2, Week3 and so on.
I have a stored procedure, and based on the number input, i want to select that column.
Example, if input is 4 then I want to make the query,
select *
from table_name
where Week4=<something>
Is there any way to do this other than using dynamic query? Because this dynamic thing will be just a small part of a huge query.
The comments about normalization are right, but if you have no choice, you can use "or" clauses:
declare #inputvalue int;
set #int = 1;
select *
from <table>
where (week1 = <something> and #inputvalue = 1)
or (week2 = <something> and #inputvalue = 2)
or (week3 = <something> and #inputvalue = 3)
or (week4 = <something> and #inputvalue = 4)
This will be very slow if the tables are of any size, as you won't be using any indexes. I wouldn't suggest doing this unless you're absolutely unable to change the table structure.
I realize this isn't what you asked for, but I figured I'd point out to some people who find this what you mean by doing this as a dynamic query.
You'd just write a procedure and hold the field name in there. Assuming that the naming standard is the same, so the input value would be the week# (1,2,7,27, 123, etc.) and the field name would directly correspond (Week1, Week2, Week7, Week27, Week123, etc.)
create or replace procedure myweek(week_in varchar2)
is
dyn_sql varchar2(1000);
begin
dyn_sql := 'select * from table_name where week'||week_in||' = ''something;'' '
execute immediate dyn_sql;
end;
/
Then to call it you'd just do something like :
exec myweek(27); and it would generate the sql:
select * from table_name where week27 = 'something';