I am trying to take some statistics against the Emp table, the create table & the rows inserted are given below. I am trying to develop a store procedure which will get all the columns for a particular table from oracle ALL_TAB_COLUMNS & I will generate the statistics.
The PL/SQL block of code given below is compiling but not returning any records when I run it. Can anyone please let me know where I might be getting wrong -
`is "distinct_cnt := 'SELECT COUNT(DISTINCT (' || table_rec.COLUMN_NAME || ')) FROM' || table_rec.TABLE_NAME;"`
a correct way of assigning the result to a variable.
create table emp( empno number(4,0),
ename varchar2(10),
job varchar2(9),
mgr number(4,0),
hiredate date,
sal number(7,2),
comm number(7,2),
deptno number(2,0)
)
insert into emp values( 7839, 'KING', 'PRESIDENT', null, to_date('17-11-1981','dd-mm-yyyy'), 5000, null, 10);
insert into emp values( 7698, 'BLAKE', 'MANAGER', 7839, to_date('1-5-1981','dd-mm-yyyy'), 2850, null, 30);
insert into emp values( 7782, 'CLARK', 'MANAGER', 7839, to_date('9-6-1981','dd-mm-yyyy'), 2450, null, 10);
insert into emp values( 7566, 'JONES', 'MANAGER', 7839, to_date('2-4-1981','dd-mm-yyyy'), 2975, null, 20);
insert into emp values( 7788, 'SCOTT', 'ANALYST', 7566, to_date('13-JUL-87','dd-mm-rr') - 85, 3000, null, 20);
insert into emp values( 7902, 'FORD', 'ANALYST', 7566, to_date('3-12-1981','dd-mm-yyyy'), 3000, null, 20);
insert into emp values( 7369, 'SMITH', 'CLERK', 7902, to_date('17-12-1980','dd-mm-yyyy'), 800, null, 20);
insert into emp values( 7499, 'ALLEN', 'SALESMAN', 7698, to_date('20-2-1981','dd-mm-yyyy'), 1600, 300, 30);
insert into emp values( 7521, 'WARD', 'SALESMAN', 7698, to_date('22-2-1981','dd-mm-yyyy'), 1250, 500, 30);
insert into emp values( 7654, 'MARTIN', 'SALESMAN', 7698, to_date('28-9-1981','dd-mm-yyyy'), 1250, 1400, 30);
insert into emp values( 7844, 'TURNER', 'SALESMAN', 7698, to_date('8-9-1981','dd-mm-yyyy'), 1500, 0, 30);
insert into emp values( 7876, 'ADAMS', 'CLERK', 7788, to_date('13-JUL-87', 'dd-mm-rr') - 51, 1100, null, 20);
insert into emp values( 7900, 'JAMES', 'CLERK', 7698, to_date('3-12-1981','dd-mm-yyyy'), 950, null, 30);
insert into emp values( 7934, 'MILLER', 'CLERK', 7782, to_date('23-1-1982','dd-mm-yyyy'), 1300, null, 10);
Commit;
create or replace
procedure p_profiling (V_tablename IN varchar2)
IS
cursor c1 is
select TABLE_NAME,
COLUMN_NAME
from
ALL_TAB_COLUMNS
where TABLE_NAME='V_tablename';
REC_CNT NUMBER;
distinct_cnt NUMBER;
is_valid NUMBER;
not_null NUMBER;
BEGIN
FOR table_rec in c1
LOOP
REC_CNT := 'SELECT COUNT(*) FROM' || table_rec.TABLE_NAME;
distinct_cnt := 'SELECT COUNT(DISTINCT (' || table_rec.COLUMN_NAME || ')) FROM' || table_rec.TABLE_NAME;
is_valid := 'SELECT COUNT(*) FROM '||table_rec.TABLE_NAME ||'WHERE'|| table_rec.COLUMN_NAME ||' IS NOT NULL
AND LENGTH('||table_rec.COLUMN_NAME||') = LENGTH(LTRIM(RTRIM('||table_rec.COLUMN_NAME||')))';
not_null := 'SELECT COUNT(*) FROM'|| table_rec.TABLE_NAME ||'WHERE '|| table_rec.COLUMN_NAME ||'IS NOT NULL';
DBMS_OUTPUT.PUT_LINE ('REC_CNT:'||REC_CNT||' '||'distinct_cnt:'||distinct_cnt||' '||'is_valid:'||is_valid ||' '||'TABLE_NAME'||table_rec.TABLE_NAME||' '||'COLUMN_NAME'||table_rec.COLUMN_NAME);
END LOOP;
END;
For executing the SQL statements created dynamically, you need to use EXECUTE IMMEDIATE:
create or replace
procedure p_profiling (V_tablename IN varchar2)
IS
cursor c1 is
select TABLE_NAME,
COLUMN_NAME
from
ALL_TAB_COLUMNS
where TABLE_NAME='V_tablename';
REC_CNT NUMBER;
distinct_cnt NUMBER;
is_valid NUMBER;
not_null NUMBER;
BEGIN
FOR table_rec in c1
LOOP
IF c1%ROWCOUNT = 1 THEN
EXECUTE IMMEDIATE 'SELECT COUNT(*) FROM' ||
table_rec.TABLE_NAME INTO REC_CNT;
END IF;
EXECUTE IMMEDIATE 'SELECT COUNT(DISTINCT (' ||
table_rec.COLUMN_NAME || ')) FROM' ||
table_rec.TABLE_NAME INTO distinct_cnt;
EXECUTE IMMEDIATE 'SELECT COUNT(*) FROM '||
table_rec.TABLE_NAME ||'WHERE'|| table_rec.COLUMN_NAME ||'
IS NOT NULL
AND LENGTH('||table_rec.COLUMN_NAME||') =
LENGTH(LTRIM(RTRIM ('||table_rec.COLUMN_NAME||')))' INTO is_valid;
EXECUTE IMMEDIATE 'SELECT COUNT(*) FROM'||
table_rec.TABLE_NAME ||'WHERE '|| table_rec.COLUMN_NAME
||'IS NOT NULL' INTO not_null;
DBMS_OUTPUT.PUT_LINE('REC_CNT:'||REC_CNT||' '||'distinct_cnt:'||
distinct_cnt||' '
||'is_valid:'||is_valid ||' '||'TABLE_NAME'||table_rec.TABLE_NAME||' '
||'COLUMN_NAME'||table_rec.COLUMN_NAME);
END LOOP;
END;
You need to use EXECUTE IMMEDIATE in order to execute these dynamics queries an also to obtain the data you are looking for.
Review this post: dynamic SELECT INTO clause in PL/SQL
Hope this help.
Related
I have problem with my dml trigger on oracle database.I want to launch trigger when i update first_name or last name on employees table in hr schema. During execution trigger i Have error ORA-00060: Please help. Idon't have any ideas how can i fix it.enter image description here
CREATE OR replace TRIGGER up_sal
BEFORE UPDATE OF first_name, last_name ON employees
FOR EACH ROW
DECLARE
PRAGMA autonomous_transaction;
var_sal employees.salary%TYPE;
var_avg NUMBER;
var_emp_id NUMBER;
BEGIN
SELECT salary
INTO var_sal
FROM employees
WHERE first_name = :NEW.first_name
OR last_name = :NEW.last_name;
SELECT Avg(salary)
INTO var_avg
FROM employees
WHERE department_id IN( :OLD.department_id );
IF var_sal < var_avg THEN
var_emp_id := :OLD.employee_id;
UPDATE employees
SET salary = var_avg * 1.1
WHERE employee_id = var_emp_id;
COMMIT;
END IF;
END;
You can use:
CREATE TRIGGER up_sal
BEFORE UPDATE OF first_name, last_name ON employees
FOR EACH ROW
DECLARE
PRAGMA AUTONOMOUS_TRANSACTION;
var_avg employees.salary%TYPE;
BEGIN
SELECT Avg(salary)
INTO var_avg
FROM employees
WHERE department_id IN( :OLD.department_id );
IF :NEW.salary < var_avg THEN
:NEW.salary := var_avg * 1.1;
END IF;
COMMIT;
END;
/
Which, for the sample data:
CREATE TABLE employees (
department_id NUMBER,
first_name VARCHAR2(20),
last_name VARCHAR2(20),
salary NUMBER
);
INSERT INTO employees (department_id, first_name, last_name, salary)
SELECT 1, 'Alice', 'Abbot', 90 FROM DUAL UNION ALL
SELECT 1, 'Betty', 'Baron', 95 FROM DUAL UNION ALL
SELECT 1, 'Carol', 'Count', 105 FROM DUAL UNION ALL
SELECT 1, 'Debra', 'Duke', 110 FROM DUAL;
Then if you do:
UPDATE employees
SET first_name = TRIM(first_name),
last_name = TRIM(last_name);
Then:
SELECT * FROM employees;
Outputs:
DEPARTMENT_ID
FIRST_NAME
LAST_NAME
SALARY
1
Alice
Abbot
110
1
Betty
Baron
110
1
Carol
Count
105
1
Debra
Duke
110
db<>fiddle here
I've created two tables: Employees and Departments
CREATE TABLE EMP
( emp_id number(3) PRIMARY KEY,
dept_id Number(3) NOT NULL,
emp_name Varchar2(50) NOT NULL,
address Varchar2(100),
phone Varchar2(20) NOT NULL,
salary Number(8,2) NOT NULL,
CONSTRAINT fk_DEPT FOREIGN KEY (dept_id) REFERENCES DEPT(DEPT_ID));
CREATE TABLE DEPT
( dept_id number(3) PRIMARY KEY,
dept_name varchar2(50) NOT NULL,
emp_cnt Number(3) NOT NULL)
I need to create trigger that changes value in DEPT.emp_cnt after inserting or deleting data in EMP table.
Here is my attempt
create or replace trigger add_emp_to_the_dep
after insert or delete on EMP
for each row
begin
update DEPT
set emp_cnt = :new.emp_id
where DEPT.dept_id = :new.dept_id;
if INSERTING then
emp_cnt += 1;
else DELETING then
emp_cnt -= 1;
end if;
end;
Wrong syntax; there's no such thing as emp_cnt += 1; in Oracle's PL/SQL.
Try something like this instead:
create or replace trigger add_emp_to_the_dep
after insert or delete on emp
for each row
begin
if inserting then
update dept set
emp_cnt = emp_cnt + 1
where dept_id = :new.dept_id;
elsif deleting then
update dept set
emp_cnt = emp_cnt - 1
where dept_id = :old.dept_id;
end if;
end;
/
You can use a compound trigger to collate the changes and make the minimum number of updates:
CREATE TRIGGER add_emp_to_the_dep
FOR INSERT OR UPDATE OR DELETE ON emp
COMPOUND TRIGGER
TYPE ids_type IS TABLE OF EMP.DEPT_ID%TYPE;
TYPE cnt_type IS TABLE OF PLS_INTEGER;
TYPE idx_type IS TABLE OF PLS_INTEGER INDEX BY PLS_INTEGER;
ids ids_type := ids_type();
cnts cnt_type := cnt_type();
idxs idx_type := idx_type();
PROCEDURE modify_dept_cnt (
id EMP.DEPT_ID%TYPE,
cnt PLS_INTEGER
)
IS
BEGIN
IF id IS NULL THEN
RETURN;
END IF;
IF NOT idxs.EXISTS(id) THEN
ids.EXTEND;
cnts.EXTEND;
ids(ids.COUNT) := id;
cnts(cnts.COUNT) := cnt;
idxs(id) := ids.COUNT;
ELSE
cnts(idxs(id)) := cnts(idxs(id)) + cnt;
END IF;
END modify_dept_cnt;
AFTER EACH ROW
IS
BEGIN
modify_dept_cnt(:NEW.DEPT_ID, 1);
modify_dept_cnt(:OLD.DEPT_ID, -1);
END AFTER EACH ROW;
AFTER STATEMENT
IS
BEGIN
FORALL i IN 1 .. ids.count
UPDATE dept
SET emp_cnt = emp_cnt + cnts(i)
WHERE dept_id = ids(i);
END AFTER STATEMENT;
END;
/
Then, if you do:
INSERT INTO emp (emp_id, dept_id, emp_name, phone, salary)
SELECT 1, 1, 'Alice', '0', 100 FROM DUAL UNION ALL
SELECT 2, 1, 'Betty', '1', 100 FROM DUAL UNION ALL
SELECT 3, 2, 'Carol', '2', 100 FROM DUAL UNION ALL
SELECT 4, 1, 'Debra', '3', 100 FROM DUAL UNION ALL
SELECT 5, 3, 'Emily', '4', 100 FROM DUAL UNION ALL
SELECT 6, 3, 'Fiona', '5', 100 FROM DUAL;
It will collate all the changes and UPDATE the DEPT table only 3 times, as employees for 3 unique DEPT_ID are added, rather than performing 6 updates, one for each inserted row.
db<>fiddle here
create or replace trigger add_emp_to_the_dep
after insert or delete on emp
for each row
begin
if inserting then
update dept set
emp_cnt = emp_cnt + 1
where dept_id = :new.dept_id;
elsif deleting then
update dept set
emp_cnt = emp_cnt - 1
where dept_id = :old.dept_id;
end if;
end;
I have a table named emp with name, id, salary as column names.
My procedure takes id as input and it can be more than one id also. like('1','2','3',...)
Required: For each and every id the values should be returned as XML.
Supoose: For id=1 and id=2 as input my output should be
<row>
<name>name1</name>
<salary>salary1</salary>
</row>
<row>
<name>name2</name>
<salary>salary2</salary>
</row>
I am able to run the code to separate id's and get the output. But I am getting stuck at sending values as xml.
Code i tried:
procedure get_details(P_ID varchar2) as
begin
select * from emp where id in (
select regexp_substr(P_ID,'[^,]+', 1, level) from dual
connect BY regexp_substr(P_ID, '[^,]+', 1, level)
is not null);
end get_values;
Any input will be appreciated.
Edit regarding solutions: Both the solutions provided in db-fiddle are working perfectly fine.
Solution 1:
CREATE PROCEDURE get_details(
P_IDs IN varchar2
) IS
v_clob CLOB;
BEGIN
SELECT XMLELEMENT(
"root",
XMLAGG(
XMLELEMENT(
"row",
XMLFOREST(
name AS "name",
salary AS "salary"
)
)
)
).getClobVal()
INTO v_clob
FROM emp
where p_ids LIKE '%''' || id || '''%';
DBMS_OUTPUT.PUT_LINE( v_clob );
end get_details;
/
BEGIN
DBMS_OUTPUT.PUT_LINE( 'Your output:' );
get_details( '''1'',''3''' );
END;
/
Solution 2:
create or replace procedure get_details(p_id varchar2)
as
lo_xml_output clob;
begin
select to_clob(xmltype(cursor(select *
from emp
where id in
(select regexp_substr(p_id,'[^,]+', 1, level)
from dual
connect by regexp_substr(P_ID, '[^,]+', 1,level) is
not null))))
into lo_xml_output from dual;
DBMS_OUTPUT.PUT_LINE( lo_xml_output );
end get_details;
/
BEGIN
DBMS_OUTPUT.PUT_LINE('');
get_details('1,3');
END;
/
You can use the XML functions:
CREATE PROCEDURE get_details(
P_IDs IN varchar2
) IS
v_clob CLOB;
BEGIN
SELECT XMLELEMENT(
"root",
XMLAGG(
XMLELEMENT(
"row",
XMLFOREST(
name AS "name",
salary AS "salary"
)
)
)
).getClobVal()
INTO v_clob
FROM emp
where p_ids LIKE '%''' || id || '''%';
DBMS_OUTPUT.PUT_LINE( v_clob );
end get_details;
/
So, for the test data:
CREATE TABLE emp ( id, name, salary ) AS
SELECT 1, 'name1', 1000 FROM DUAL UNION ALL
SELECT 2, 'name2', 2000 FROM DUAL UNION ALL
SELECT 3, 'name3', 3000 FROM DUAL
The anonymous block:
BEGIN
get_details( '''1'',''3''' );
END;
/
Outputs:
<root><row><name>name1</name><salary>1000</salary></row><row><name>name3</name><salary>3000</salary></row></root>
db<>fiddle here
Another way will be by using xmltype,you can pass a query as a cursor to xmltype.
create or replace procedure get_details(p_id varchar2)
as
lo_xml_output clob;
begin
select to_clob(xmltype(cursor(select *
from emp
where id in
(select regexp_substr(p_id,'[^,]+', 1, level)
from dual
connect by regexp_substr(P_ID, '[^,]+', 1,level) is
not null))))
into lo_xml_output from dual;
DBMS_OUTPUT.PUT_LINE( lo_xml_output );
end get_details;
/
-- test
BEGIN
DBMS_OUTPUT.PUT_LINE('');
get_details('1,3');
END;
/
Db<>fiddle for your reference
P.S. we can also usedbms_xmlgen.getxml but then we need to build the SQL dynamically before passing it to dbms_xmlgen.getxml. you can see in the fiddle as well.
Good day,
I am trying to capture date from a apex_item.text but its not working
my classic report table is
SELECT apex_item.checkbox2(1,productID,'class=indCheck')
|| apex_item.hidden(2,productname)
|| apex_item.hidden(3, productcode)
|| apex_item.hidden(5,amount)
as "SELECT",
PRODUCTID,
PRODUCTCODE,
PRODUCTNAME,
PRODUCTDESC,
CATEGORYCODE,
SERIALNUMBER,
UNITPRICE,
REORDERLEVEL,
DISCONTINUED,
UNITSINHAND,
STATUS,
LOCATION,
LOCATIONDESCRIPTION,
apex_item.text (20,amount) as amount
from PRODUCTS
my pl/sql consist of
begin
for idx in 1 .. apex_application.g_f01.count loop
if apex_application.g_f01(idx) is not null then
insert into pending
(products,employeename,department,dates ,amount
)
values
(apex_application.g_f02(idx),:app_user,:app_user,
sysdate,apex_application.g_f05(idx));
end if ;
end loop;
end;
everything is being captured except the apex_item.text (20,amount) as amount which is being reference with apex_application.g_f05(idx)
I did use the same code as you are using and it worked fine.
See my understanding.
Table 1:
DROP TABLE ASHISH_SAMPLE_EMP CASCADE CONSTRAINTS;
CREATE TABLE ASHISH_SAMPLE_EMP
(
EMPNO NUMBER,
ENAME VARCHAR2(20 BYTE),
SAL NUMBER,
LOC VARCHAR2(22 BYTE),
DEPT VARCHAR2(22 BYTE),
ACTIVE VARCHAR2(4 BYTE) DEFAULT 'N'
);
Sample data
SET DEFINE OFF;
Insert into ASHISH_SAMPLE_EMP
(EMPNO, ENAME, SAL, LOC, DEPT,
ACTIVE)
Values
(1, 'Sahay', 1000, 'Delhi', 'SOFTWARE',
'N');
Insert into ASHISH_SAMPLE_EMP
(EMPNO, ENAME, SAL, LOC, DEPT,
ACTIVE)
Values
(4, 'TEst', 555, 'Noida', 'DBA',
'N');
Insert into ASHISH_SAMPLE_EMP
(EMPNO, ENAME, SAL, LOC, DEPT,
ACTIVE)
Values
(7, 'TEst', 555, 'Noida', 'DBA',
'N');
Insert into ASHISH_SAMPLE_EMP
(EMPNO, ENAME, SAL, LOC, DEPT,
ACTIVE)
Values
(2, 'Ashish', 1000, 'Gurugram', 'IT2',
'N');
COMMIT;
Table 2:
CREATE TABLE ashish_apex_item_issue
(
empno NUMBER,
amount NUMBER
);
Report Query
select apex_item.checkbox2(1,empno,'class=indCheck') || apex_item.hidden(5,sal) as "SELECT",
EMPNO,
ENAME,
apex_item.text (20,SAL) as amount,
LOC,
DEPT,
ACTIVE
from ASHISH_SAMPLE_EMP
Save data Process
begin
for idx in 1 .. apex_application.g_f01.count loop
if apex_application.g_f01(idx) is not null then
insert into ashish_apex_item_issue
(empno,amount
)
values
(apex_application.g_f01(idx),apex_application.g_f05(idx));
end if ;
end loop;
end;
Please follow the below article
https://roelhartman.blogspot.com/2018/02/apexapplicationgf0x-array-processing-in.html
In the below PL/SQL program I have 2 cursors that are almost similar in functionality. I'm not sure, how I can retain the same functionality just by having one cursor and get rid of the get_all_employes cursor which doesn't have the where clause.
Thanks in advance for your time and help!
CREATE OR REPLACE PROCEDURE EMP_DETAILS(dept_id VARCHAR2) IS
CURSOR get_all_employes IS
SELECT EMP_ID, EMP_NAME, EMP_SAL, EMP_DEPT FROM EMP;
CURSOR get_employee_info IS
SELECT EMP_ID, EMP_NAME, EMP_SAL, EMP_DEPT FROM EMP WHERE EMP_DEPT = dept_id;
BEGIN
IF dept_id IS NULL THEN
FOR X IN get_all_employes;
DBMS_OUTPUT.PUT_LINE(X.EMP_ID || ' ' || X.EMP_NAME || ' ' || X.EMP_SAL || X.EMP_DEPT);
END LOOP;
ELSE
FOR Y IN get_all_employes;
DBMS_OUTPUT.PUT_LINE(Y.EMP_ID || ' ' || Y.EMP_NAME || ' ' || Y.EMP_SAL || Y.EMP_DEPT);
END LOOP;
END IF;
END EMP_DETAILS;
/
Use one cursor and include WHERE EMP_DEPT = dept_id OR dept_id IS NULL:
CREATE PROCEDURE EMP_DETAILS(
dept_id IN EMP.EMP_DEPT%TYPE
)
IS
CURSOR get_employee_info IS
SELECT EMP_ID,
EMP_NAME,
EMP_SAL,
EMP_DEPT
FROM EMP
WHERE EMP_DEPT = dept_id
OR dept_id IS NULL;
BEGIN
FOR Y IN get_employee_info LOOP
DBMS_OUTPUT.PUT_LINE(Y.EMP_ID || ' ' || Y.EMP_NAME || ' ' || Y.EMP_SAL || Y.EMP_DEPT);
END LOOP;
END EMP_DETAILS;
/
So, for the test data:
CREATE TABLE emp ( EMP_ID, EMP_NAME, EMP_SAL, EMP_DEPT ) AS
SELECT 1, 'a', 100, 1 FROM DUAL UNION ALL
SELECT 2, 'b', 200, 1 FROM DUAL UNION ALL
SELECT 3, 'c', 300, 2 FROM DUAL;
Then:
BEGIN
emp_details( NULL );
END;
/
Would output all the rows:
1 a 1001
2 b 2001
3 c 3002
and:
BEGIN
emp_details( 1 );
END;
/
would output only the rows for department 1:
1 a 1001
2 b 2001
db<>fiddle here
Try something like this (fyi your example code does not compile)
create or replace PROCEDURE EMP_DETAILS(dept_id VARCHAR2) IS
TYPE EmpCurTyp IS REF CURSOR;
emp_cv EmpCurTyp;
empRec EMP%ROWTYPE;
BEGIN
IF dept_id IS NULL THEN
open emp_cv for SELECT EMP_ID, EMP_NAME, EMP_SAL, EMP_DEPT FROM EMP;
ELSE
open emp_cv for SELECT EMP_ID, EMP_NAME, EMP_SAL, EMP_DEPT FROM EMP WHERE EMP_DEPT = dept_id;
END IF;
LOOP
FETCH emp_cv INTO empRec;
IF emp_cv%NOTFOUND
THEN
EXIT;
END IF;
DBMS_OUTPUT.PUT_LINE(empREc.EMP_ID || ' ' || empREc.EMP_NAME || ' ' || empREc.EMP_SAL || empREc.EMP_DEPT);
END LOOP;
CLOSE emp_cv;
END EMP_DETAILS;