Dynamically generate GRANT SQL from syscat.tabauth table - sql

I have a database (on DB2 9.7) A in which suppose I have tables X,Y,Z...n
Now I have created same tables X,Y,Z...n in database B. I want to provide same GRANTs to users in database B as it was in database A. So based on SYSCAT.TABAUTH I am trying to generate GRANT SQLs. I have written the following query for it:
db2 "select 'GRANT '||
case INSERTAUTH
WHEN 'Y' THEN 'INSERT,'
WHEN 'N' THEN ' '
END||
case ALTERAUTH
WHEN 'Y' THEN 'ALTER,'
WHEN 'N' THEN ' '
END||
case DELETEAUTH
WHEN 'Y' THEN 'DELETE,'
WHEN 'N' THEN ' '
END||
case SELECTAUTH
WHEN 'Y' THEN 'SELECT,'
WHEN 'N' THEN ' '
END||
case UPDATEAUTH
WHEN 'Y' THEN 'UPDATE,'
WHEN 'N' THEN ' '
END||
' ON '||TABSCHEMA||'.'||TABNAME||' TO '||GRANTEE from SYSCAT.TABAUTH
where INSERTAUTH='Y' OR ALTERAUTH='Y' OR DELETEAUTH='Y' OR SELECTAUTH='Y' OR UPDATEAUTH='Y'"
However, the problem I am facing is of the additional ',' at end.
Suppose a user has only Insert auth, the above query will generate GRANT sql as:
GRANT INSERT, ON SCHEMA.TABLE TO GRANTEENAME
or if user has insert and select grants then:
GRANT INSERT,SELECT, ON SCHEMA.TABLE TO GRANTEENAME
How can I solve this? Please help..

This is what I finally made and it works fine:
db2 "SELECT
'GRANT ' ||
SUBSTR(T.AUTHSTRING, 1 ,LENGTH(T.AUTHSTRING) - 1)
|| T.TABNAME
FROM(
select
case INSERTAUTH
WHEN 'Y' THEN 'INSERT,'
WHEN 'N' THEN ''
END
||
case ALTERAUTH
WHEN 'Y' THEN 'ALTER,'
WHEN 'N' THEN ''
END
||
case DELETEAUTH
WHEN 'Y' THEN 'DELETE,'
WHEN 'N' THEN ''
END
||
case SELECTAUTH
WHEN 'Y' THEN 'SELECT,'
WHEN 'N' THEN ''
END
||
case UPDATEAUTH
WHEN 'Y' THEN 'UPDATE,'
WHEN 'N' THEN ''
END
AS AUTHSTRING,
' ON ' ||TRIM(TABSCHEMA)||'.'||TRIM(TABNAME)||' TO ' ||GRANTEE AS TABNAME
from SYSCAT.TABAUTH
where INSERTAUTH='Y'
OR ALTERAUTH='Y'
OR DELETEAUTH='Y'
OR SELECTAUTH='Y'
OR UPDATEAUTH='Y'
) AS T"

You can always push the dynamic part into a sub-query, and then use a combination of LENGTH() and SUBSTR() to trim off the extra comma. Here is your SQL I modified a bit:
SELECT
'GRANT' ||
SUBSTR(T.AUTHSTRING, 1 LENGTH(T.AUTHSTRING) -1)
|| T.TABNAME
FROM(
select
case INSERTAUTH
WHEN 'Y' THEN 'INSERT,'
WHEN 'N' THEN ' '
END
||
case ALTERAUTH
WHEN 'Y' THEN 'ALTER,'
WHEN 'N' THEN ' '
END
||
case DELETEAUTH
WHEN 'Y' THEN 'DELETE,'
WHEN 'N' THEN ' '
END
||
case SELECTAUTH
WHEN 'Y' THEN 'SELECT,'
WHEN 'N' THEN ' '
END
||
case UPDATEAUTH
WHEN 'Y' THEN 'UPDATE,'
WHEN 'N' THEN ' '
END
AS AUTHSTRING,
' ON ' || RTRIM(TABSCHEMA) || '.' || RTRIM(TABNAME)||' TO ' || RTRIM(GRANTEE) AS TABNAME
from SYSCAT.TABAUTH
where INSERTAUTH='Y'
OR ALTERAUTH='Y'
OR DELETEAUTH='Y'
OR SELECTAUTH='Y'
OR UPDATEAUTH='Y'
) AS T
I tested this, and it worked on LUW 9.7 and z/OS 9.1.

