Oracle - Loop through a list of columns - oracle

I have a base SQL query that I'm hoping to turn into a dynamic cursor.
I have a list of columns that I am checking to see if the values have changed since previous run, columns that I am checking include: Income, Ethnicity, etc.
The output of the before-and-after values need to be stored in a temp or permanent table for further investigation - such as source of the value change etc...
Because the list of the columns is more than 600+, I don't think I want to compile the base SQL 600 times..
Is there a better way to write out a dynamic SQL cursor to accomplish this task?
Thanks!
---base sql
SELECT a.*,
'Last_name' AS "field_name",
b.LAST_name AS last_name_updated
from
(SELECT person_id, last_name
FROM person
WHERE batch_id = (select max(batch_id) from person)
) a
FULL OUTER JOIN
(SELECT person_id, last_name
FROM person
WHERE batch_id = (select max(batch_id) - 1 from person)
) b
ON a.person_id = b.person_id
WHERE nvl(a.last_name,0) <> nvl(b.last_nm,0)
UNION
SELECT a.*,
'Income',
b.income AS income_updated
from
(SELECT person_id, income
FROM person
WHERE batch_id = (select max(batch_id) from person)
) a
FULL OUTER JOIN
(SELECT person_id, income
FROM person
WHERE batch_id = (select max(batch_id) - 1 from person)
) b
ON a.person_id = b.person_id
WHERE nvl(a.income,0) <> nvl(b.income,0)
---desired output
person_id || field_name || previous_value || updated_value
8783 || income || 95000 || 98000
235731 || last_name || Dawson || Dawson Jr.

