I'd like a Oracle Pipelined Function which compares two records from a query result and shows only the column which have changed between the query result?
Here is a blunt instrument approach to the problem.
create or replace function col_diff
( p_empno_1 in emp.empno%type
, p_empno_2 in emp.empno%type )
return col_nt pipelined
is
out_val col_t := new col_t(null, null, null);
emp_rec1 emp%rowtype;
emp_rec2 emp%rowtype;
begin
select *
into emp_rec1
from emp
where empno = p_empno_1;
select *
into emp_rec2
from emp
where empno = p_empno_2;
if emp_rec1.ename != emp_rec2.ename
then
out_val.col_name := 'ENAME';
out_val.old_val := emp_rec1.ename;
out_val.new_val := emp_rec2.ename;
pipe row (out_val);
end if;
if emp_rec1.hiredate != emp_rec2.hiredate
then
out_val.col_name := 'HIREDATE';
out_val.old_val := to_char(emp_rec1.hiredate, 'DD-MON-YYYY');
out_val.new_val := to_char(emp_rec2.hiredate, 'DD-MON-YYYY');
pipe row (out_val);
end if;
return;
end;
/
So, given this test data...
SQL> select empno, ename, hiredate
2 from emp
3 where empno > 8100
4 /
EMPNO ENAME HIREDATE
---------- ---------- ---------
8101 PSMITH 03-DEC-10
8102 PSMITH 02-JAN-11
SQL>
... we get this output:
SQL> select * from table (col_diff(8101,8102))
2 /
COL_NAME
------------------------------
OLD_VAL
-------------------------------------------------------------------
NEW_VAL
-------------------------------------------------------------------
HIREDATE
03-DEC-2010
02-JAN-2011
SQL>
Now, doubtlessly you would like something which is less verbose. I think it may be possible to do something using the enhanced Method 4 dynamic SQL which was introduced in 11g. Alas, you say you are using 10g.
Its not quite what you want, but Kevin Meade's blog on OracleFAQ has a solution which works for me:
http://www.orafaq.com/node/1826
http://www.orafaq.com/files/column_diffs.txt
Related
Is there any limit on the number of aggregate Functions on different columns in a query?
example
SELECT SUM(col1), AVG(col2), SUM(col3), ... FROM table;
DB- Oracle
and if yes, why?
There's none, as far as I can tell. Why do you think there is, or that there should be?
Oracle limits e.g. number of columns in a table to 1000 (as of 19c version), but - this is a SELECT column list, no matter you use aggregates on different columns.
I did a test (and so can you) on Scott's sample schema:
create table test (col clob);
declare
l_str varchar2(32000) := 'select ';
l_col varchar2(20);
begin
for i in 1 .. 1010 loop
l_col := case when substr(i, -1) = 1 then 'sum(sal)'
when substr(i, -1) = 3 then 'avg(empno)'
when substr(i, -1) = 5 then 'max(job)'
else 'min(deptno)'
end;
l_str := l_str || l_col ||',';
end loop;
l_str := rtrim(l_str, ',') || ' from emp';
insert into test values (l_str);
end;
/
select * From test;
Result is such a looooong string (1010 columns in total):
select sum(sal),min(deptno),avg(empno),min(deptno),max(job), ... from emp
which returns a result. For demonstration purposes, I shortened the query as it won't work in SQL*Plus because of "SP2-0027: Input is too long (> 2499 characters) - line ignored". It runs OK with 1010 columns in e.g. TOAD:
SQL> select sum(sal),min(deptno),avg(empno),min(deptno),max(job),min(deptno) from emp;
SUM(SAL) MIN(DEPTNO) AVG(EMPNO) MIN(DEPTNO) MAX(JOB) MIN(DEPTNO)
---------- ----------- ---------- ----------- --------- -----------
29025 10 7726,57143 10 SALESMAN 10
SQL>
So, no problem.
I am new to PL/SQL and oracle, I am using SQL developer 19 against an Oracle 12C database.
All I am trying to do, as I am used to do in T-SQL, is to select some data from a table where a date field value is between two stored procedure ate parameters; below the stored procedure I am using that give to me an error saying that I have to "SELECT INTO" ??
create or replace PROCEDURE GET_DMR_HALO_VALUES ( START_DATE IN DATE , END_DATE IN DATE )
AS
BEGIN
SELECT
HALO_RECORD_ID ,
ASSET_ID ,
ASSET_NAME ,
NUMERIC_VALUE ,
IS_ENABLED ,
ADDED_BY ,
VALUE_DATE ,
NOTES ,
DATE_ADDED
FROM halo_inputs
WHERE trunc(value_date) BETWEEN START_DATE and END_DATE;
END GET_DMR_HALO_VALUES;
then I also have another problem... assuming that the above works I am trying to view the returned table data by calling the stored procedure in SQL developer as follow
DEFINE START_DATE date := TO_DATE('2019-02-12','YYYY-DD-MM');
DEFINE END_DATE date := TO_DATE('2019-02-12','YYYY-DD-MM');
exec GET_DMR_HALO_VALUES(:START_DATE, :END_DATE );
Am I right by calling the SP like above?
UPDATE
after looking at this stack-over article
I changed the stored procedure as follow
create or replace PROCEDURE GET_DMR_HALO_VALUES ( START_DATE IN DATE , END_DATE IN DATE )
AS
c1 sys_refcursor;
BEGIN
open c1 for
SELECT
HALO_RECORD_ID ,
ASSET_ID ,
ASSET_NAME ,
NUMERIC_VALUE ,
IS_ENABLED ,
ADDED_BY ,
VALUE_DATE ,
NOTES ,
DATE_ADDED
FROM halo_inputs
WHERE trunc(value_date) BETWEEN START_DATE and END_DATE;
dbms_sql.return_result(c1);
END;
but are cursors the only way in Oracle to get table data ?
UPDATE 2
I also changed the exec query as follow
DECLARE
START_DATE date := TO_DATE('12-02-20','DD-MM-YY');
END_DATE date := TO_DATE('12-02-20','DD-MM-YY');
BEGIN
GET_DMR_HALO_VALUES(START_DATE,END_DATE );
END;
and it works but how I get SQL developer to display data in a grid view rather than in plain text ?
The simplest option to make this query return data in data grid is to ... well, run it outside of the stored procedure. Procedures are nice, but not that nice for doing what you want.
When returning something, a function might be a better choice. For example:
Create it first:
SQL> create or replace FUNCTION f_test (par_deptno in number)
2 return sys_refcursor
3 is
4 l_rc sys_refcursor;
5 begin
6 open l_rc for
7 select empno, ename, job, sal
8 from emp
9 where deptno = par_deptno;
10 return l_rc;
11 end;
12 /
Function created.
Then call it:
SQL> select f_test(10) from dual;
F_TEST(10)
--------------------
CURSOR STATEMENT : 1
CURSOR STATEMENT : 1
EMPNO ENAME JOB SAL
---------- ---------- --------- ----------
7782 CLARK MANAGER 2450
7839 KING PRESIDENT 5000
7934 MILLER CLERK 1300
SQL>
In SQL Developer, you'd run the last statement (select f_test(10) from dual;) as a script (F9 keyboard shortcut) and it'll then display the result nicely (as my example shows above, although it is SQL Plus command line tool output). Though, it still won't be in a data grid.
I have this function that separates the primary keys by commas and does the search, but when passing the keys to the function I'm getting the following error:
ORA-00932: inconsistent datatypes: expected CHAR got RDVE.SPLIT_TBL
00932. 00000 - "inconsistent datatypes: expected %s got %s"
Function:
create or replace type split_tbl as table of varchar2(32767);
create or replace function split2(
list in varchar2,
delimiter in varchar2 default ','
)
return split_tbl as
splitted split_tbl := split_tbl();
i pls_integer := 0;
list_ varchar2(32767) := list;
begin
loop
i := instr(list_, delimiter);
if i > 0 then
splitted.extend(1);
splitted(splitted.last) := substr(list_, 1, i - 1);
list_ := substr(list_, i + length(delimiter));
else
splitted.extend(1);
splitted(splitted.last) := list_;
return splitted;
end if;
end loop;
end;
Calling the function and performing select:
SELECT *
FROM INFO_APONTAMENT info
WHERE info.Boat_Id = split2(:id)
ORDER BY info.Date DESC
FETCH FIRST 1 ROWS ONLY;
already tried to change the types to CHAR, but it did not solve
The way you put it, it seems that this might do the job:
SELECT *
FROM INFO_APONTAMENT info
WHERE info.Boat_Id in (select * from table(split2(:id))) --> this
ORDER BY info.Date DESC
FETCH FIRST 1 ROWS ONLY;
I created your function in my database and used it on Scott's EMP table, so - if that's what you wanted it to do, see if it helps.
SQL> select deptno, ename
2 from emp
3 where deptno in (select * from table(split2('10,20')));
DEPTNO ENAME
---------- ----------
10 MILLER
10 KING
10 CLARK
20 FORD
20 ADAMS
20 SCOTT
20 JONES
20 SMITH
8 rows selected.
SQL>
Use the MEMBER OF operator:
SELECT *
FROM INFO_APONTAMENT info
WHERE info.Boat_Id MEMBER OF split2('1,2,3,123')
ORDER BY info."Date" DESC
FETCH FIRST 1 ROWS ONLY;
However, that will only return a single row.
If you want the latest row for each boat_id then you can use:
SELECT *
FROM (
SELECT info.*,
ROW_NUMBER() OVER ( PARTITION BY boat_id ORDER BY "Date" DESC ) AS rn
FROM INFO_APONTAMENT info
WHERE info.Boat_Id MEMBER OF split2('1,2,3,123')
)
WHERE rn = 1;
db<>fiddle
I would like to monitor some field in my database ; when an SQL query updates that field with a certain value, I would like to log the query that triggered the update.
How can I do that ?
Thank you in advance!
Good question. Made me curious. I found an answer here.
A LOGMINER utility is also mentioned there. Maybe worth looking into?
SQL> CREATE OR REPLACE FUNCTION cur_sql_txt
2 RETURN CLOB
3 AS
4 v_cnt BINARY_INTEGER;
5 v_sql ORA_NAME_LIST_T;
6 v_rtn CLOB;
7 BEGIN
8 v_cnt := ora_sql_txt (v_sql);
9 FOR l_bit IN 1..v_cnt LOOP
10 v_rtn := v_rtn || RTRIM (v_sql (l_bit), CHR (0));
11 END LOOP;
12 RETURN RTRIM (v_rtn, CHR (10)) || ';';
13 END;
14 /
Function created.
SQL> CREATE OR REPLACE TRIGGER trigger_name
2 BEFORE UPDATE ON emp
3 FOR EACH ROW
4 BEGIN
5 DBMS_OUTPUT.PUT_LINE (cur_sql_txt);
6 END;
7 /
Trigger created.
SQL> SET SERVEROUTPUT ON;
SQL> UPDATE emp
2 SET empno = empno,
3 ename = ename
4 WHERE ROWNUM = 1;
UPDATE emp
SET empno = empno,
ename = ename
WHERE ROWNUM =
:"SYS_B_0";
1 row updated.
SQL>
Why don't you use the audit statement? It allows you to monitor for instance updates against a table.
I need to create a Oracle query for example
select * from emp where emp_id=i_emp_id and emp_nm=i_emp_nm and emp_dpt=i_emp_dpt
if all the three inputs are not null it should function like
select * from emp where emp_id=i_emp_id and emp_nm=i_emp_nm and emp_dpt=i_emp_dpt
if i pass i_emp_id as null then the query should function like
select * from emp where emp_nm=i_emp_nm and emp_dpt=i_emp_dpt
if i pass i_emp_id as null and i_emp_dpt as null then the query should function like
select * from emp where emp_nm=i_emp_nm
The best way to handle different permutations of input variables is to assemble the query dynamically. The following example will produce a query which performs well and handles NULL values neatly so as to return the correct result.
create or replace function get_dyn_emps
(i_empno in emp.empno%type
, i_ename in emp.ename%type
, i_deptno in emp.deptno%type)
return sys_refcursor
is
rc sys_refcursor;
stmt varchar2(32767);
begin
stmt := 'select * from emp where 1=1';
if i_empno is not null
then
stmt := stmt||' and empno = :p_empno';
else
stmt := stmt||' and (1=1 or :p_empno is null)';
end if;
if i_ename is not null
then
stmt := stmt||' and ename = :p_ename';
else
stmt := stmt||' and (1=1 or :p_ename is null)';
end if;
if i_deptno is not null
then
stmt := stmt||' and deptno = :p_deptno';
else
stmt := stmt||' and (1=1 or :p_deptno is null)';
end if;
open rc for stmt
using i_empno, i_ename , i_deptno;
return rc;
end get_dyn_emps;
/
This may seem like a long-winded solution compared to the currently-accepted answer, but here's why it is the better approach: it returns the correct answer.
In deparment 40 there is an employee with no name:
SQL> var rc refcursor
SQL> exec :rc := get_dyn_emps(null, null, 40)
PL/SQL procedure successfully completed.
SQL> print rc
EMPNO ENAME JOB MGR HIREDATE SAL COMM DEPTNO
------- ---------- --------- ---------- --------- ---------- ---------- ---------
8101 03-DEC-10 40
SQL>
If I implement the apparently neater DECODE() solution ...
create or replace function get_fix_emps
(i_empno in emp.empno%type
, i_ename in emp.ename%type
, i_deptno in emp.deptno%type)
return sys_refcursor
is
rc sys_refcursor;
begin
open rc for
SELECT * FROM emp
WHERE empno = DECODE(NVL(i_empno,0), 0, empno, i_empno)
AND ename = DECODE(NVL(i_ename,'X'), 'X', ename, i_ename)
AND deptno = DECODE(NVL(i_deptno,0), 0, deptno, i_deptno);
return rc;
end get_fix_emps;
/
... this is what happens:
SQL> exec :rc := get_fix_emps(null, null, 40)
PL/SQL procedure successfully completed.
SQL> print rc
no rows selected
SQL>
Because NULL does not ever equal NULL, which is what ename = DECODE(NVL(i_ename,'X'), 'X', ename, i_ename) evaluates to in this case.
As I did in my applications, you can achieve this functionality by simply using NVL and DECODE functions.
SELECT * FROM emp
WHERE emp_id = DECODE(NVL(i_emp_id,0), 0, emp_id, i_emp_id)
AND emp_nm = DECODE(NVL(i_emp_nm,0), 0, emp_nm, i_emp_nm)
AND emp_dpt = DECODE(NVL(i_emp_dpt,'X'), 'X', emp_dpt, i_emp_dpt)
If i_emp_id is null than it will match with current value so all records will match otherwise only record which matches i_emp_id will return. Same applies to emp_nm and emp_dpt.