Related

Combining a CASE WHEN statement & a column containing strings of type "Column 1 || '_' || Column 2" in Oracle SQL

I have a table consisting of three columns, which are called the following:
1) Month
2) Store_Type
3) City
I need this table to be expanded to contain five columns and the two columns that I wish to be added are detailed below.
Firstly, the query needs to create a new column called Store_Code. The Store_Code columns job is to store a numerical value which corresponds to what type of store it is.
I presume this would done using a CASE WHEN statement of the type:
SELECT Month,Store_Type,City,
CASE
WHEN Store_Type = 'Corner Shop' THEN '1'
WHEN Store_Type = 'Megastore' THEN '2'
WHEN Store_Type = 'Petrol Station' THEN '3'
....
ELSE '10'
END Store_Code
FROM My_Table
After this is complete, I need to create a column known as "Store_Key". The values contained within the Store_Key column need to be of the following form:
"The Month For That Row""The Store Type For That Row""The City associated with that row"_"The Store Code for that row"
I imagine the best way to create this column would be to use a query similar to the following:
SELECT (My_Table.Month || '_' || My_Table.Store_Type || '_' || My_Table.City || '_' ||
My_Table.Store_Code)
FROM My_Table
What I need is for these two separate queries to be combined into one query. I imagine this could be done by sub-setting the different SELECT queries but I am open to and grateful for any alternative solutions.
Thank you for taking the time to read through this problem and all solutions are greatly appreciated.
Do the case expression part inside a derived table (the subquery):
SELECT (My_Table2.Month || '_' || My_Table2.Store_Type || '_' || My_Table2.City || '_' ||
My_Table2.Store_Code)
FROM
(
SELECT Month,Store_Type,City,
CASE
WHEN Store_Type = 'Corner Shop' THEN '1'
WHEN Store_Type = 'Megastore' THEN '2'
WHEN Store_Type = 'Petrol Station' THEN '3'
....
ELSE '10'
END Store_Code
FROM My_Table
) My_Table2
If this is you trying to populate your new columns, then you need an update statement. I would use two updates to ensure you get the store_case committed for your store_code. Otherwise if you're deriving it in real time, the subquery select answer would be the way to go.
update my_table
set store_case =
case store_type
when 'Corner Shop' then 1
when 'Megastore' THEN 2
when 'Petrol Station' THEN 3
...
else 10
end case;
commit;
update my_table
set store_code = Month || '_' || to_char(Store_Type) || '_' || City || '_' || Store_Code;
commit;
Why to use sub query? It can be done within single query as following:
SELECT My_Table.Month || '_' || My_Table.Store_Type || '_' || My_Table.City || '_' ||
CASE
WHEN Store_Type = 'Corner Shop' THEN '1'
WHEN Store_Type = 'Megastore' THEN '2'
WHEN Store_Type = 'Petrol Station' THEN '3'
....
ELSE '10'
END as result
FROM My_Table
or you can use DECODE function as following:
SELECT My_Table.Month || '_' || My_Table.Store_Type || '_' || My_Table.City || '_' ||
DECODE(Store_Type,
'Corner Shop', '1',
'Megastore', '2',
'Petrol Station', '3'
....,
'10') -- this is default value same as else part of the case statement
as result
FROM My_Table
Cheers!!

Executing resulting rows for the result of Dynamic Native SQL query

