View based on function, correct query treated as bad - oracle

This is code:
CREATE TABLE emp_where (where_clause VARCHAR2(4000));
INSERT INTO emp_where (where_clause)
VALUES ('first_name=''KING'' or department_id = 20');
commit;
CREATE OR REPLACE TYPE t_emp_rec AS OBJECT (
EMPLOYEE_ID NUMBER(4),
FIRST_NAME VARCHAR2(10),
JOB_ID VARCHAR2(9),
MANAGER_ID NUMBER(4),
HIRE_DATE DATE,
SALARY NUMBER(7,2),
DEPARTMENT_ID NUMBER(2)
);
/
CREATE OR REPLACE TYPE t_emp_tab AS TABLE OF t_emp_rec;
/
CREATE OR REPLACE FUNCTION emp_fn RETURN t_emp_tab
PIPELINED IS
l_sql VARCHAR2(32767);
l_where VARCHAR2(4000);
TYPE l_cur_type IS REF CURSOR;
l_cur l_cur_type;
l_rec employees%ROWTYPE;
BEGIN
SELECT where_clause INTO l_where FROM emp_where;
l_sql := 'SELECT * FROM employees WHERE ' || l_where;
OPEN l_cur FOR l_sql;
LOOP
FETCH l_cur
INTO l_rec;
EXIT WHEN l_cur%NOTFOUND;
PIPE ROW(t_emp_rec(EMPLOYEE_ID => l_rec.EMPLOYEE_ID
,FIRST_NAME => l_rec.FIRST_NAME
,JOB_ID => l_rec.JOB_ID
,MANAGER_ID => l_rec.MANAGER_ID
,hire_date => l_rec.hire_date
,SALARY => l_rec.SALARY
,DEPARTMENT_ID => l_rec.DEPARTMENT_ID));
END LOOP;
RETURN;
EXCEPTION
WHEN OTHERS THEN
raise_application_error(-20000, SQLERRM || chr(10) || l_sql);
END;
/
CREATE OR REPLACE VIEW emp_vw AS
UPDATE emp_where SET where_clause = 'EMPLOYEE_ID BETWEEN 100 and 200';
COMMIT;
SELECT * FROM emp_vw;
When I execute SELECT * FROM emp_vw; with this clause: EMPLOYEE_ID BETWEEN 100 and 200
Oracle gives me an error
ORA-20000: ORA-06502: PL/SQL: numeric or value error: number precision too large
SELECT * FROM employees WHERE EMPLOYEE_ID BETWEEN 100 and 200
But when I execute query itself (SELECT * FROM employees WHERE EMPLOYEE_ID BETWEEN 100 and 200) there is no error. Another scenario -
when clause is 'deparment_id = 20', executing a view is correct. But when I change '=' to '>' (department_id > 20) - numeric or value error: number precision too large.
Can someone explain me how this is happening?

If I run:
DESCRIBE employees
Then I get the output:
Name Null Type
-------------- -------- ------------
EMPLOYEE_ID NOT NULL NUMBER(6)
FIRST_NAME VARCHAR2(20)
LAST_NAME NOT NULL VARCHAR2(25)
EMAIL NOT NULL VARCHAR2(25)
PHONE_NUMBER VARCHAR2(20)
HIRE_DATE NOT NULL DATE
JOB_ID NOT NULL VARCHAR2(10)
SALARY NUMBER(8,2)
COMMISSION_PCT NUMBER(2,2)
MANAGER_ID NUMBER(6)
DEPARTMENT_ID NUMBER(4)
If you compare it to your t_emp_rec object then you will see that most of the object attributes are a smaller size than the table columns.
Change the object to have the same sizes and it should work:
CREATE OR REPLACE TYPE t_emp_rec AS OBJECT (
EMPLOYEE_ID NUMBER(6),
FIRST_NAME VARCHAR2(20),
JOB_ID VARCHAR2(10),
MANAGER_ID NUMBER(6),
HIRE_DATE DATE,
SALARY NUMBER(8,2),
DEPARTMENT_ID NUMBER(4)
);
/

