Hello, I have asked this question yesterday but thanks to your help, I did a lot of modifications so now I'm putting the new version of my code because it's getting better but still not working.
Let's assume i have the following table which is called payroll created by a user called PCM
EMP_ID DEPT TOTAL TAXES
-------------------- -------------------- ---------- ----------
E1 accounting 2400 100
E2 sales 2500 75
E3 research 3000 110
E4 operations 4200 120
E5 sales 4800 130
E6 sales 2500 75
E7 accounting 5200 140
E8 accounting 2700 105
Now what i want to achieve is the following: Anyone with the dept = accounting" can select all other rows with dept != accounting but anyone with dept != accounting can only view his/her record.
Now I'm connected as another user not the owner of the payroll table so I'm connected as a user called ANNE:
CREATE OR REPLACE CONTEXT payroll_ctx USING payroll_ctx_pkg;
CREATE OR REPLACE PACKAGE payroll_ctx_pkg IS
PROCEDURE set_dept;
END;
/
CREATE OR REPLACE PACKAGE BODY payroll_ctx_pkg IS
PROCEDURE set_dept
AS
v_dept varchar2(400);
BEGIN
SELECT dept INTO v_dept FROM PCM.PAYROLL
WHERE EMP_ID = SYS_CONTEXT('USERENV', 'SESSION_USER');
DBMS_SESSION.SET_CONTEXT('payroll_ctx', 'dept', v_dept);
EXCEPTION
WHEN NO_DATA_FOUND THEN
DBMS_SESSION.SET_CONTEXT('payroll_ctx', 'dept', 'NO');
END set_dept;
END;
/
Considering that the users who will try to access the table have the names of the emp_id column,now:
CREATE TRIGGER set_dept_trig AFTER LOGON ON DATABASE
BEGIN
ANNE.payroll_ctx_pkg.set_dept;
END;
/
Now the problem(i know it's wrong) but can't find the solution yet:
CREATE OR REPLACE PACKAGE security_package AS
FUNCTION sec_fun (D1 VARCHAR2, D2 VARCHAR2)
RETURN VARCHAR2;
END;
/
CREATE OR REPLACE PACKAGE BODY security_package AS
FUNCTION sec_fun (D1 VARCHAR2, D2 VARCHAR2)
RETURN VARCHAR2
IS
vv_dept varchar2(400);
V_ID varchar2(400);
begin
V_ID := SYS_CONTEXT('USERENV', 'SESSION_USER');
vv_dept := 'SYS_CONTEXT(''payroll_ctx'', ''dept'')';
if (vv_dept != 'accounting') then
RETURN 'EMP_ID = ' || CHR(39)||V_ID||CHR(39);
ELSE
RETURN 'DEPT != ' || CHR(39)||vv_dept||CHR(39);
END IF;
EXCEPTION
WHEN NO_DATA_FOUND
THEN
RETURN '1 = 0';
end sec_fun;
end security_package;
/
And Finally:
BEGIN
DBMS_RLS.ADD_POLICY (
object_schema => 'PCM',
object_name => 'PAYROLL',
policy_name => 'payroll_policy',
function_schema => 'ANNE',
policy_function => 'security_package.sec_fun',
statement_types => 'select');
END;
/
Now when a user E1 tries to select from payroll the output is:
EMP_ID DEPT TOTAL TAXES
-------------------- -------------------- ---------- ----------
E1 accounting 2400 100
what I'm doing wrong?? It's supposed to return all rows where dept != accounting
Thanks to your helping i finally managed to solve the problem, It was in the variable vv_dept when i made the following modifications it worked out:
CREATE OR REPLACE PACKAGE security_package AS
FUNCTION sec_fun (D1 VARCHAR2, D2 VARCHAR2)
RETURN VARCHAR2;
END;
/
CREATE OR REPLACE PACKAGE BODY security_package AS
FUNCTION sec_fun (D1 VARCHAR2, D2 VARCHAR2)
RETURN VARCHAR2
IS
V_ID varchar2(400);
begin
V_ID := SYS_CONTEXT('USERENV', 'SESSION_USER');
if (SYS_CONTEXT('payroll_ctx','dept') = 'accounting') then
RETURN 'DEPT != ' || CHR(39)||SYS_CONTEXT('payroll_ctx','dept')||CHR(39);
ELSE
RETURN 'EMP_ID = ' || CHR(39)||V_ID||CHR(39);
END IF;
EXCEPTION
WHEN NO_DATA_FOUND
THEN
RETURN '1 = 0';
end sec_fun;
end security_package;
/
Related
Here is my procedure where i want to insert values with the conditions like emp id start with sequence 131, phone number must be 13 digits, hire date not greater than sys date and salary not greater than 50,000. while executing im facing error . how to solve this.
create or replace procedure empinsert AS
cursor employee is select length(PHONE_NUMBER),HIRE_DATE,SAL from emp;
e_mobile_no emp.phone_number%type;
e_hire_date emp.hire_date%type;
e_salary emp.sal%type;
Begin
open employee ;
loop
fetch employee into e_mobile_no,e_hire_date,e_salary;
if ( e_mobile_no = 13 and e_hire_date < sysdate and e_salary <=50000 ) then
INSERT INTO EMP (EMP_ID,
EMP_NAME,
EMAIL,
PHONE_NUMBER,
HIRE_DATE,
JOB_ID,
SAL)
values(empinc.nextval, 'ramji','ramji#gmail.com','8975432109875','10/07/2021','JUN_TECH',40000);
else
exit;
end if;
end loop;
end;
/
I was trying to create a pipelined table function in PL/SQL but facing the below error. Is this an syntax error?
CREATE OR REPLACE FUNCTION FUNC_IDS(
IDS_IN IN VARCHAR2
) RETURN IDS_T
PIPELINED
IS
BEGIN
select * from dual;
return;
END func_ids;
Script Output:
Function FUNC_IDS compiled
LINE/COL ERROR
--------- -------------------------------------------------------------
0/0 PL/SQL: Compilation unit analysis terminated
3/10 PLS-00201: identifier 'IDS_T' must be declared
Errors: check compiler log
I missed to create the row and table types before creating the func. Have created them later as below and trying to create the function that gets input as a string of IDs and pipe out the individual IDs to another function.
CREATE TYPE TF_ROW AS OBJECT (ID NUMBER);
CREATE TYPE IDS_T IS TABLE OF TF_ROW;
create or replace function func_ids (ids_in in varchar2) return ids_t pipelined is
n_start pls_integer := 1;
n_end pls_integer := 1;
--
begin
loop
-- find the first and next comma in string
n_start := instr(ids_in, ',', n_start, 1);
n_end := instr(ids_in, ',', n_start, 2);
--
if (n_end <= 0) then
exit;
end if;
-- get the string and pipe it back out
pipe row (to_number(substr(ids_in, n_start+1, n_end - n_start - 1)));
-- ready for next one
n_start := n_end;
end loop;
return;
end func_ids;
Script Output:
Function FUNC_IDS compiled
LINE/COL ERROR
--------- -------------------------------------------------------------
22/5 PL/SQL: Statement ignored
22/15 PLS-00382: expression is of wrong type
Errors: check compiler log
What's wrong with the expression here?pipe row (to_number(substr(ids_in, n_start+1, n_end - n_start - 1)));
As you were told, IDS_T isn't declared.
Here's an example which works. See how I did it, do it yourself with your data.
Types:
SQL> create or replace type t_row is object (empno number, ename varchar2 (20));
2 /
Type created.
SQL> create or replace type t_tab is table of t_row;
2 /
Type created.
Function:
SQL> create or replace function func_ids (ids_in in varchar2)
2 return t_tab
3 pipelined
4 is
5 begin
6 for cur_r in (select empno, ename
7 from emp
8 where deptno = ids_in)
9 loop
10 pipe row (t_row (cur_r.empno, cur_r.ename));
11 end loop;
12
13 return;
14 end func_ids;
15 /
Function created.
Testing:
SQL> select * from table(func_ids (20));
EMPNO ENAME
---------- --------------------
7369 SMITH
7566 JONES
7788 SCOTT
7876 ADAMS
7902 FORD
SQL>
How to use the result returned by FUNC_IDS? I'm doing it using code you posted as a comment (the FUNC_ENAME function); I marked what you did wrong.
SQL> CREATE OR REPLACE FUNCTION func_ename (ids t_tab)
2 RETURN VARCHAR2
3 IS
4 l_tmp VARCHAR2 (100);
5 l_result VARCHAR2 (400);
6 BEGIN
7 l_result := ';';
8
9 FOR i IN ids.FIRST .. ids.LAST
10 LOOP
11 SELECT l.ename
12 INTO l_tmp
13 FROM emp l
14 WHERE l.empno = ids (i).empno; --> this line was wrong
15
16 l_result := l_result || l_tmp || ';';
17 END LOOP;
18
19 RETURN l_result;
20 END;
21 /
Function created.
SQL> SELECT func_ename (func_ids (10)) FROM DUAL;
FUNC_ENAME(FUNC_IDS(10))
------------------------------------------------------------------
;CLARK;KING;MILLER;
SQL>
I need to create a procedure that receives as a parameter a month and a year.
Inside the procedure I need to have a query to retrieve some values, that will have to take in account the parameters received.
create or replace procedure GET_REVS(MONTH in VARCHAR2,YEAR in varchar2) is
SELECT
*
FROM
REVENUS_TABLE
WHERE
Y_CODE IN ('YEAR')
AND M_CODE IN ()
Now, M_CODE should have the values since the start of the year until the month received by parameter.
Example if i receive in as parameter for the month a 4 i want my select to like this AND M_CODE IN ('1','2','3','4')
But if i receive MONTH = 3.. i need the select to have AND M_CODE IN ('1','2','3')
So what is the best way to do the procedure in order to be able to do that?
Thanks a lot
You could cast both M_CODE and MONTH as numbers and use BETWEEN operator or just <=:
CREATE OR REPLACE PROCEDURE get_revs(month IN VARCHAR2, year IN VARCHAR2) IS
BEGIN
....
SELECT *
FROM revenus_table
WHERE y_code = year
AND TO_NUMBER(m_code) BETWEEN 1 AND TO_NUMBER(month);
...
END;
You can do some thing like below example to make your procedure more dynamic
CREATE OR REPLACE PROCEDURE get_revs (
MONTH IN VARCHAR2,
YEAR IN VARCHAR2
)
IS
variable_name table_name%ROWTYPE;
v_sql VARCHAR2 (1000)
:= 'SELECT * FROM REVENUS_TABLE WHERE Y_CODE IN ('
|| MONTH
|| ')';
BEGIN
IF MONTH = 4 THEN
v_sql := v_sql || ' and M_CODE IN (''1'',''2'',''3'',''4'')';
END IF;
IF MONTH = 3 THEN
v_sql := v_sql || 'and M_CODE IN (''1'',''2'',''3'')';
END IF;
EXECUTE IMMEDIATE v_sql
INTO variable_name;
END;
Just an alternative to think about.
CREATE OR REPLACE PROCEDURE get_revs(month IN VARCHAR2, year IN VARCHAR2) IS
lv_in_clause VARCHAR2(100 CHAR);
p_ref sys_refcursor;
BEGIN
SELECT 'IN ('
||WMSYS.WM_CONCAT(A.NUM)
||')'
INTO lv_in_clause
FROM
(SELECT ''''
||LEVEL
||'''' NUM,
1 ID1
FROM DUAL
CONNECT BY LEVEL < to_number(MONTH)
)A
GROUP BY a.ID1;
OPEN p_ref FOR 'SELECT *
FROM revenus_table
WHERE y_code = '|| year
||' AND m_code '||lv_in_clause;
END;
How to retrieve list of employees for each department from table EMP into a comma-delimited new table
something like:
[column x:ie deptno] [column y:ie ename]
--------------------------
7 Jesus, María, José
5 Staz, Przemek, Tomek
6 John, Jane, Bob
below table is where I want to put my result from Function concatenate_list compilation
CREATE TABLE Z
(
x NUMBER(2) NOT NULL,
y VARCHAR2 (4000) NOT NULL
);
SET SERVEROUTPUT ON
CREATE OR REPLACE FUNCTION concatenate_list (xy_cursor IN SYS_REFCURSOR)
RETURN VARCHAR2
IS
lret VARCHAR2(30000);
ltemp VARCHAR2(30000);
BEGIN
LOOP
FETCH xy_cursor
INTO ltemp;
EXIT WHEN xy_cursor%notfound;
lret := lret || ',' || ltemp;
END LOOP;
RETURN LTRIM(lret, ',');
END;
/
SHOW ERRORS
how to insert the results from "Function concatenate_lit compile" and get a result as mentioned above.
Maybe using something like this:
INSERT INTO Z( x, y) SELECT e1.x,
concatenate_list(CURSOR(SELECT e2.y FROM EMP e2 WHERE e2.x= e1.x));
but how to set it up form inside the PL/SQL block
This may help you.
declare
type cur_name is ref cursor;
emp_name cur_name;
v_ename emp.ename%type;
v_all_ename varchar2(1000);
v_deptno emp.deptno%type;
cursor c is select deptno,cursor(select ename from emp e where e.deptno=f.deptno) from emp f group by deptno;
begin
open c;
loop
fetch c into v_deptno,emp_name;
exit when c%notfound;
loop
fetch emp_name into v_ename;
exit when emp_name%notfound;
v_all_ename:=v_all_ename||v_ename;
v_all_ename:=v_all_ename||',';
end loop;
dbms_output.put_line(v_deptno||' '||v_all_ename);
v_all_ename:='';
end loop;
close c;
end;
I'm not sure how to phrase this question, but I am trying to convert a row value into columns in PL/SQL. I'm not a newbie, but I just can't figure how to go about doing this. I have the following table data:
Name Responsibility
Joe Sales
Steve Sales
Paul Exec
Pete Manager
John Exec
Roger Sec
Scott Exec
I need to create a query that will return and display the data as follows:
Sales Manager Exec Sec
Joe Pete John Roger
Steve Null Scott Null
Null Null Paul Null
The problem I am facing is that the row data is not related to eachother in anyway. Can this even be done?
I appreciate any help anyone can offer on this.
Thanks.
First of all it would be better if you give us your database version in the future.For exemple if you have Oracle 11g even if it isn't exectly what you want you can do something similar with PIVOT
with pivot_data as (select rownum as no,emp_name,job_name from (
select 'Joe' as emp_name,'Sales' as job_name from dual union all
select 'Steve','Sales' from dual union all
select 'Paul','Exec' from dual union all
select 'Pete','Manager' from dual union all
select 'John','Exec' from dual union all
select 'Roger','Sec' from dual union all
select 'Scott','Exec' from dual))
select "'Sales'" as Sales,"'Exec'" as Exec,"'Sec'" as Sec,"'Manager'" as Manager
from pivot_data
pivot(max(emp_name) for job_name in ('Sales','Exec','Sec','Manager'))
SALES EXEC SEC MANAGER
-------------------------------
Roger
Scott
John
Joe
Steve
Paul
Pete
Or if this isn't enough for you this PL/SQL does what you want.I tested it on 11g and i'm fairly certain that it would work with 10g et 9i but i can't guarantee anything for DB2 or PostGre :
declare
cursor C1 is
select 'Joe' as emp_name,'Sales' as job_name from dual union all
select 'Steve','Sales' from dual union all
select 'Paul','Exec' from dual union all
select 'Pete','Manager' from dual union all
select 'John','Exec' from dual union all
select 'Roger','Sec' from dual union all
select 'Scott','Exec' from dual;
varray_sales dbms_sql.varchar2_table;
varray_exec dbms_sql.varchar2_table;
varray_manager dbms_sql.varchar2_table;
varray_sec dbms_sql.varchar2_table;
i_sales number := 0;
i_exec number := 0;
i_manager number := 0;
i_sec number := 0;
n_line_aextr number;
colwidth number := 30;
line varchar2(120);
function max_de_deux (i number,x number) return number is
begin
return case when i > x then i else x end;
end;
begin
dbms_output.enable(120);
for l1 in c1 loop
if L1.job_name = 'Sales' then
i_sales := i_sales+1;
varray_sales(i_sales) := L1.emp_name;
elsif L1.job_name = 'Exec' then
i_exec := i_exec+1;
varray_exec(i_exec) := L1.emp_name;
elsif L1.job_name = 'Manager' then
i_Manager := i_Manager+1;
varray_manager(i_Manager) := L1.emp_name;
elsif L1.job_name = 'Sec' then
i_sec := i_sec+1;
varray_sec(i_sec) := L1.emp_name;
end if;
end loop;
dbms_output.put_line(rpad('Sales',colwidth,' ')||rpad('Exec',colwidth,' ')||rpad('Manager',colwidth,' ')
||rpad('Sec',colwidth,' '));
dbms_output.put_line(rpad('-',colwidth,'-')||rpad('-',colwidth,'-')||rpad('-',colwidth,'-')
||rpad('-',colwidth,'-'));
n_line_aextr := max_de_deux(i_manager,max_de_deux(i_sec,max_de_deux(i_sales,i_exec)));
for l in 1..n_line_aextr loop
line := '';
if l <= i_sales then
line := line || rpad(varray_sales(l),colwidth,' ');
else
line := line || rpad(' ',colwidth,' ');
end if;
if l <= i_exec then
line := line || rpad(varray_exec(l),colwidth,' ');
else
line := line || rpad(' ',colwidth,' ');
end if;
if l <= i_manager then
line := line || rpad(varray_manager(l),colwidth,' ');
else
line := line || rpad(' ',colwidth,' ');
end if;
if l <= i_sec then
line := line || rpad(varray_sec(l),colwidth,' ');
else
line := line || rpad(' ',colwidth,' ');
end if;
dbms_output.put_line(line);
end loop;
end;
Sales Exec Manager Sec
------------------------------------------------------------
Joe Paul Pete Roger
Steve John
Scott