I'm going mental over this. I'm fairly new to dynamic SQL, so I may just not be asking Google the right question, but here's what I'm trying to do... I have a query with dynamic SQL. When I run that query, it produces several rows. All of these rows (about 30) make up a single union query. I can copy all of those rows and paste into a new query and run - works fine, but what I need to do is run this all in a single query. I've looked up examples of using execute immediate and fetch, but I cannot seem to get them to actually spit out the data...they just end up saying something like "Executed Successfully", but doesn't actually produce any resulting rows. The resulting column name of the below SQL is "qry_txt" - instead of producing it at face value, I want to execute it as a query. Again, I may not be articulating this well, but I'm basically trying to turn 2 queries (with a manual copy/paste step involved) into a single query. Hope this makes sense...
Here's my SQL:
Select CASE when
lead(ROWNUM) over(order by ROWNUM) is null then
'SELECT '||''''||T.TABLE_NAME||''''||' as TABLE_NAME,'||''''||T.COLUMN_NAME||''''||' as COLUMN_NAME, cast('|| T.COLUMN_NAME ||' as
varchar2(100)) as SAMPLE_DATA ||
from rpt.'||T.TABLE_NAME ||' where '||T.COLUMN_NAME||' is not null and ROWNUM=1;'
else
'SELECT '||''''||T.TABLE_NAME||''''||' as TABLE_NAME,'||''''||T.COLUMN_NAME||''''||' as COLUMN_NAME, cast('|| T.COLUMN_NAME ||' as
varchar2(100)) as SAMPLE_DATA from rpt.'||T.TABLE_NAME ||' where '||T.COLUMN_NAME||' is not null and ROWNUM=1 union ' end as qry_txt
from all_tab_columns t where T.OWNER='rpt' and T.DATA_TYPE != 'BLOB' and T.DATA_TYPE != 'LONG' and T.TABLE_NAME = 'NME_DMN'
ORDER BY ROWNUM asc;
You cannot write a dynamic query in a SQL. You need to use PLSQL block to accomploish that. Please see how you can do it.
PS: Code is not tested.
declare
var1 <decalration same of column in select list> ;
var2 <decalration same of column in select list> ;
var3 <decalration same of column in select list> ;
....
varn ;
begin
for i in ( SELECT LEAD (ROWNUM) OVER (ORDER BY ROWNUM) COl1
FROM all_tab_columns t
WHERE T.OWNER = 'rpt'
AND T.DATA_TYPE != 'BLOB'
AND T.DATA_TYPE != 'LONG'
AND T.TABLE_NAME = 'NME_DMN'
ORDER BY ROWNUM ASC)
Loop
If i.col1 IS NULL Then
execute immediate 'SELECT '
|| ''''
|| T.TABLE_NAME
|| ''''
|| ' as TABLE_NAME,'
|| ''''
|| T.COLUMN_NAME
|| ''''
|| ' as COLUMN_NAME, cast('
|| T.COLUMN_NAME
|| ' as
varchar2(100)) as SAMPLE_DATA ||
from rpt.'
|| T.TABLE_NAME
|| ' where '
|| T.COLUMN_NAME
|| ' is not null and ROWNUM=1' into var1 , var2 ,var3 ....varn;
Else
execute immediate 'SELECT '
|| ''''
|| T.TABLE_NAME
|| ''''
|| ' as TABLE_NAME,'
|| ''''
|| T.COLUMN_NAME
|| ''''
|| ' as COLUMN_NAME, cast('
|| T.COLUMN_NAME
|| ' as
varchar2(100)) as SAMPLE_DATA from rpt.'
|| T.TABLE_NAME
|| ' where '
|| T.COLUMN_NAME
|| ' is not null and ROWNUM=1' into var1 , var2 ,var3 ....varn;
end if;
End Loop;
exception
when others then
dbms_output.put_lin(sqlcode ||'--'||sqlerrm);
End;

Remove substring within aggregate