Here is something to get you started. Rather than generate one enormous query with 600 unions, it generates 600 queries and runs them sequentially. You would need to add code to save the results from each iteration, perhaps into another table:
declare
l_sql long;
l_default varchar2(10);
l_max_batch_id number;
rc sys_refcursor;
begin
select max(batch_id)
into l_max_batch_id
from person;
for r in (select column_name, data_type
from user_tab_columns
where table_name = 'PERSON'
and column_name not in ('PERSON_ID') -- Exclude any other columns you don't want to compare
)
loop
l_sql := q'[SELECT a.person_id,
a.col AS new_value
'#COL#' AS column_name,
b.#COL# AS old_value
from
(SELECT person_id, #COL#
FROM person
WHERE batch_id = #MAXBATCH#
) a
FULL OUTER JOIN
(SELECT person_id, #COL#
FROM person
WHERE batch_id = #MAXBATCH#-1
) b
ON a.person_id = b.person_id
WHERE nvl(a.#COL#,#DEFAULT#) <> nvl(b.#COL#,#DEFAULT#)]';
l_sql := replace (l_sql, '#COL#', r.column_name);
l_sql := replace (l_sql, '#MAXBATCH#', l_max_batch_id);
l_default := case r.data_type
when 'NUMBER' then '0'
when 'DATE' then q'[DATE '4000-31-12']'
else q'['~~~']'
end;
l_sql := replace (l_sql, '#DEFAULT#', l_default);
open rc for l_sql;
-- Now fetch all the data and e.g. write to a table...
end loop;
end;

Related

Select into variables from cte using if oracle

I need to save values from cte into variables in the procedure.
My example:
WITH test_cte AS (
SELECT *
from(
SELECT date_a,lastName,firstName,birthDate, rank() over(ORDER BY date_a desc) rnk
FROM test_table a
join test_table b ON b.id = a.bid
)a1
WHERE rnk =1)
SELECT count(*) into count_a
FROM test_table a
join test_table b ON b.id = a.bid
WHERE a.code = code_name;
IF count_a > 0 THEN
SELECT date_a,lastName,firstName,birthDate
into date_a_var,lastName_var,firstName_var,birthDate_var
FROM test_cte;
ELSE
date_a_var := NULL;
lastName_var := NULL;
firstName_var := NULL;
birthDate_var := NULL;
END IF;
but when I try to compile it I'm getting next error:
PL / SQL: ORA-00942: table or view does not exist
on FROM test_cte;
What can I do to solve this problem?
In your code:
-- Start of first SELECT statement.
WITH test_cte AS (
SELECT *
from(
SELECT date_a,lastName,firstName,birthDate, rank() over(ORDER BY date_a desc) rnk
FROM test_table a
join test_table b ON b.id = a.bid
)a1
WHERE rnk =1
)
SELECT count(*) into count_a
FROM test_table a
join test_table b ON b.id = a.bid
WHERE a.code = code_name;
-- End of first SELECT statement.
IF count_a > 0 THEN
-- Start of second SELECT statement.
SELECT date_a,lastName,firstName,birthDate
into date_a_var,lastName_var,firstName_var,birthDate_var
FROM test_cte;
-- End of second SELECT statement.
ELSE
date_a_var := NULL;
lastName_var := NULL;
firstName_var := NULL;
birthDate_var := NULL;
END IF;
It won't work because the test_cte only exists for the one statement and when you finish the statement's final SELECT statement then it no longer exists for the subsequent statements. (However, you do not use the sub-query factoring clause [a.k.a. CTE] in the SELECT for that statement so it is not clear why you need the WITH clause.)
Instead of trying to use COUNT, just get the data and handle the NO_DATA_FOUND exception if it occurs (also, from Oracle 12, you don't need to use RANK and can use FETCH FIRST ROW WITH TIES instead):
DECLARE
date_a_var test_table.date_a%TYPE;
lastName_var test_table.lastname%TYPE;
firstName_var test_table.firstname%TYPE;
birthDate_var test_table.birthdate%TYPE;
BEGIN
BEGIN
SELECT date_a,
lastName,
firstName,
birthDate
INTO date_a_var,
lastName_var,
firstName_var,
birthDate_var
FROM test_table a
join test_table b ON b.id = a.bid
ORDER BY date_a DESC
FETCH FIRST ROW WITH TIES;
EXCEPTION
WHEN NO_DATA_FOUND THEN
date_a_var := NULL;
lastName_var := NULL;
firstName_var := NULL;
birthDate_var := NULL;
END;
-- Continue processing
END;
(Note: You may also get a TOO_MANY_ROWS exception if there are duplicate dates. Either use FETCH FIRST ROW ONLY or, before Oracle 12, the ROW_NUMBER analytic function.)

Is there a way to make a PLSQL script that lists all columns that IS NULL for every record in a table?

I am working with a huge database with several columns and records. I want to browse a specific table and make a list of the columns that are empty for every record.
Is this possible without refering to all the specific column names?
Thanks for help!
It's possible but if you have a lot data it will last a long time.
create table xxx as select * from dba_objects where rownum < 10000;
prepare test table get table stats. It can be long lasting process.
begin
dbms_stats.gather_table_stats(user,'XXX',estimate_percent =>100);
-- ..
-- others tables to analizye
end;
Generate reports.
select table_name,column_name from user_tab_cols where coalesce(low_value,high_value) is null and table_name in('XXX');
You can use the below script to find out the null columns in your database -
DECLARE
COUNT_COL INT;
SQL_STR VARCHAR2(100);
BEGIN
FOR I IN (SELECT OBJECT_NAME, COLUMN_NAME
FROM USER_OBJECTS UO
JOIN USER_TAB_COLS UTC ON UO.OBJECT_NAME = UTC.TABLE_NAME) LOOP
SQL_STR := 'SELECT COUNT(1) FROM ' || I.OBJECT_NAME || ' WHERE ' || i.COLUMN_NAME || ' IS NOT NULL';
EXECUTE IMMEDIATE SQL_STR INTO COUNT_COL;
IF COUNT_COL = 0 THEN
DBMS_OUTPUT.PUT_LINE(I.COLUMN_NAME);
END IF;
END LOOP;
END;
Here is the fiddle.
Try for all record in table:
SELECT a.owner, a.table_name, b.column_name
FROM all_tables a, all_tab_columns b
WHERE a.table_name = '<TABLE_NAME>'
AND a.table_name = b.table_name
AND a.num_rows = b.num_nulls
For all table
SELECT a.owner, a.table_name, b.column_name
FROM all_tables a, all_tab_columns b
WHERE a.table_name = b.table_name
AND a.num_rows = b.num_nulls

Find MAX(PK_COLUMN) and display it with corresponding Table Name

I am having 20 tables ( Each table has a PK and data ), i want to find out what is the current MAX(PK) Value for each table.
I Want the result as follows :
TABLE_NAME MAX_VAL
-------------------- ----------
TABELE_A 114
TABELE_B 55
TABELE_C 14
TABELE_D 866
TABELE_3 4552
is there any way to accomplish this or else i have to write 20 times SELECT MAX(PK_COL) FROM TABLE ?
Assuming your currently connected schema is composed of those twenty tables, and each have identical primary key column name(pk_col), then consider the following code block containing an implicit cursor :
declare
v_max pls_integer;
begin
dbms_output.put_line('table_name max_val');
for c in ( select * from user_tables )
loop
execute immediate 'select max(pk_col) from '||c.table_name into v_max;
dbms_output.put_line(c.table_name||' '||v_max);
end loop;
end;
/
i have found another method which will bring TABLE_NAME,PK_COLUMN and MAX( PK_COLUMN ).
SELECT CASE
WHEN RN = 1 THEN
FORMATTED_QUERY_SET
ELSE
FORMATTED_QUERY_SET || ' UNION ALL '
END AS FORMATTED_QUERY_SET
FROM (SELECT ' SELECT NVL(MAX( ' || COL.COLUMN_NAME ||
' ),0) CURR_MAX_VAL, ''' || TAB.TABLE_NAME ||
''' TABLE_NAME,''' || COL.COLUMN_NAME ||
''' COLUMN_NAME FROM ' || TAB.TABLE_NAME AS FORMATTED_QUERY_SET,
TAB.TABLE_NAME,
ROW_NUMBER() OVER(ORDER BY TAB.TABLE_NAME DESC) AS RN
FROM USER_CONSTRAINTS TAB
JOIN USER_CONS_COLUMNS COL
ON TAB.TABLE_NAME = COL.TABLE_NAME
JOIN USER_TAB_COLUMNS COL2
ON COL.COLUMN_NAME = COL2.COLUMN_NAME
AND COL.TABLE_NAME = COL2.TABLE_NAME
WHERE TAB.CONSTRAINT_TYPE = 'P'
AND COL.CONSTRAINT_NAME LIKE '%_PK'
AND REGEXP_LIKE(COL2.DATA_TYPE, ('NUMB|INTE')))
ORDER BY TABLE_NAME;
Copy the output returned by the above query and execute.
Note : Remove the last ' UNION ALL ' operator from the query string.
Note : Please correct me if i am doing anything wrong .

oracle read column names from select statement

I wonder how read column names in oracle. Ok, I know that there is table named USER_TAB_COLUMNS which gives info about it, but if I have 2 or 3 level nested query and I don't know column names. Or I just have simple query with join statement and i want to get column names. How to do that? any idey?
select * from person a
join person_details b where a.person_id = b.person_id
thanks
I would go for:
select 'select ' || LISTAGG(column_name , ',') within group (order by column_id) || ' from T1'
from user_tab_columns
where table_name = 'T1';
to get a query from database. To get columns with types to fill map you can use just:
select column_name , data_type
from user_tab_columns
where table_name = 'T1';
I assume you are looking for this:
DECLARE
sqlStr VARCHAR2(1000);
cur INTEGER;
columnCount INTEGER;
describeColumns DBMS_SQL.DESC_TAB2;
BEGIN
sqlStr := 'SELECT a.*, b.*, SYSDATE as "Customized column name"
FROM person a JOIN person_details b
WHERE a.person_id = b.person_id';
cur := DBMS_SQL.OPEN_CURSOR;
DBMS_SQL.PARSE(cur, sqlStr, DBMS_SQL.NATIVE);
DBMS_SQL.DESCRIBE_COLUMNS2(cur, columnCount, describeColumns);
FOR i IN 1..columnCount LOOP
DBMS_OUTPUT.PUT_LINE ( describeColumns(i).COL_NAME );
END LOOP;
DBMS_SQL.CLOSE_CURSOR(cur);
END;

picking 1 column from 2 tables and comparing them

In my Oracle database there are two tables which are TEMP_HR and PAY_SLIP_APR_16. Both of them have a common column named EMP_ID. TEMP_HR has over 10,000 records and PAY_SLIP_APR_16 has around 6,000 records. I want to know how many EMP_ID of PAY_SLIP_APR_16 is matched with TEMP_HR. If any ID doesn't match then print it. And here is my simple approach but I think its a very bad approach. So any faster method?
DECLARE
INPUT_EMP_NO VARCHAR2(13 BYTE);
INPUT_EMP_ID VARCHAR2(13 BYTE);
ROW_COUNT_1 NUMBER(6,0);
ROW_COUNT_2 NUMBER(6,0);
MATCHED_ID NUMBER;
UNMATCHED_ID NUMBER;
BEGIN
ROW_COUNT_1:=0;
ROW_COUNT_2:=0;
MATCHED_ID:=0;
UNMATCHED_ID:=0;
SELECT COUNT(*) INTO ROW_COUNT_1 FROM PAY_SLIP_APR_16;
SELECT COUNT(*) INTO ROW_COUNT_2 FROM TEMP_HR;
FOR A IN 1..ROW_COUNT_1 LOOP
BEGIN
SELECT EMP_ID INTO INPUT_EMP_ID FROM (SELECT EMP_ID, ROWNUM AS RN FROM PAY_SLIP_APR_16) WHERE RN=A;
FOR B IN 1..ROW_COUNT_2 LOOP
SELECT EMP_NO INTO INPUT_EMP_NO FROM (SELECT EMP_NO, ROWNUM AS RON FROM TEMP_HR) WHERE RON=B;
IF(INPUT_EMP_ID=INPUT_EMP_NO)THEN
MATCHED_ID:=MATCHED_ID+1;
EXIT;
ELSE
CONTINUE;
END IF;
END LOOP;
UNMATCHED_ID:=UNMATCHED_ID+1;
DBMS_OUTPUT.PUT_LINE(INPUT_EMP_ID);
EXCEPTION
WHEN OTHERS THEN
DBMS_OUTPUT.PUT_LINE(INPUT_EMP_ID||' -> '||SQLERRM);
END;
END LOOP;
DBMS_OUTPUT.PUT_LINE('MATCHED -> '||MATCHED_ID);
DBMS_OUTPUT.PUT_LINE('UNMATCHED -> '||UNMATCHED_ID);
END;
Use an outer join filtering for missed joins:
select p.*
from PAY_SLIP_APR_16 p
left join TEMP_HR t on t.EMP_ID = p.EMP_ID
where t.EMP_ID is null
An index on TEMP_HR(EMP_ID) will make this query fly.
You should use SQL sets!
To check which EMP_ID isn't in TEMP_HR you may try this:
select EMP_ID FROM PAY_SLIP_APR_16
where EMP_ID not in (select EMP_NO from TEMP_HR);
Then the other way around:
select EMP_NO FROM TEMP_HR
where EMP_NO not in (select EMP_ID from PAY_SLIP_APR_16);

Resources