Related

Trigger DML in oracle database

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

how to validate employee age that it must me greater than 18 before inserting record(tiried using check constraint while creating table but not work)

--dept table
create table department(
dept_id number(5) ,
dept_name varchar2(100),
dept_city varchar2(100) ,
dept_country varchar2(100),
CONSTRAINT dept_pk PRIMARY KEY(dept_id)
);
insert into department( dept_id, dept_name, dept_city, dept_country )values(1,'hr','hyderabad','india');
insert into department( dept_id, dept_name, dept_city, dept_country )values(2,'marketing','banglore','india');
insert into department(dept_id, dept_name, dept_city, dept_country)values(3,'sales','dhaka','bangladesh');
create sequence s1
start with 1
increment by 1;
create table employee(
employee_id number(10) ,
employee_name varchar2(100) NOT NULL,
employee_age number(3) ,
employee_sal number(9,2),
dept_id number(5),
CONSTRAINT employee_pk PRIMARY KEY(employee_id),
constraint dept_fk foreign key(dept_id) references department(dept_id)
);
CREATE OR REPLACE TRIGGER trg_before_emp_insr
BEFORE INSERT
on employee_details
FOR EACH ROW
DECLARE
emp_age number;
BEGIN
IF (employee_age < 18) THEN
RAISE_APPLICATION_ERROR(-20000,'Employee age must be greater than or equal to 18.');
END IF;
END;
/
insert into employee(employee_id, employee_name, employee_age, employee_sal,dept_id )values(s1.nextval,'ravi',45,7333,1);
insert into employee(employee_id, employee_name, employee_age, employee_sal,dept_id )values(s1.nextval,'sai',74,4451,2);
insert into employee(employee_id, employee_name, employee_age, employee_sal,dept_id )values(s1.nextval,'chandu',35,9428,3);
insert into employee( employee_id,employee_name, employee_age, employee_sal,dept_id )values(s1.nextval,'raju',7,25422,2);
insert into employee( employee_id,employee_name, employee_age, employee_sal,dept_id )values(s1.nextval,'teja',36,7955,1);
select * from employee
You want to use the :NEW record to get the value from the row being inserted (and to use the EMPLOYEE table rather than EMPLOYEE_DETAILS):
CREATE OR REPLACE TRIGGER trg_before_emp_insr
BEFORE INSERT
on employee
FOR EACH ROW
BEGIN
IF (:NEW.employee_age < 18) THEN
RAISE_APPLICATION_ERROR(-20000,'Employee age must be greater than or equal to 18.');
END IF;
END;
/
db<>fiddle here
However, you should consider storing date of birth rather than age as tomorrow (or definitely next year) the age value will be outdated but storing the date of birth and calculating the age would not.
create table employee(
employee_id number(10) ,
employee_name varchar2(100) NOT NULL,
employee_dob DATE,
employee_sal number(9,2),
dept_id number(5),
CONSTRAINT employee_pk PRIMARY KEY(employee_id),
constraint dept_fk foreign key(dept_id) references department(dept_id)
);
CREATE OR REPLACE TRIGGER trg_before_emp_insr
BEFORE INSERT
on employee
FOR EACH ROW
BEGIN
IF :NEW.employee_dob > TRUNC(ADD_MONTHS(SYSDATE, -18*12)) THEN
RAISE_APPLICATION_ERROR(-20000,'Employee age must be greater than or equal to 18.');
END IF;
END;
/
db<>fiddle here

SQL Developer will not insert data

