Loop through the list of tables and check for a value in a field (DB2) - db2

In DB2, I can get a list of tables with the following sql statement:
select tabname from syscat.tables where `tabschema = 'DBO'
Assuming that each table has a field named a1, how can I
loop through the tables and check for a value in that field
in every table?

There are two general ways. One would be to write a program that processes each file to check that column. The program could use embedded SQL to retrieve the count of the chosen value from each table. Or you could create a stored proc that accepts a table and schema name as inputs and sets an output value as essentially a boolean indicator of whether or not that table had the chosen value.
Potentially, you could perhaps create an outer proc to loop through the list of tables. And for each table it would call the inner proc that tests presence of the value.
This is a test proc that I used to verify the basic principle. It checks a column for APFILE='ACCPTH'. It returns either (1) or (0) depending on whether any row has that value or not.
-- Generate SQL
-- Version: V6R1M0 080215
-- Generated on: 03/22/14 02:59:07
-- Relational Database: TISI
-- Standards Option: DB2 for i
DROP SPECIFIC PROCEDURE SQLEXAMPLE.CHKFLDVAL ;
SET PATH "QSYS","QSYS2","SYSPROC","SYSIBMADM","mylib" ;
CREATE PROCEDURE SQLEXAMPLE.CHKFLDVAL (
IN TABLENAME VARCHAR(128) ,
IN SCHEMANAME VARCHAR(128) ,
OUT VALFOUND SMALLINT )
LANGUAGE SQL
SPECIFIC SQLEXAMPLE.CHKFLDVAL
NOT DETERMINISTIC
READS SQL DATA
CALLED ON NULL INPUT
SET OPTION ALWBLK = *ALLREAD ,
ALWCPYDTA = *OPTIMIZE ,
COMMIT = *NONE ,
CLOSQLCSR = *ENDMOD ,
DECRESULT = (31, 31, 00) ,
DFTRDBCOL = *NONE ,
DLYPRP = *NO ,
DYNDFTCOL = *NO ,
DYNUSRPRF = *USER ,
RDBCNNMTH = *RUW ,
SRTSEQ = *HEX
P1 : BEGIN
DECLARE STMTSQL VARCHAR ( 256 ) ;
DECLARE RTNRESULT SMALLINT ;
SET STMTSQL = 'VALUES (select CASE WHEN count(*) = 0 THEN 0 ELSE 1 END as chkVal from ' CONCAT SCHEMANAME CONCAT '.' CONCAT TABLENAME CONCAT ' where APFILE=''ACCPTH'' group by APFILE) INTO ?' ;
PREPARE STMT_NAME FROM STMTSQL ;
EXECUTE STMT_NAME USING RTNRESULT ;
SET VALFOUND = RTNRESULT ;
END P1 ;
COMMENT ON SPECIFIC PROCEDURE SQLEXAMPLE.CHKFLDVAL
IS 'Check field value in some table' ;
If I call it with a different TableName or SchemaName parameter value, I can get different values returned in rtnResult.
SQL is all that's actually needed. It's not a particularly good thing for SQL to do.

You cannot do this using just SQL statements. You will have to do a bit of scripting or programming of some sort to create new queries based on the table names you find and run them.

Related

Value not Store in Dynamic SQL

I've different different tables to categorically store data and a log table where all the transactions log are recorded
e.g. 1) VoucherNO, Add, ...
2) VoucherNO, Delete, ..
After I backup the database and restore in another server for my Reporting Purpose. That time I want to ensure all the log data and transaction are available in TestDB if not then I remove log from 'AUD_USER_ACTIVITY'.
To find the transaction exist or not, I create a dynamic sql select statement and check whether record is exist or not.
Basis on #RecExist Value I do the action like if records is not available in TestDB the log will be remove, if record exist immediately break this loop and going for next procedure
But #RecExist variable is not updating in Dynamic SQL Execution. Please guide me
declare #MvDocNo varchar(50)
DECLARE #SCtr as DECIMAL(10,0)
declare #LocationCode varchar(4)
declare #UName Nvarchar(40)
declare #toe varchar(30)
declare #QryTxt as nvarchar(MAX);
Declare #RecExist as INT =0;
SET #RecExist=0
WHILE #RecExist=0
BEGIN
select top 1 #MvDocNo=DOCNO, #SCtr=SrlNo,#LocationCode =DMLTYPE,#UName=TABLENAME
FROM R_AUDDB..AUD_USER_ACTIVITY
WHERE DBNAME='TestDB' and DMLTYPE not in ('AD','D','PD') ORDER BY SRLNO DESC;
select top 1 #toe=docno from TestDB..M_TYPEOFENTRY where TBLNAME=#UName;
set #QryTxt='Select #RecExist=1 From R_TestDB..'+#UName+ ' Where '+#toe+'='''+#MvDocNo+''''
exec (#QryTxt)
IF #RecExist=0
BEGIN
DELETE R_AUDDB..AUD_USER_ACTIVITY WHERE SRLNO=#SCtr
END
END
The following code sample demonstrates how to check for a row in a table with a specific column and value using dynamic SQL. You ought to be able to change the values of the first three variables to reference a table and column in your database for testing.
Note that SQL injection is still possible: there is no validation of the table or column names.
-- Define the table to check and the target column name and value.
declare #TableName as SysName = 'Things';
declare #ColumnName as SysName = 'ThingName';
declare #TestValue as NVarChar(32) = 'Beth';
-- Create a SQL statement to check for a row in the target table with the specified column name and value.
declare #SQL as NVarChar(1024);
declare #Result as Bit;
-- Note that only object names are substituted into the statement at this point and QuoteName() is used to reduce problems.
set #SQL = N'select #iResult = case when exists ( select 42 from dbo.' + QuoteName( #TableName ) +
N' where ' + QuoteName( #ColumnName ) + N' = #iTestValue ) then 1 else 0 end;'
select #SQL as SQL;
-- Execute the SQL statement.
-- Note that parameters are used for all values, i.e. the target value and return value.
execute sp_executesql #stmt = #SQL,
#params = N'#iTestValue NVarChar(32), #iResult Bit output',
#iTestValue = #TestValue, #iResult = #Result output
-- Display the result.
select #Result as Result;

simple stored procedure in Postgres

I am copying data (importing)from table tmp_header into as_solution2 table, first IdNumber and Date needs to be checked on destiny table, to not copy repeated values. if date and idNumber are found in destiny table, i don't copy the row, if not found ,row is copied into table as_solution2.
Source table has 800.000 records and destiny table already contains 200.000 records.
caveat: the id_solution pk in "as_solution2" table is not serial, so I created a sequence and start from the last id.
v_max_cod_solicitud := (select max(id_solution)+1 from municipalidad.as_solution2);
CREATE SEQUENCE increment START v_max_cod_solicitud;
this provokes an errorerror
tmp_header (id, cod_cause, idNumber , date_sol(2012-05-12), glosa_desc)
as_solution2(id_solution, cod_cause, idNumber, date_sol, desc )
CREATE OR REPLACE FUNCTION municipalidad.as_importar()
RETURNS integer AS
$$
DECLARE
v_max_cod_solicitud numeric;
id_solution numeric;
begin
v_max_cod_solicitud := (select max(id_solution)+1 from municipalidad.as_solution2);
CREATE SEQUENCE increment START v_max_cod_solicitud;
INSERT INTO municipalidad.as_solution2(
id_solution,
cod_cause,
idNumber,
date_sol,
desc,
)
SELECT
(SELECT nextval('increment')), <-- when saving i need to start from the last sequence number
cod_causingreso,
idNumber,
date_sol,
glosa_atenc,
FROM municipalidad.tmp_header as tmp_e
WHERE(SELECT count(*)
FROM municipalidad.as_solution2 as s2
WHERE s2.idNumber = tmp_e.idNumber AND s2.date_sol::date = tmp_e.date_sol::date)=0;
drop sequence increment;
return 1;
end
$$
LANGUAGE 'plpgsql'
thanks in advance
You can brute-force the execution of the sequence with the start parameter as follows:
execute (format ('CREATE SEQUENCE incremento start %s', v_max_cod_solicitud));
Unrelated, but I think you will gain efficiencies by changing your insert to use an anti-join instead of the Where select count (*) = 0:
INSERT INTO as_solution2(
id_solution,
cod_cause,
idNumber,
date_sol,
description
)
SELECT
nextval('incremento'), -- when saving i need to start from the last sequence number
cod_causingreso,
idNumber,
date_sol,
glosa_atenc
FROM tmp_header as tmp_e
WHERE not exists (
select null
from as_solution2 s2
where
s2.idNumber = tmp_e.idNumber AND
s2.date_sol::date = tmp_e.date_sol::date
)
This will scale very nicely as your dataset increases in size.
Even though it's not listed as a reserved key word in https://www.postgresql.org/docs/9.5/sql-keywords-appendix.html, the increment in your create sequence statement might not be allowed here:
CREATE SEQUENCE increment START v_max_cod_solicitud;
As the parser expects this:
ALTER SEQUENCE name [ INCREMENT [ BY ] increment ]
It probably thinks you forgot the name

How to work with more than one output parameter in single stored procedure

I have a SP with an Output parameter that looks like:
ALTER PROCEDURE [dbo].[SP_Name] #VarName decimal(18,2) OUTPUT as ...
I call that procedure from vb.net to get the value for calculations. My problem is: I have 8 SP's with the following structure:
CREATE PROCEDURE [dbo].[SP_Name] #VarName decimal(18,2) OUTPUT as ...
CREATE TABLE #TempTable
Begin
Select ...
End
SET #VarName = Result
But the TempTable is always the same. No I am looking for a way to get all 8 values with only one stored procedure. My idea:
CREATE PROCEDURE [dbo].[SP_Name] #VarName decimal(18,2) OUTPUT as ...
CREATE TABLE #TempTable
---Get first value
Begin
Select ...
End
SET #VarName1 = Result
---Get second value
Begin
Select ...
End
SET #VarName2 = Result
...
How do i have to rewrite the line: ALTER PROCEDURE [dbo].[SP_Name] #VarName decimal(18,2) OUTPUT ir can I even work with an array?
You can use a single stored procedure with all your queries in it. Following will return a single row result set with eight fields and you can grab them from your code using the specific filed name or index.
CREATE PROCEDURE [dbo].[SP_Name]
#VarName decimal(18,2)
AS
BEGIN
DECLARE #VarName1 Datatype, #VarName2 Datatype, ...#VarName8 Datatype
SELECT #VarName1 = yourCol
FROM --First query
SELECT #VarName2 = yourCol
FROM --Second query
...
SELECT #VarName8 = yourCol
FROM --Eighth query
--Finally Select all the variables
SELECT #VarName1 Col1, #VarName2 Col2, ...,#VarName8 Col8
END
OR if you are looking to return results of your all 8 queries, that is also possible. Simply do your select queries in a single stored procedure and grab the DATASET from your code and you can access individual table using zero based Index (ex DataTable1 = YourDataSet.Tables[0])

Check that user belongs to db role

I took some code here: Check if role consists of particular user in DB?
SELECT *
FROM sys.database_role_members AS RM
JOIN sys.database_principals AS U
ON RM.member_principal_id = U.principal_id
JOIN sys.database_principals AS R
ON RM.role_principal_id = R.principal_id
WHERE U.name = 'operator1'
AND R.name = 'myrole1'
that query returns 1 row. It means that user 'operator1' belongs to role 'myrole1'.
Now I'm trying to create a stored procedure for this:
CREATE PROCEDURE [dbo].[Proc1]
(
#userName varchar,
#roleName varchar
)
AS
IF EXISTS(SELECT *
FROM sys.database_role_members AS RM
JOIN sys.database_principals AS U
ON RM.member_principal_id = U.principal_id
JOIN sys.database_principals AS R
ON RM.role_principal_id = R.principal_id
WHERE U.name = #userName
AND R.name = #roleName)
RETURN 0
ELSE RETURN -1
I use standard command from sql server 2008 'Execute stored procedure'
DECLARE #return_value int
EXEC #return_value = [dbo].[Proc1]
#userName = N'operator1',
#roleName = N'myrole1'
SELECT 'Return Value' = #return_value
GO
it always returns -1. WHY ???
You need to define the length attribute for each of your procedure parameters. If you change the beginning of your stored procedure declaration to the following, it will return the correct response:
CREATE PROCEDURE [dbo].[Proc1]
(
#userName varchar(255),
#roleName varchar(255)
)
AS
Without the length attributes, SQL Server auto-defines the length of the variables as 1, so the user and role were being matched against "o" and "m", respectively. SQL Server is inconsistent in it's behavior of how it treats variables without the length specifier - sometimes they have a length of 30 and sometimes they have a length of 1. It is best practice to always specify the length attributes on string variables. See the following SQL Blog article for more information:
Bad habits to kick : declaring VARCHAR without (length)
This code is not good, because it can return false, if a user is member of a group, which then is member of a role.
User the built-in function IS_MEMBER() -- this is much simpler and always accureate.

Array-like access to variables in T-SQL

In my stored procedure I have multiple similar variables #V1, #V2 ... #V20 (let's say 20 of them) FETCHED from a record. How would I use dynamic SQL to make 20 calls to another stored procedure using those variables as parameters?
Of course #V[i] syntax is incorrect, but it expresses the intent
fetch next from maincursor into #status, #V1, #V2, ...
while #i<21
begin
-- ??? execute sp_executesql 'SecondSP', '#myParam int', #myParam=#V[i]
-- or
-- ??? execute SecondSP #V[i]
set #i = #i+1
end
As others have said, set up a temporary table, insert the values that you need into it. Then "iterate" through it executing the necessary SQL from those values. This will allow you to have 0 to MANY values to be executed, so you don't have to set up a variable for each.
The following is a complete sample of how you may go about doing that without cursors.
SET NOCOUNT ON
DECLARE #dict TABLE (
id INT IDENTITY(1,1), -- a unique identity column for reference later
value VARCHAR(50), -- your parameter value to be passed into the procedure
executed BIT -- BIT to mark a record as being executed later
)
-- INSERT YOUR VALUES INTO #dict HERE
-- Set executed to 0 (so that the execution process will pick it up later)
-- This may be a SELECT statement into another table in your database to load the values into #dict
INSERT #dict
SELECT 'V1Value', 0 UNION ALL
SELECT 'V2Value', 0
DECLARE #currentid INT
DECLARE #currentvalue VARCHAR(50)
WHILE EXISTS(SELECT * FROM #dict WHERE executed = 0)
BEGIN
-- Get the next record to execute
SELECT
TOP 1 #currentid = id
FROM #dict
WHERE executed = 0
-- Get the parameter value
SELECT #currentvalue = value
FROM #dict
WHERE id = #currentid
-- EXECUTE THE SQL HERE
--sp_executesql 'SecondSP', '#myParam int', #myParam =
PRINT 'SecondSP ' + '#myParam int ' + '#myParam = ' + #currentvalue
-- Mark record as having been executed
UPDATE d
SET executed = 1
FROM #dict d
WHERE id = #currentid
END
Use a #TempTable
if you are at SQL Server 2005 you can create a #TempTable in the parent stored procedure, and it is available in the child stored procedure that it calls.
CREATE TABLE #TempTable
(col1 datatype
,col2 datatype
,col3 datatype
)
INSERT INTO #TempTable
(col1, col2, col3)
SELECT
col1, col2, col3
FROM ...
EXEC #ReturnCode=YourOtherProcedure
within the other procedure, you have access to #TempTable to select, delete, etc...
make that child procedure work on a set of data not on one element at a time
remember, in SQL, loops suck performance away!
Why not just use the table variable instead, and then just loop through the table getting each value.
Basically treat each row in a table as your array cell, with a table that has one column.
Just a thought. :)
This seems like an odd request - will you always have a fixed set of variables? What if the number changes from 20 to 21, and so on, are you constantly going to have to be declaring new variables?
Is it possible, instead of retrieving the values into separate variables, to return them each as individual rows and just loop through them in a cursor?
If not, and you have to use the individual variables as explained, here's one solution:
declare #V1 nvarchar(100)
set #V1 = 'hi'
declare #V2 nvarchar(100)
set #V2 = 'bye'
declare #V3 nvarchar(100)
set #V3 = 'test3'
declare #V4 nvarchar(100)
set #V4 = 'test4'
declare #V5 nvarchar(100)
set #V5 = 'end'
declare aCursor cursor for
select #V1
union select #V2 union select #V3
union select #V4 union select #V5
open aCursor
declare #V nvarchar(100)
fetch next from aCursor into #V
while ##FETCH_STATUS = 0
begin
exec TestParam #V
fetch next from aCursor into #V
end
close aCursor
deallocate aCursor
I don't really like this solution, it seems messy and unscalable. Also, as a side note - the way you phrased your question seems to be asking if there are arrays in T-SQL. By default there aren't, although a quick search on google can point you in the direction of workarounds for this if you absolutely need them.