I am working in a data warehouse and combining 3 columns with the following:
CAST(
ISNULL(PORCH_TYPE_1,'') ||
CASE WHEN PORCH_TYPE_2 IS NULL THEN '' ELSE ', ' END ||
ISNULL(PORCH_TYPE_2,'') ||
CASE WHEN PORCH_TYPE_3 IS NULL THEN '' ELSE ', ' END ||
ISNULL(PORCH_TYPE_3,'') AS VARCHAR(250)
) AS PORCH_TYPE,
This is working, except in the results, I can end up with something that looks like:
Open Porch, None, None
or
None, None, Open Porch
What I'm needing to do is remove both
None
and
,None
How would I go about doing that within this same column/statement?
One method is to concatenate the comma to the end of each valid porch type. Then remove the final trailing comma using TRIM():
TRIM(TRAILING ',' FROM
((CASE WHEN PORCH_TYPE_1 <> 'None' THEN PORCH_TYPE_1 || ',' END) ||
(CASE WHEN PORCH_TYPE_2 <> 'None' THEN PORCH_TYPE_2 || ',' END) ||
(CASE WHEN PORCH_TYPE_3 <> 'None' THEN PORCH_TYPE_3 || ',' END)
)

How to dynamically select tables and columns in SQL Server?

I am trying to create a SQL code in SQL Server that dynamically selects all the tables in a specific database and then for each column in each table, counts the number of missing values and non-null values. I also want this result inserted into another table.
Is there any way I can do this without manually changing the column names for each:
Table Name - Column selection
I have a teradata code for the same which I tried to convert to SQL Server code. But I am unable to get the dynamic allocation and insertion parts right.
insert into temp
values (select ''CAMP'',
rtrim(''' || tablename || '''),
rtrim(''' || columnname || '''),
rtrim(''' || columnformat || '''),
count(1),
count(rtrim(upper(case when ' || columnname || '='''' then NULL else '|| columnname ||' end))),
(cast (count(rtrim(upper(case when ' || columnname || '='''' then NULL else ' || columnname || ' end))) as float) / (cast (count(1) as float))) * 100,
count(distinct rtrim(upper(case when ' || columnname || '='''' then NULL else '|| columnname ||' end))),
min(rtrim(upper(case when ' || columnname || '='''' then NULL else '|| columnname ||' end))),
max(rtrim(upper(case when ' || columnname || '='''' then NULL else '|| columnname ||' end))),
min(len(rtrim(upper(case when ' || columnname || '='''' then NULL else '|| columnname ||' end)))),
max(len(rtrim(upper(case when ' || columnname || '='''' then NULL else '|| columnname ||' end))))
from ' || tablename ||')
Any help on this front would be great!
Thanks!
Not sure if you need a UNION or a JOIN, but in either case you can just use a three-part name for the object in the other database if you are using multi-database:
USE database1; // Your database name
GO
CREATE VIEW dbo.MyView
AS
SELECT columns FROM dbo.Table1
UNION ALL
SELECT columns FROM database2.dbo.Table2; //second database
GO
select * from dbo.MyView // Getting all data from view
Hope that helps
Does something like this help you:
SELECT [Table] = t.[name]
, [Column] = c.[name]
FROM sys.tables t
INNER JOIN sys.columns c
ON c.[object_id] = t.[object_id]

Phantom LONG datatype is crashing my SQL code - ORA-00997

I have the following code which is supposed to find each column in a database and ouptput the column name, table name, data type, number of null values, and number of rows.
The problem that I run into is that when I run it, it runs for about two minutes, and then complains about an 'illegal use of LONG datatype', but I am not using any LONG here.
If I edit my search to only select WHERE rownum < 100 (commented out in the following code), it works perfectly. Additionally, if I only do the SELECT statement, it runs just fine and outputs all of the correct SQL statements. (about 18000 of them) So I am guessing that the error is in the loop somewhere.
Any guidance on how to fix this?
SET SERVEROUTPUT ON;
declare
myCol1 varchar2(1000);
myCol2 varchar2(1000);
myCol3 varchar2(1000);
myCol4 number;
myCol5 number;
begin
for line in
(
SELECT
'SELECT ''' || atc.column_name || ''', ''' || atc.table_name || ''', ''' || atc.data_type || ''',
SUM(CASE WHEN temp.'|| atc.column_name || ' IS NULL THEN 0 ELSE 1 END) "Filled Values",
COUNT(temp.' || atc.column_name || ') "Total Records"
FROM all_tab_columns atc
JOIN '|| atc.table_name || ' temp ON atc.column_name = ''' ||
atc.column_name ||''' AND atc.table_name = ''' || atc.table_name || '''' AS SQLRow
FROM all_tab_columns atc --WHERE rownum < 100
)
loop
execute immediate line.Sqlrow into myCol1, myCol2, myCol3, myCol4, myCol5;
INSERT INTO results VALUES (myCol1, myCol2, myCol3, myCol4, myCol5);
end loop;
end;
SELECT * FROM results;
/
One of the tables being picked up has a LONG column. Your static code isn't referring to it directly, but the dynamic SQL you're generating is, e.g.
SELECT 'SQL_TEXT', 'OL$', 'LONG',
SUM(CASE WHEN temp.SQL_TEXT IS NULL THEN 0 ELSE 1 END) "Filled Values",
COUNT(temp.SQL_TEXT) "Total Records"
FROM all_tab_columns atc
JOIN OL$ temp ON atc.column_name = 'SQL_TEXT' AND atc.table_name = 'OL$'
It's complaining about the COUNT. You can't apply aggregates, even something that seems as simple as a count, to a LONG column. Or any built-in function; from the data types documentation:
In addition, LONG columns cannot appear in these parts of SQL
statements:
GROUP BY clauses, ORDER BY clauses, or CONNECT BY clauses or with the DISTINCT operator in SELECT statements
The UNIQUE operator of a SELECT statement
The column list of a CREATE CLUSTER statement
The CLUSTER clause of a CREATE MATERIALIZED VIEW statement
SQL built-in functions, expressions, or conditions
...
The ROWNUM filter just happens to be stopping before it encounters any LONG columns in the data dictionary.
To run this for everything else you'd need to exclude LONG columns from your query. You might want to restrict it you selected schemas though; reporting the data types of system tables/columns seems a little odd.
I'm not sure why you're joining back to all_tab_columns in your generated query. This would get the same result (for a column with a different data type in the same table):
SELECT 'SPARE2', 'OL$', 'VARCHAR2',
SUM(CASE WHEN temp."SPARE2" IS NULL THEN 0 ELSE 1 END),
COUNT(temp."SPARE2")
FROM SYSTEM."OL$" temp
COUNT only counts non-null values, so it'll give you the same result as the SUM (except the sum gives null if the table is empty). If you want to count all rows then count a constant, not the column name. So instead you could do:
SELECT 'SPARE2', 'OL$', 'VARCHAR2',
COUNT(temp."SPARE2"),
COUNT(1)
FROM SYSTEM."OL$" temp
You can give a null result for LONG and LOB values, rather than skipping those columns altogether, by changing the dynamic query based on the data type. You might also want to quote all the identifiers just in case you have mixed case or other problems:
for line in (
SELECT
'SELECT ''' || atc.column_name || ''', '
|| '''' || atc.table_name || ''', '
|| '''' || atc.data_type || ''', '
|| CASE WHEN DATA_TYPE IN ('LONG', 'CLOB', 'BLOB') THEN 'NULL'
ELSE 'COUNT(temp."' || atc.column_name || '")' END || ', '
|| 'COUNT(1) '
|| 'FROM '|| atc.owner || '."' || atc.table_name || '" temp ' AS SQLRow
FROM all_tab_columns atc
WHERE owner NOT IN ('SYS', 'SYSTEM') -- and others
) loop
Or use your SUM version if you want to get the not-null count for those too, I suppose, but with NVL so it reports zero for empty tables.