How would I display all records in the database using the items table? My current query displays the information for item 894. I attempted using a loop, but no luck.
I have two tables, inventory and itemid. Where itemid has the item number and the description, and the inventory table has the items' information, such as size, color, price, and quantity on hand.
set serveroutput on
DECLARE
current_item number(8);
totalvalue number(8,2);
description varchar2(50);
item_id number(3);
CURSOR Inventory_Info IS
SELECT
itemsize
,color
,curr_price
,qoh
,curr_price*qoh as Total
FROM inventory
WHERE itemid=Current_item;
BEGIN
current_item:=894;
totalvAlue:=0;
SELECT
itemdesc, itemid
INTO description, item_id
FROM item
WHERE itemid=current_item;
DBMS_OUTPUT.PUT_LINE('Item ID: ' || TO_CHAR(item_id) || ' Item Description: ' || description);
OPEN Inventory_Info;
LOOP
Fetch Inventory_Info INTO Inventory_rocord;
EXIT WHEN Inventory_Info%NOTFOUND;
DBMS_OUTPUT.PUT_LINE('Size: ' || Inventory_record.itemsize);
DBMS_OUTPUT.PUT_LINE('Color: ' || Inventory_record.color);
DBMS_OUTPUT.PUT_LINE('Price: ' || Inventory_record.curr_price);
DBMS_OUTPUT.PUT_LINE('QOH: ' || Inventory_record.qoh);
DBMS_OUTPUT.PUT_LINE('Value: ' || Inventory_record.total);
TotalValue:=TotalValue + Inventory_record.total;
End Loop;
DBMS_OUTPUT.PUT_LINE('TOTAL VALUE: ' || TotalValue);
Close Inventory_Info;
EXCEPTION
WHEN NO_DATA_FOUND THEN
DBMS_OUTPUT.PUT_LINE('No inventory for Item No. '|| current_item);
WHEN OTHERS THEN
DBMS_OUTPUT.PUT_LINE('Error Message: '|| SQLERRM);
END;
If we for a moment forget about the formatting this could be done much simpler with a cursor for loop.
set serveroutput ON
DECLARE
BEGIN
FOR item_rec IN (SELECT itemdesc, itemid
FROM item
) LOOP
DBMS_OUTPUT.PUT_LINE('Item ID: ' || TO_CHAR(item_rec.itemid)
|| ' Item Description: ' || item_rec.itemdesc);
FOR Inventory_record IN (SELECT itemsize
, color
, curr_price
, qoh
, curr_price*qoh AS Total
FROM inventory
WHERE itemid = item_rec.itemid
) LOOP
DBMS_OUTPUT.PUT_LINE('Size: ' || Inventory_record.itemsize);
DBMS_OUTPUT.PUT_LINE('Color: ' || Inventory_record.color);
DBMS_OUTPUT.PUT_LINE('Price: ' || Inventory_record.curr_price);
DBMS_OUTPUT.PUT_LINE('QOH: ' || Inventory_record.qoh);
DBMS_OUTPUT.PUT_LINE('Value: ' || Inventory_record.total);
TotalValue:= TotalValue + Inventory_record.total;
END LOOP;
END LOOP;
END;
You're using a separate SELECT..INTO to fetch the itemid, but you assign the itemid to a single value and don't change that.
Looking at your queries, you can move the itemid fetch into the cursor & join the 2 tables. You fetch the items into inventory_record, but I don't see the definition/declaration of that anywhere.
Here I declare a record type variable consisting of what you're fetching, open the cursor, fetch the details into that cursor. Since there's no explicit where condition, it will perform an inner join between item & inventory tables & fetch all rows matching the join condition.
set serveroutput ON
DECLARE
TYPE inventory_rec IS RECORD (
itemid item.itemid%TYPE,
itemdesc item.itemdesc%TYPE,
itemsize inventory.itemsize%TYPE,
color inventory.color%TYPE,
curr_price inventory.curr_price%TYPE,
qoh inventory.qoh %TYPE,
total inventory.curr_price%TYPE); -- record type to hold what cursor will be fetching
inventory_record INVENTORY_REC;
current_item NUMBER(8);
totalvalue NUMBER(8, 2);
description VARCHAR2(50);
item_id NUMBER(3);
CURSOR inventory_info IS
SELECT itemid,
itemdesc,
itemsize,
color,
curr_price,
qoh,
curr_price * qoh AS Total
FROM inventory
join item USING (itemid);
-- join item & inventory, so that it shows listings for all items present in inventory
BEGIN
OPEN inventory_info;
LOOP
FETCH inventory_info INTO inventory_record;
EXIT WHEN inventory_info%NOTFOUND;
dbms_output.Put_line('Current item: '
|| inventory_record.itemdesc);
dbms_output.Put_line('Size: '
|| inventory_record.itemsize);
dbms_output.Put_line('Color: '
|| inventory_record.color);
dbms_output.Put_line('Price: '
|| inventory_record.curr_price);
dbms_output.Put_line('QOH: '
|| inventory_record.qoh);
dbms_output.Put_line('Value: '
|| inventory_record.total);
totalvalue := totalvalue + inventory_record.total;
END LOOP;
dbms_output.Put_line('TOTAL VALUE: '
|| totalvalue);
CLOSE inventory_info;
EXCEPTION
WHEN no_data_found THEN
dbms_output.Put_line('No inventory for Item No. '
|| current_item);
WHEN OTHERS THEN
dbms_output.Put_line('Error Message: '
|| SQLERRM);
END;
Don't use nested look-ups, use joins. Databases are really good at joins, and the performance of set operations is a lot a better than row-by-row processing.
Also, you don't need to declare cursors and variables in most situations. Use a cursor loop and let Oracle do the heavy lifting for you.
set serveroutput on
DECLARE
totalvalue number(8,2);
BEGIN
totalvAlue:=0;
FOR inv_itm_rec IN (
SELECT itm.itemid
, itm.itemdesc
, inv.itemsize
, inv.color
,inv.curr_price
,inv.qoh
,inv.curr_price*inv.qoh as Total
FROM inventory inv
JOIN item itm
ON itm.itemid=inv.itemid
)
LOOP
DBMS_OUTPUT.PUT_LINE('ItemId: ' || inv_itm_rec.itemid);
DBMS_OUTPUT.PUT_LINE('Description: ' || inv_itm_rec.itemdesc);
DBMS_OUTPUT.PUT_LINE('Size: ' || inv_itm_rec.itemsize);
DBMS_OUTPUT.PUT_LINE('Color: ' || inv_itm_rec.color);
DBMS_OUTPUT.PUT_LINE('Price: ' || inv_itm_rec.curr_price);
DBMS_OUTPUT.PUT_LINE('QOH: ' || inv_itm_rec.qoh);
DBMS_OUTPUT.PUT_LINE('Value: ' || inv_itm_rec.total);
TotalValue:=TotalValue + inv_itm_rec.total;
END LOOP;
DBMS_OUTPUT.PUT_LINE('TOTAL VALUE: ' || TotalValue);
END;
Note, this solution assumes that evry ITEM does have a matching INVENTORY record. It would be a rum old warehouse application if the data model allowed anything else to be the case.
If it's just a report(it seems to be). Consider using sql*plus report formatting commands to display the output of a query in a way you want it to be displayed.
Here is an example:
SQL> create table Items(
2 i_num number,
3 i_descr varchar2(101),
4 i_size varchar2(3),
5 i_price number,
6 i_qoh number
7 );
Table created
SQL> create table Inventories(
2 id number,
3 i_num number
4 );
Table created
SQL> insert into items(i_num,i_size,i_price,i_qoh,i_descr)
2 select 1, 'S', 123, 25, 'Item_1' from dual union all
3 select 2, 'L', 424, 12, 'Item_1' from dual union all
4 select 4, 'S', 45, 54, 'Item_4' from dual union all
5 select 5, 'S', 78, 54, 'Item_4' from dual union all
6 select 6, 'S', 123, 22, 'Item_5' from dual union all
7 select 7, 'S', 127, 65, 'Item_5' from dual
8 ;
6 rows inserted
SQL> commit;
Commit complete
SQL> insert into inventories
2 select i_num, i_num
3 from items;
6 rows inserted
SQL> commit;
Commit complete
And now our report:
SQL> column i_descr format a10
SQL> column i_size format a3
SQL> column i_price format 99999
SQL> column i_qoh format 99999
SQL> column value format 99999
SQL> break on i_descr skip 2
SQL> compute sum label 'Total: ' of value on i_descr;
SQL> select itm.i_descr
2 , itm.i_size
3 , itm.i_price
4 , itm.i_qoh
5 , sum(i_price*i_qoh) Value
6 from inventories inv
7 join items itm on (inv.i_num = itm.i_num)
8 group by itm.i_num
9 , itm.i_descr
10 , itm.i_size
11 , itm.i_price
12 , itm.i_qoh
13 order by i_descr, i_price;
I_DESCR I_S I_PRICE I_QOH VALUE
---------- --- ---------- ------ ------
Item_1 S 123 25 3075
L 424 12 5088
********** ------
Total: 8163
Item_4 L 45 54 2430
S 78 54 4212
********** ------
Total: 6642
Item_5 S 123 22 2706
L 127 65 8255
********** ------
Total: 10961
6 rows selected.
Related
I would like to replace every string are null by 'n' and every number by 0.
Is there a way to do that?
With a polymorphic table function I can select all the columns of a certain type but I can't modify the value of the columns.
Yes, it is possible with PTF also. You may modify columns in case you've set pass_through to false for the column in the describe method (to drop it) and copy it into new_columns parameter of the describe.
Below is the code:
create package pkg_nvl as
/*Package to implement PTF*/
function describe(
tab in out dbms_tf.table_t
) return dbms_tf.describe_t
;
procedure fetch_rows;
end pkg_nvl;
/
create package body pkg_nvl as
function describe(
tab in out dbms_tf.table_t
) return dbms_tf.describe_t
as
modif_cols dbms_tf.columns_new_t;
new_col_cnt pls_integer := 0;
begin
/*Mark input columns as used and as modifiable for subsequent row processing*/
for i in 1..tab.column.count loop
if tab.column(i).description.type in (
dbms_tf.type_number,
dbms_tf.type_varchar2
) then
/*Modifiable*/
tab.column(i).pass_through := FALSE;
/*Used in the PTF context*/
tab.column(i).for_read := TRUE;
/* Propagate column to the modified*/
modif_cols(new_col_cnt) := tab.column(i).description;
new_col_cnt := new_col_cnt + 1;
end if;
end loop;
/*Return the list of modified cols*/
return dbms_tf.describe_t(
new_columns => modif_cols
);
end;
procedure fetch_rows
/*Process rowset and replace nulls*/
as
rowset dbms_tf.row_set_t;
num_rows pls_integer;
in_col_vc2 dbms_tf.tab_varchar2_t;
in_col_num dbms_tf.tab_number_t;
new_col_vc2 dbms_tf.tab_varchar2_t;
new_col_num dbms_tf.tab_number_t;
begin
/*Get rows*/
dbms_tf.get_row_set(
rowset => rowset,
row_count => num_rows
);
for col_num in 1..rowset.count() loop
/*Loop through the columns*/
for rn in 1..num_rows loop
/*Calculate new values in the same row*/
/*Get column by index and nvl the value for return column*/
if rowset(col_num).description.type = dbms_tf.type_number then
dbms_tf.get_col(
columnid => col_num,
collection => in_col_num
);
new_col_num(rn) := nvl(in_col_num(rn), 0);
elsif rowset(col_num).description.type = dbms_tf.type_varchar2 then
dbms_tf.get_col(
columnid => col_num,
collection => in_col_vc2
);
new_col_vc2(rn) := nvl(in_col_vc2(rn), 'n');
end if;
end loop;
/*Put the modified column to the result*/
if rowset(col_num).description.type = dbms_tf.type_number then
dbms_tf.put_col(
columnid => col_num,
collection => new_col_num
);
elsif rowset(col_num).description.type = dbms_tf.type_varchar2 then
dbms_tf.put_col(
columnid => col_num,
collection => new_col_vc2
);
end if;
end loop;
end;
end pkg_nvl;
/
create function f_replace_nulls(tab in table)
/*Function to replace nulls using PTF*/
return table pipelined
row polymorphic using pkg_nvl;
/
with a as (
select
1 as id, 'q' as val_vc2, 1 as val_num
from dual
union all
select
2 as id, '' as val_vc2, null as val_num
from dual
union all
select
3 as id, ' ' as val_vc2, 0 as val_num
from dual
)
select
id
, a.val_num
, a.val_vc2
, n.val_num as val_num_repl
, n.val_vc2 as val_vc2_repl
from a
join f_replace_nulls(a) n
using(id)
ID | VAL_NUM | VAL_VC2 | VAL_NUM_REPL | VAL_VC2_REPL
-: | ------: | :------ | -----------: | :-----------
3 | 0 | | 0 |
2 | null | null | 0 | n
1 | 1 | q | 1 | q
db<>fiddle here
Use COALESCE or NVL and list the columns you want to apply them to:
SELECT COALESCE(col1, 'n') AS col1,
COALESCE(col2, 0) AS col2,
COALESCE(col3, 'n') AS col3
FROM table_name;
The way I understood the question, you actually want to modify table's contents. If that's so, you'll need dynamic SQL.
Here's an example; sample data first, with some numeric and character columns having NULL values:
SQL> desc test
Name Null? Type
----------------------------------------- -------- ----------------------------
ID NUMBER
NAME VARCHAR2(20)
SALARY NUMBER
SQL> select * from test order by id;
ID NAME SALARY
---------- ---------- ----------
1 Little 100
2 200
3 Foot 0
4 0
Procedure reads USER_TAB_COLUMNS, checking desired data types (you can add some more, if you want), composes the update statement and executes it:
SQL> declare
2 l_str varchar2(1000);
3 begin
4 for cur_r in (select column_name, data_type
5 from user_tab_columns
6 where table_name = 'TEST'
7 and data_type in ('CHAR', 'VARCHAR2')
8 )
9 loop
10 l_str := 'update test set ' ||
11 cur_r.column_name || ' = nvl(' || cur_r.column_name ||', ''n'')';
12 execute immediate l_str;
13 end loop;
14
15 --
16
17 for cur_r in (select column_name, data_type
18 from user_tab_columns
19 where table_name = 'TEST'
20 and data_type in ('NUMBER')
21 )
22 loop
23 l_str := 'update test set ' ||
24 cur_r.column_name || ' = nvl(' || cur_r.column_name ||', 0)';
25 execute immediate l_str;
26 end loop;
27 end;
28 /
PL/SQL procedure successfully completed.
Result:
SQL> select * from test order by id;
ID NAME SALARY
---------- ---------- ----------
1 Little 100
2 n 200
3 Foot 0
4 n 0
SQL>
You can create a table macro to intercept the columns and replace them with nvl if they're a number or varchar2:
create or replace function replace_nulls (
tab dbms_tf.table_t
)
return clob sql_macro as
stmt clob := 'select ';
begin
for i in 1..tab.column.count loop
if tab.column(i).description.type = dbms_tf.type_number
then
stmt := stmt || ' nvl ( ' ||
tab.column(i).description.name || ', 0 ) ' ||
tab.column(i).description.name || ',';
elsif tab.column(i).description.type = dbms_tf.type_varchar2
then
stmt := stmt || ' nvl ( ' ||
tab.column(i).description.name || ', ''n'' ) ' ||
tab.column(i).description.name || ',';
else
stmt := stmt || tab.column(i).description.name || ',';
end if;
end loop;
stmt := rtrim ( stmt, ',' ) || ' from tab';
return stmt;
end;
/
with a (
aa1,aa2,aa3
) as (
select 1, '2hhhh', sysdate from dual
union all
select null, null, null from dual
)
select * from replace_nulls(a);
AA1 AA2 AA3
---------- ----- -----------------
1 2hhhh 27-JUN-2022 13:17
0 n <null>
Need some advice here. I have two tables: 1) Car owners; 2) Cars. With cursor loop I need to count how many cars owns each person. I don't understand how do you write the select code when two tables are involved.
CREATE TABLE car_owner(
pnr VARCHAR2(13) PRIMARY KEY,
fname VARCHAR2(20);
CREATE TABLE car(
regnr VARCHAR2(6) PRIMARY KEY,
pnr REFERENCES car_owner(pnr);
INSERT INTO car_owner VALUES('19490321','anna');
INSERT INTO car_owner VALUES('19540201','tomas');
INSERT INTO car_owner VALUES('19650823','roger');
INSERT INTO car VALUES('ase456','19490321');
INSERT INTO car VALUES('ptg889','19490321');
INSERT INTO car VALUES('bon666','19650823');
declare
cursor c_cars is select pnr, fnamn, count(amount_of_cars(pnr))
from car_owner, car;
v_pnr car_owner.pnr%type;
v_fnamn car_owner.fnamn%type;
begin
if not c_cars %isopen then
open c_cars;
end if;
loop
fetch c_cars
into v_pnr, v_fnamn;
exit when c_cars %notfound;
dbms_output.put_line(v_pnr || ', ' || v_fnamn || ', ' ||
', owns: ' || amount_of_cars || ' cars');
end loop;
close c_cars;
end;
So the answer should look like this:
19490321, anna, owns: 2 cars
19540201, tomas, owns: 1 car
19650823, roger, owns: 0 cars
Join those tables. As you're aggregating something, all non-aggregated columns should be part of the group by clause.
Also, consider using a cursor FOR loop as it is way simpler (Oracle does all the dirty job for you - you don't have to declare variable(s), open the cursor, worry about exiting the loop, close the cursor).
SQL> DECLARE
2 CURSOR c_cars IS
3 SELECT o.pnr, o.fname, COUNT (*) cnt
4 FROM car_owner o JOIN car c ON c.pnr = o.pnr
5 GROUP BY o.pnr, o.fname;
6 BEGIN
7 FOR cur_r IN ( SELECT o.pnr, o.fname, COUNT (*) cnt
8 FROM car_owner o JOIN car c ON c.pnr = o.pnr
9 GROUP BY o.pnr, o.fname)
10 LOOP
11 DBMS_OUTPUT.put_line (
12 cur_r.pnr
13 || ', '
14 || cur_r.fname
15 || ', '
16 || ', owns: '
17 || cur_r.cnt
18 || ' car(s)');
19 END LOOP;
20 END;
21 /
19650823, roger, , owns: 1 car(s)
19490321, anna, , owns: 2 car(s)
PL/SQL procedure successfully completed.
SQL>
#C:\Users\4\Desktop\dbdrop;
#C:\Users\4\Desktop\dbcreate;
SET SERVEROUTPUT ON;
DECLARE
ORDER_ID ORDERS.ODID%TYPE;
COMPANY_NAME ORDERS.CNAME%TYPE;
ORDER_DATE ORDERS.ODATE%TYPE;
CURSOR ord_cursor IS
SELECT ODID, CNAME, ODATE
FROM ORDERS
WHERE ODER_DATE< TRUNC(SYSDATE);
FETCH FIRST 5 ROWS ONLY;
BEGIN
OPEN ord_cursor;
LOOP
FETCH ord_cursor into ORDER_ID, COMPANY_NAME, ORDER_DATE;
DBMS_OUTPUT.PUT_LINE(' ');
DBMS_OUTPUT.PUT_LINE('ODER ID: '|| TO_CHAR(Order_Id));
DBMS_OUTPUT.PUT_LINE( 'ODER DATE: ' || ORDER_DATE );
DBMS_OUTPUT.PUT_LINE('COMPANY NAME: '|| COMPANY_NAME );
DBMS_OUTPUT.PUT_LINE( '------------');
DBMS_OUTPUT.PUT_LINE( '------------');
IF ord_cursor%NOTFOUND THEN
EXIT;
END IF;
END LOOP;
CLOSE ord_cursor;
EXCEPTION
WHEN OTHERS THEN
DBMS_OUTPUT.PUT_LINE(SQLERRM);
END;
Error report -
ORA-06550: line 9, column 12:
PLS-00103: Encountered the symbol "FETCH" when expecting one of the following:
begin function pragma procedure subtype type
current cursor delete
exists prior
06550. 00000 - "line %s, column %s:\n%s"
*Cause: Usually a PL/SQL compilation error.
*Action:
This:
WHERE ODER_DATE< TRUNC(SYSDATE);
FETCH FIRST 5 ROWS ONLY;
is wrong; either remove semi-colon in the first line (if your database supports FETCH clause), or entire second line.
Shorter version of your code is something like this (I don't have your tables so I fabricated one, based on Scott's EMP table):
SQL> create table orders as
2 select empno ordid, ename cname, hiredate odate
3 from emp
4 where deptno = 10;
Table created.
Code itself:
SQL> set serveroutput on
SQL> begin
2 for cur_r in
3 (select ordid, cname, odate
4 from orders
5 where odate < trunc(sysdate)
6 and rownum <= 5
7 )
8 loop
9 dbms_output.put_line('------------');
10 dbms_output.put_line('Order ID = ' || cur_r.ordid);
11 dbms_output.put_line('Order date = ' || to_char(cur_r.odate, 'dd.mm.yyyy'));
12 dbms_output.put_line('Company = ' || cur_r.cname);
13 end loop;
14 end;
15 /
------------
Order ID = 7782
Order date = 09.06.1981
Company = CLARK
------------
Order ID = 7839
Order date = 17.11.1981
Company = KING
------------
Order ID = 7934
Order date = 23.01.1982
Company = MILLER
PL/SQL procedure successfully completed.
SQL>
Your query ends before the FETCH clause and that is the issue.
You can use simple FOR loop and string concatenation as follows:
BEGIN
FOR CUR IN (SELECT 'ODER ID: '|| TO_CHAR(ODID) || CHR(10)
|| 'ODER DATE: ' || ODATE || CHR(10)
|| 'COMPANY NAME: ' || CNAME || CHR(10)
|| '------------' AS STR
FROM ORDERS
WHERE ODER_DATE< TRUNC(SYSDATE)
FETCH FIRST 5 ROWS ONLY)
LOOP
DBMS_OUTPUT.PUT_LINE(CUR.STR);
END LOOP;
EXCEPTION
WHEN OTHERS THEN
DBMS_OUTPUT.PUT_LINE(SQLERRM);
END;
/
I need to know what columns of one table have only null values. I understand that I should do a loop in user_tab_columns. But how detect only columns with null value?
Thanks and sorry for my English
To perform a query where you don't know the column identifies in advance, you need to use dynamic SQL. Assuming you already know the table is not empty, you could do something like:
declare
l_count pls_integer;
begin
for r in (
select table_name, column_name
from user_tab_columns
where table_name = 'T42'
and nullable = 'Y'
)
loop
execute immediate 'select count(*) '
|| ' from "' || r.table_name || '"'
|| ' where "' || r.column_name || '" is not null'
into l_count;
if l_count = 0 then
dbms_output.put_line('Table ' || r.table_name
|| ' column ' || r.column_name || ' only has nulls');
end if;
end loop;
end;
/
Remember to set serveroutput on or your client's equivalent before executing.
The cursor gets the columns from the table which are declared as nullable (if they aren't, not much point checking them; though this won't catch explicit check constraints). For each column it builds a query to count the rows where that column is not null. If that count is zero then it didn't find any that are not null, therefore they all are. Again, assuming you know the table isn't empty before you start.
I've included the table name in the cursor select list and references so you only need to change the name in one place to search a different table, or you could use a variable for that name. Or check multiple tables at once by changing that filter.
You may get better performance by selecting a dummy value from any non-null row, with a rownum stop check - which means it will stop as soon as it finds a non-null value, rather than having to check every row to get an actual count:
declare
l_flag pls_integer;
begin
for r in (
select table_name, column_name
from user_tab_columns
where table_name = 'T42'
and nullable = 'Y'
)
loop
begin -- inner block to allow exception trapping within loop
execute immediate 'select 42 '
|| ' from "' || r.table_name || '"'
|| ' where "' || r.column_name || '" is not null'
|| ' and rownum < 2'
into l_flag;
-- if this foudn anything there is a non-null value
exception
when no_data_found then
dbms_output.put_line('Table ' || r.table_name
|| ' column ' || r.column_name || ' only has nulls');
end;
end loop;
end;
/
or you could do something similar with an exists() check.
If you don't know that the table has data then you can do a simple count(*) from the table before the loop to check if it is empty, and report that instead:
...
begin
if l_count = 0 then
dbms_output.put_line('Table is empty');
return;
end if;
...
Or you could combine it with the cursor query, but this would need some work if you wanted to check multiple tables at once as it would stop as soon as it found any empty one (have to leave you something to do... *8-)
declare
l_count_any pls_integer;
l_count_not_null pls_integer;
begin
for r in (
select table_name, column_name
from user_tab_columns
where table_name = 'T42'
and nullable = 'Y'
)
loop
execute immediate 'select count(*),'
|| ' count(case when "' || r.column_name || '" is not null then 1 end)'
|| ' from "' || r.table_name || '"'
into l_count_any, l_count_not_null;
if l_count_any = 0 then
dbms_output.put_line('Table ' || r.table_name || ' is empty');
exit; -- only report once
elsif l_count_not_null = 0 then
dbms_output.put_line('Table ' || r.table_name
|| ' column ' || r.column_name || ' only has nulls');
end if;
end loop;
end;
/
You could of course populate a collection or make it a pipelined function or whatever if you didn't want to reply on dbms_output, but I assume this is a one-off check so it is probably acceptable.
You can loop though your columns and count null rows. If it is same with your table count, then that column has only null values.
The first question is: one column with zero row could be regarded as only (null) value containing column. But it can remain your decision: the scripts below provide solutions to both ways. (In my opinion: no. The empty columns is not a column with only (null) value)
If you want to know the (null) values about one table you can it with count(column):
select count(column) from table
and when the count(column) = 0 then the column has only (null) value or has no value. (So, you cannot make a correct decision).
E.g. The following three tables (x, y and z) has the following columns:
select * from x;
N_X M_X
---------------
100 (null)
200 (null)
300 (null)
select * from y;
N_Y M_Y
---------------
101 (null)
202 (null)
303 apple
select * from z;
N_Z M_Z
---------------
The count() selects:
select count(n_x), count(m_x) from x;
COUNT(N_X) COUNT(M_X)
-----------------------
3 0
select count(n_y), count(m_y) from y;
COUNT(N_Y) COUNT(M_Y)
-----------------------
3 1
select count(n_z), count(m_Z) from z;
COUNT(N_Z) COUNT(M_Z)
-----------------------
0 0
As you can see, the difference between x and y is appears but you cannot decide that the table z has no rows or only full of (null) values.
The general solution:
I have separeted the schema and the db level but the basic idea is common:
Schema level: the current user’s table
DB level: all users or a chosen schema
The number of (null) in one columns:
all_tab_columns.num_nulls
(Or: user_tab_columns, num_nulls).
And we need the num_rows of the table:
all_all_tables.num_rows
(Or: user_all_tables.num_rows)
Where the num_nulls equals to num_rows there are only (null) values.
Firstly, you need to run the DBMS_STATS for refresh the statistics.
on database level:
exec DBMS_STATS.GATHER_DATABASE_STATS;
(it can use a lot of resources)
on schema level:
EXEC DBMS_STATS.gather_schema_stats('TRANEE',DBMS_STATS.AUTO_SAMPLE_SIZE); (owner = tranee)
-- column with zero row = column has only (null) values -> exclude num_nulls > 0 condition
-- column with zero row <> column has only (null) values -> include num_nulls > 0 condition
the scripts:
-- 1. current user
select
a.table_name,
a.column_name,
a.num_nulls,
b.num_rows
from user_tab_columns a, user_all_tables b
where a.table_name = b.table_name
and num_nulls = num_rows
and num_nulls > 0;
-- 2. chosen user / all user -> exclude the a.owner = 'TRANEE' condition
select
a.owner,
a.table_name,
a.column_name,
a.num_nulls,
b.num_rows
from all_tab_columns a, all_all_tables b
where a.owner = b.owner
and a.table_name = b.table_name
and a.owner = 'TRANEE'
and num_nulls = num_rows
and num_nulls > 0;
TABLE_NAME COLUMN_NAME NUM_NULLS NUM_ROWS
----------------------------------------------------
LEADERS COMM 4 4
EMP_ACTION ACTION 12 12
X M_X 3 3
These tables and columns have only (null) values in tranee schema.
I have a SQL statement with the PIVOT operation. The maximum number of columns I will have in PIVOT is 5, but I can have less, 4, 3, 2.
How can I read these columns in my cursor and assign (fetch .. into...) to the fixed variables, without occurring the error ORA-01007.
...
sql_stmt := 'select * from
(select codcoligada,
idprd,
codcfo,
valnegociado
from tcitmorcamento
where codcoligada = ' || p_codcoligada || '
and codcotacao = ' || '''' || p_codcotacao || '''' || ')
pivot
(
sum(valnegociado) for codcfo in (' || pivot_clause || ')
)';
ret := t_tab_sesa_cotacao();
open vCursor for sql_stmt;
loop
/* If my cursor returns less than 5 columns in PIVOT the error occurs ORA-01007 */
fetch vCursor into vCodColigada, vIdProduto, vValor01, vValor02, vValor03, vValor04, vValor05;
exit when vCursor%NOTFOUND;
ret.extend;
ret(ret.count) := t_type_sesa_cotacao(vCodColigada, vIdProduto, vValor01, vValor02, vValor03, vValor04, vValor05);
end loop;
close vCursor;
...
If I return less than 5 colums, I want to fill in the remainder of the variables with a value of 0 or null.
The variables vCodColigada and vIdProduto are identified, only PIVOT columns that can vary between 1 and 5 (vValor1, vValor2, vValor3, vValor4, vValor5)
Result PIVOT SQL:
CODCOLIGADA IDPRD '000125' '002272' '002342'
----------------- ---------------- ---------------- ---------------- ----------------
1 15464 45 300 30
1 18460 35 200 20
1 57492 20 100 10
-------- End of Data --------
Example:
If the cursor returns 3 values in the PIVOT (above), the variables vValor01, vValor02, vValor03 will be filled in, and the variables vValor04, vValor05 must be 0 or null.
Example:
CODCOLIGADA IDPRD VALOR01 VALOR02 VALOR03 VALOR04 VALOR05
----------------- ---------------- ---------------- ---------------- ---------------- ---------------- ----------------
1 15464 45 300 30 0 0
1 18460 35 200 20 0 0
1 57492 20 100 10 0 0
-------- End of Data --------
As I only have 3 columns in PIVOT, and I have 5 variables, the ORA-01007 error occurs in (fetch .. into ...).
Hope this below snippet helps. What the basic understanding is we need to add excess variable as null or blank to make this work.
SET serveroutput ON;
DECLARE
lv_pivot VARCHAR2(100):='''Y'',''N''';
TYPE lv
IS
RECORD
(
flg_y VARCHAR2(100),
flg_n VARCHAR2(100),
flg_e VARCHAR2(100));
type lv_tab
IS
TABLE OF lv;
lv_num lv_tab;
lv_check VARCHAR2(1000);
BEGIN
lv_check :=regexp_count(lv_pivot,',',1);
IF lv_check < 3 THEN
FOR z IN 1..(2-lv_check)
LOOP
lv_pivot:=lv_pivot||',null as val'||z;
END LOOP;
ELSE
lv_pivot:=lv_pivot;
END IF;
dbms_output.put_line(lv_pivot);
EXECUTE IMMEDIATE ' SELECT * FROM
(SELECT col1 FROM <table> )
pivot ( COUNT(1) FOR col1 IN ('||lv_pivot||'))' BULK COLLECT INTO lv_num;
END;
---------------------------Refactoring in Function------------------------------
--Create Object Type
CREATE OR REPLACE TYPE lv_obj IS OBJECT
(
flg_y VARCHAR2(100),
flg_n VARCHAR2(100),
flg_e VARCHAR2(100)
);
--Create Table Type
CREATE OR REPLACE TYPE lv_tab IS TABLE OF lv_obj;
--Create Function
CREATE OR REPLACE
FUNCTION test_func
RETURN lv_tab
AS
lv_pivot VARCHAR2(100):='''Y'',''N''';
lv_num lv_tab;
lv_check VARCHAR2(1000);
BEGIN
lv_check :=regexp_count(lv_pivot,',',1);
IF lv_check < 3 THEN
FOR z IN 1..(2-lv_check)
LOOP
lv_pivot:=lv_pivot||',null as val'||z;
END LOOP;
ELSE
lv_pivot:=lv_pivot;
END IF;
dbms_output.put_line(lv_pivot);
EXECUTE IMMEDIATE ' SELECT * FROM
(SELECT col1 FROM <table> )
pivot ( COUNT(1) FOR col1 IN ('||lv_pivot||'))' BULK COLLECT INTO lv_num;
RETURN lv_tab;
END;
-------------------------------------------------Output-----------------------------------------------
SELECT * FROM TABLE(test_func);
-------------------------------------------------------------------------------------------------------
Try padding the results in the query. Add a join to a select from dual statement that passes back all five columns.