create or replace PROCEDURE ADD_TO_BLACKLIST(
P_EMPLOYEE_USERNAME IN VARCHAR2,
T_CURSOR OUT SYS_REFCURSOR
)
AS
BEGIN
DECLARE
E_COUNT PLS_INTEGER := 0;
BEGIN
SELECT COUNT(*) INTO E_COUNT FROM EXAMPLE_TABLE
WHERE UPPER(EMPLOYEE_USERNAME) LIKE UPPER(P_EMPLOYEE_USERNAME)||'%';
IF E_COUNT = 0 THEN
INSERT INTO EXAMPLE_TABLE
(employee_number, employee_username)
SELECT EMPLOYEE_NUMBER, EMAIL FROM EXAMPLE_VIEW
WHERE UPPER(EMAIL)=CONCAT(UPPER(P_EMPLOYEE_USERNAME), '#microsoft.com');
ELSE
UPDATE EXAMPLE_TABLE
SET (EMPLOYEE_NUMBER, EMPLOYEE_USERNAME) =
(SELECT EMPLOYEE_NUMBER, EMAIL FROM EXAMPLE_VIEW
WHERE UPPER(EMAIL) = CONCAT(UPPER(P_EMPLOYEE_USERNAME), '#microsoft.com'));
COMMIT;
END IF;
OPEN T_CURSOR For
SELECT * FROM EXAMPLE_VIEW
WHERE EMAIL LIKE CONCAT(UPPER(P_EMPLOYEE_USERNAME), '%');
END;
END ADD_TO_BLACKLIST;
This compiles, but when I try to test it with a valid P_EMPLOYEE_USERNAME (which I've confirmed to be in the EXAMPLE_VIEW), I do not see any data being inserted.
I am new to PLSQL and not sure how to figure out the value of E_COUNT
The Example_Table DDL is
CREATE TABLE "Example_Table"
( "EMPLOYEE_NUMBER" NUMBER NOT NULL ENABLE,
"EMPLOYEE_USERNAME" VARCHAR2(250 BYTE) NOT NULL ENABLE,
"ACCOUNT_STATUS" NUMBER DEFAULT 0,
"ACCOUNT_STATUS_LAST_UPDATE" TIMESTAMP (6) DEFAULT SYSDATE NOT NULL ENABLE,
CONSTRAINT "BOE_SAFEGAURD_PK" PRIMARY KEY ("EMPLOYEE_USERNAME"))
The issue is in below line,you are not converting the case after concatenation.please modify and try below,
WHERE UPPER(EMAIL) = UPPER(CONCAT(UPPER(P_EMPLOYEE_USERNAME), '#microsoft.com'));
EDIT : To prove the theory please find below the details.
I have tested this and it works,
DDL to create the tables:
CREATE TABLE Example_Table
( EMPLOYEE_NUMBER NUMBER NOT NULL ENABLE,
EMPLOYEE_USERNAME VARCHAR2(250 BYTE) NOT NULL ENABLE,
ACCOUNT_STATUS NUMBER DEFAULT 0,
ACCOUNT_STATUS_LAST_UPDATE TIMESTAMP (6) DEFAULT SYSDATE NOT NULL ENABLE,
CONSTRAINT BOE_SAFEGAURD_PK PRIMARY KEY (EMPLOYEE_USERNAME));
CREATE TABLE Example_view
( EMPLOYEE_NUMBER NUMBER NOT NULL ENABLE,
EMAIL VARCHAR2(250 BYTE) NOT NULL ENABLE,
ACCOUNT_STATUS NUMBER DEFAULT 0,
ACCOUNT_STATUS_LAST_UPDATE TIMESTAMP (6) DEFAULT SYSDATE NOT NULL ENABLE
);
DML to populate data to example_view that will be used for the test.
insert into example_view values(1,'Test#microsoft.com',1,sysdate);
Modified the procedure to add UPPER on the rightside of the join for both insert and update conditions and place the commit after end if.A good code should have only one commit and that should be at the end of execution before the exception block of main begin..end block.
create or replace PROCEDURE ADD_TO_BLACKLIST(
P_EMPLOYEE_USERNAME IN VARCHAR2,
T_CURSOR OUT SYS_REFCURSOR
)
AS
BEGIN
DECLARE E_COUNT PLS_INTEGER := 0;
BEGIN
SELECT COUNT(*) INTO E_COUNT FROM EXAMPLE_TABLE WHERE UPPER(EMPLOYEE_USERNAME) LIKE UPPER(P_EMPLOYEE_USERNAME)||'%';
IF E_COUNT = 0 THEN
INSERT INTO EXAMPLE_TABLE
(employee_number, employee_username)
SELECT EMPLOYEE_NUMBER, EMAIL FROM EXAMPLE_VIEW WHERE UPPER(EMAIL)=UPPER(CONCAT(UPPER(P_EMPLOYEE_USERNAME), '#microsoft.com'));
ELSE
UPDATE EXAMPLE_TABLE SET (EMPLOYEE_NUMBER, EMPLOYEE_USERNAME) = (SELECT EMPLOYEE_NUMBER, EMAIL FROM EXAMPLE_VIEW WHERE UPPER(EMAIL)=UPPER(CONCAT(UPPER(P_EMPLOYEE_USERNAME), '#microsoft.com')));
END IF;
COMMIT;
OPEN T_CURSOR For
SELECT * FROM EXAMPLE_VIEW WHERE EMAIL LIKE CONCAT(UPPER(P_EMPLOYEE_USERNAME), '%');
END;
END ADD_TO_BLACKLIST;
In an anonymous block invoked the procedure,
DECLARE
T_CURSOR SYS_REFCURSOR;
BEGIN
ADD_TO_BLACKLIST('test',T_CURSOR);
end;
Ran a query to check if records are inserted,
select * from example_table;
Output is below,
You just need a commit after IF-ELSE condition rather than inside it. I have updated your code along with some other minor updates -
create or replace PROCEDURE ADD_TO_BLACKLIST( P_EMPLOYEE_USERNAME IN VARCHAR2,
T_CURSOR OUT SYS_REFCURSOR
)
AS
E_COUNT PLS_INTEGER := 0;
BEGIN
SELECT COUNT(*)
INTO E_COUNT
FROM EXAMPLE_TABLE
WHERE UPPER(EMPLOYEE_USERNAME) LIKE UPPER(P_EMPLOYEE_USERNAME)||'%';
IF E_COUNT = 0 THEN
INSERT INTO EXAMPLE_TABLE
(employee_number, employee_username)
SELECT EMPLOYEE_NUMBER, EMAIL
FROM EXAMPLE_VIEW
WHERE UPPER(EMAIL) = CONCAT(UPPER(P_EMPLOYEE_USERNAME), '#microsoft.com');
ELSE
UPDATE EXAMPLE_TABLE
SET (EMPLOYEE_NUMBER, EMPLOYEE_USERNAME) = (SELECT EMPLOYEE_NUMBER, EMAIL
FROM EXAMPLE_VIEW
WHERE UPPER(EMAIL)=CONCAT(UPPER(P_EMPLOYEE_USERNAME), '#microsoft.com'));
END IF;
COMMIT;
OPEN T_CURSOR For
SELECT *
FROM EXAMPLE_VIEW
WHERE EMAIL LIKE CONCAT(UPPER(P_EMPLOYEE_USERNAME), '%');
END ADD_TO_BLACKLIST;

Writing a procedure with an array as out parameter

I can't create a PL/SQL stored procedure. For example returns all of the ename of employees in deptno 20.
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),
constraint pk_emp primary key (empno),
constraint fk_deptno foreign key (deptno) references dept (deptno)
);
This is what i manage to do but i can't seem to move/write it into a procedure.
declare
TYPE name_array IS TABLE OF emp.ename %type
INDEX BY BINARY_INTEGER;
enames name_array;
begin
update emp set empno = empno where deptno = 20
returning ename bulk collect into enames;
for i in 1..enames.count loop
dbms_output.put_line('ename : '||enames(i));
end loop;
end;
/
When returning a single object (which includes a table of values), I prefer functions over out parameters. Here is code that implements both.
drop table emp purge;
drop table dept purge;
CREATE TABLE dept
(
deptno INTEGER PRIMARY KEY
);
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)
, CONSTRAINT pk_emp PRIMARY KEY (empno)
, CONSTRAINT fk_deptno FOREIGN KEY (deptno) REFERENCES dept (deptno)
);
CREATE or replace PACKAGE emp_pkg
AS
TYPE name_array_t IS TABLE OF emp.ename%TYPE
INDEX BY BINARY_INTEGER;
FUNCTION upd (p_empno IN emp.empno%TYPE)
RETURN name_array_t;
PROCEDURE upd (
p_empno IN emp.empno%TYPE
, p_names OUT name_array_t
);
END emp_pkg;
CREATE or replace PACKAGE BODY emp_pkg
AS
FUNCTION upd (p_empno IN emp.empno%TYPE)
RETURN name_array_t
AS
l_names name_array_t;
BEGIN
UPDATE emp
SET empno = empno
WHERE deptno = 20
RETURNING ename
BULK COLLECT INTO l_names;
RETURN l_names;
END upd;
PROCEDURE upd (
p_empno IN emp.empno%TYPE
, p_names OUT name_array_t
)
AS
BEGIN
p_names := upd (p_empno);
END upd;
END emp_pkg;
This is a sample Stored Proc with return type as Array
create
procedure sample_proc(p_cust_id in number, p_customers out custarray)
as
my_cust custarray := custarray();

Oracle: Dynamically set all NOT NULL columns in a Table to allow NULL

I have a table with 75+ columns in it.
Almost all of the columns have the NOT NULL constraint.
If do a giant alter table modify statement (with every column in there), I get an error saying something along the lines of "You can't set this field to NULL, because it already is NULL"
I have to do this for several tables, and so would prefer to have a dynamic solution.
Can I dynamically find all of the columns that are NOT NULL, and set them to NULL?
I've seen several similar questions like this, but can't find a solution for Oracle SQL.
Modify all columns in a table to 'not null' no matter what
Here is a test table, with two not null columns, and one null column:
create table zzz_mark_test_me (
cust_id varchar2(20) not null,
cust_name varchar2(20) null,
cust_phone varchar2(20) not null
);
table ZZZ_MARK_TEST_ME created.
desc zzz_mark_test_me
Name Null Type
---------- -------- ------------
CUST_ID NOT NULL VARCHAR2(20)
CUST_NAME VARCHAR2(20)
CUST_PHONE NOT NULL VARCHAR2(20)
Now invoke this SQL:
select 'alter table ' || table_name ||
' modify (' || column_name || ' null );'
from user_tab_columns
where table_name='ZZZ_MARK_TEST_ME' and nullable='N'
order by column_id;
Which yields this:
alter table ZZZ_MARK_TEST_ME modify (CUST_ID null );
alter table ZZZ_MARK_TEST_ME modify (CUST_PHONE null );
Copy/paste the output into SQL*Plus etc. and invoke:
alter table ZZZ_MARK_TEST_ME modify (CUST_ID null );
table ZZZ_MARK_TEST_ME altered.
alter table ZZZ_MARK_TEST_ME modify (CUST_PHONE null );
table ZZZ_MARK_TEST_ME altered.
And now, no more NOT NULL:
desc zzz_mark_test_me
Name Null Type
---------- ---- ------------
CUST_ID VARCHAR2(20)
CUST_NAME VARCHAR2(20)
CUST_PHONE VARCHAR2(20)
You can use this procedure. You can first comment out line containing "execute immediate" to see what it executes, before running.
First parameter is schema_name, second is table_name.
create or replace procedure proc_null(t_owner in varchar2, t_name in varchar2) as
v_exec_imm varchar2(1000);
begin
for o in (select owner, column_name from all_tab_cols where owner=t_owner and table_name=t_name and nullable = 'N')
loop
v_exec_imm := 'alter table '||t_owner||'.'||t_name||' modify ('||o.column_name||' null) ';
execute immediate v_exec_imm; -- comment this line if You want, modifies table
dbms_output.put_line( v_exec_imm );
end loop;
end proc_null;

Resources