Access "outer row" value in an analytic expression - oracle
I would like to access the value of the "current row" on which I write the analytic expression on. For example, given the following sample data:
DROP TABLE emp PURGE;
CREATE TABLE emp (
empno NUMBER(4) CONSTRAINT pk_emp PRIMARY KEY,
ename VARCHAR2(10),
job VARCHAR2(9),
mgr NUMBER(4),
hiredate DATE,
sal NUMBER(7,2),
comm NUMBER(7,2),
deptno NUMBER(2)
);
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 (7566,'JONES','MANAGER',7839,to_date('2-4-1981','dd-mm-yyyy'),2975,NULL,20);
INSERT INTO emp VALUES (7654,'MARTIN','SALESMAN',7698,to_date('28-9-1981','dd-mm-yyyy'),1250,1400,30);
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 (7788,'SCOTT','ANALYST',7566,to_date('13-7-87','dd-mm-rr')-85,3000,NULL,20);
INSERT INTO emp VALUES (7839,'KING','PRESIDENT',NULL,to_date('17-11-1981','dd-mm-yyyy'),5000,NULL,10);
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-7-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 (7902,'FORD','ANALYST',7566,to_date('3-12-1981','dd-mm-yyyy'),3000,NULL,20);
INSERT INTO emp VALUES (7934,'MILLER','CLERK',7782,to_date('23-1-1982','dd-mm-yyyy'),1300,NULL,10);
COMMIT;
Let's say I would like to calculate the average (using the deptno for partitioning) only if the salary is smaller than the salary value of the "outer row"
Here is the query that calculates the average for everyone in the specific window, the row that is commented out what I would like to be able to do, "pseudocode".
SELECT t.empno, t.deptno, t.sal
,AVG(t.sal) OVER (PARTITION BY t.deptno) AS avg_dept_sal
--,AVG(CASE WHEN t.sal < OUTER_VALUE(t.sal) THEN t.sal ELSE NULL END) OVER (PARTITION BY t.deptno) AS avg_dept_sal_2
FROM emp t
;
So, while avg_dept_sal returns ~2916 for deptno = 10, for each row, with avg_dept_sal_2 should return:
1300 for empno = 7782
1875 for empno = 7839
NULL for empno = 7934
What would be the best approach to achieve this?
Use a RANGE window in the analytic function:
SELECT empno,
deptno,
sal,
AVG(sal) OVER (PARTITION BY deptno) AS avg_dept_sal,
AVG(sal) OVER (
PARTITION BY deptno
ORDER BY sal
RANGE BETWEEN UNBOUNDED PRECEDING AND 0.01 PRECEDING
) AS avg_dept_sal_2
FROM emp;
Which, for the sample data, outputs:
EMPNO
DEPTNO
SAL
AVG_DEPT_SAL
AVG_DEPT_SAL_2
7934
10
1300
2916.666666666666666666666666666666666667
null
7782
10
2450
2916.666666666666666666666666666666666667
1300
7839
10
5000
2916.666666666666666666666666666666666667
1875
7369
20
800
2175
null
7876
20
1100
2175
800
7566
20
2975
2175
950
7788
20
3000
2175
1625
7902
20
3000
2175
1625
7900
30
950
1566.666666666666666666666666666666666667
null
7654
30
1250
1566.666666666666666666666666666666666667
950
7521
30
1250
1566.666666666666666666666666666666666667
950
7844
30
1500
1566.666666666666666666666666666666666667
1150
7499
30
1600
1566.666666666666666666666666666666666667
1237.5
7698
30
2850
1566.666666666666666666666666666666666667
1310
db<>fiddle here
Related
ORA-00936 : missing expressions for IN clause
select * from students where s_id IN (:sIds); public List<Integer> getStudents(#Param("userId") Integer sIds); where sIds => List of student ids
You cannot use a single bind variable to hold multiple values and use it as an in-list. Depending on the type of the s_id column you can do something like this (here I suppose :sIds is a comma-separated list of integers): select * from students where s_id in (select regexp_substr(:sIds, '[0-9]+',1,level) from val connect by level<regexp_count(:sIds,',')+2);
Another option might be this: SQL> select * from emp 2 where empno in 3 (select * from table(sys.odcinumberlist(7369, 7499, 7521))); EMPNO ENAME JOB MGR HIREDATE SAL COMM DEPTNO ---------- ---------- --------- ---------- ---------- ---------- ---------- ---------- 7369 SMITH CLERK 7902 17-12-1980 800 20 7499 ALLEN SALESMAN 7698 20-02-1981 1600 300 30 7521 WARD SALESMAN 7698 22-02-1981 1250 500 30 SQL> where 7369, 7499, 7521 represents values you'd enter into sIds
Row Number In oracle
I want to fetch data based on the last row number. I want the records highlighted in yellow color. Please guide.
Should be a simple MAX function. select max(row_number) rn, account_no from your_table group by account_no order by account_no; If "row_number" represents the result of analytic function (which isn't clear from what you posted so far), then include ORDER BY clause into the function (I don't know which column you're sorting data on) in descending order so that your "max" actually becomes "min" whose RN = 1 and then it is easy to select it as a final result. with temp as (select columnb, columnc, row_number() over (partition by accountno order by SOMETHING desc) rn ^^^^^^^^^^^^^^^^^^^^^^^ add this from some_table ) select columnb, columnc from temp where rn = 1 As I don't have your tables, here's Scott's EMP: SQL> select * from emp; EMPNO ENAME JOB MGR HIREDATE SAL COMM DEPTNO ---------- ---------- --------- ---------- -------- ---------- ---------- ---------- 7369 SMITH CLERK 7902 17.12.80 920 20 7499 ALLEN SALESMAN 7698 20.02.81 1600 300 30 7521 WARD SALESMAN 7698 22.02.81 1250 500 30 7566 JONES MANAGER 7839 02.04.81 2975 20 7654 MARTIN SALESMAN 7698 28.09.81 1250 1400 30 7698 BLAKE MANAGER 7839 01.05.81 2850 30 7782 CLARK MANAGER 7839 09.06.81 2450 10 7788 SCOTT ANALYST 7566 09.12.82 3000 20 7839 KING PRESIDENT 17.11.81 10000 10 7844 TURNER SALESMAN 7698 08.09.81 1500 0 30 7876 ADAMS CLERK 7788 12.01.83 1100 20 7900 JAMES CLERK 7698 03.12.81 950 30 7902 FORD ANALYST 7566 03.12.81 3000 20 7934 MILLER CLERK 7782 23.01.82 1300 10 14 rows selected. Your code would then be like this; note line #6 which calculates row number as I suggested; you'll use it in the final WHERE clause (line #11), while presenting that "max" row number value you desperately wanted. SQL> with temp as 2 (select deptno, 3 sal, 4 row_number() over (partition by deptno order by sal) rn, 5 -- 6 row_number() over (partition by deptno order by sal desc) rnd 7 from emp 8 ) 9 select deptno, sal, rn 10 from temp 11 where rnd = 1 12 / DEPTNO SAL RN ---------- ---------- ---------- 10 10000 3 20 3000 4 30 2850 6 SQL>
SQL Top 5 and the rest as others
I want to select the top 5 rows and additional a 6th row named Others with rest aggregated. with Anzahl as (SELECT NVL (parse_listener_log_line (connect_string, 'HOST'), 'n/a') host, COUNT(*) cnt FROM listener_log WHERE ID_MANDANT = :P100_MANDANT AND ID_SERVER = :P100_SERVER GROUP BY parse_listener_log_line (connect_string, 'HOST') ORDER BY cnt DESC), client as (select case when rownum > 4 then 'Others' else host end as client, cnt FROM Anzahl) SELECT client, cnt FROM client; CLIENT CNT jdbc 118553 server2 106170 server1 101710 server4 13370 Others 8734 Others 1760 Others 1365 Others 1058
A little bit of analytic functions (row_number) along with set operations (union) might do what you're looking for. Scott's EMP table contains these data: SQL> select ename, sal from emp order by sal desc; ENAME SAL ---------- ---------- KING 5000 FORD 3000 SCOTT 3000 JONES 2975 BLAKE 2850 CLARK 2450 ALLEN 1600 TURNER 1500 MILLER 1300 WARD 1250 MARTIN 1250 ADAMS 1100 JAMES 950 SMITH 800 14 rows selected. Now: find each row's ordinal number (using row_number) and union the first 5 rows (take each of them as is) with the sixth one that contains aggregated salaries: SQL> with temp as 2 (select ename, 3 sal, 4 row_number() over (order by sal desc) rn 5 from emp 6 ) 7 select rn, 8 ename, 9 sal 10 from temp 11 where rn <= 5 12 union all 13 select 6, 14 'Other', 15 sum(sal) 16 from temp 17 where rn > 5 18 order by rn; RN ENAME SAL ---------- ---------- ---------- 1 KING 5000 2 SCOTT 3000 3 FORD 3000 4 JONES 2975 5 BLAKE 2850 6 Other 12200 6 rows selected. SQL>
Oracle Conditional where clause
is there any way to write query with following functionality, add where clause as a conditional way, select e.emp_id, emp.admin_user from employees e if emp.admin != 'Y' then query run with where clause else query run without where clause ?
Using a CASE expression in the WHERE clause should do the trick. When you say you don't need the where clause if condition is not met, then all you want is a condition like WHERE 1 = 1, i.e. when condition is not met then return all rows. So, you need to make the not met condition as always TRUE. For example, I have an employee table, SQL> SELECT empno, ename, deptno 2 FROM emp; EMPNO ENAME DEPTNO ---------- ---------- ---------- 7369 SMITH 20 7499 ALLEN 30 7521 WARD 30 7566 JONES 20 7654 MARTIN 30 7698 BLAKE 30 7782 CLARK 10 7788 SCOTT 20 7839 KING 10 7844 TURNER 30 7876 ADAMS 20 7900 JAMES 30 7902 FORD 20 7934 MILLER 10 14 rows selected. SQL> I want to select the employee details, if department is 20 then use the where clause else return all the employee details, but filter the department which meets the where condition. SQL> SELECT empno, ename, deptno 2 FROM emp 3 WHERE ename = 4 CASE 5 WHEN deptno = 20 6 THEN 'SCOTT' 7 ELSE ename 8 END 9 / EMPNO ENAME DEPTNO ---------- ---------- ---------- 7499 ALLEN 30 7521 WARD 30 7654 MARTIN 30 7698 BLAKE 30 7782 CLARK 10 7788 SCOTT 20 7839 KING 10 7844 TURNER 30 7900 JAMES 30 7934 MILLER 10 10 rows selected. SQL> So, for department 20, the filter is applied by where clause, and I get only the row for ename SCOTT, for others it returns all the rows.
To keep it simple I would go for union clause in this case, so you can have your where clause as complex as you need. I tried to guess your table structure from above comment, let's see this example: SQL> create table employees (emp_id number, admin_user number, project_id number); Table created. SQL> create table project_accessible_to_user (emp_id number, project_id number); Table created. Now make simple union all of two queries one with where condition anoother without it SQL> select * from employees e where e.admin_user!='Y' and project_id in (select project_id from project_accessible_to_user where emp_id=e.emp_id) union all select * from employees e where (e.admin_user is null or e.admin_user='Y'); UNION ALL is better from performance point of view as UNION because it means that it is not checking for intersect values so if there are any it will return duplicates. However in this case it is filtered already by condition on admin_user, so these duplicates will not occure.
Show the column required of a table using group by function in oracle
I want to display the column c.officeID along with the column "Amount". This is my query: select c.officeID,max(Sum(p.amount)) as “Amount” from payment p, client c where c.clientid in (select clientid from client) and p.clientID=c.clientID group by c.officeID; I tried using: select c.officeID,max(Sum(p.amount)) as “Amount” from payment p, client c where c.clientid in (select clientid from client) and p.clientID=c.clientID group by c.officeID,p.amount; But I am getting a error saying 'ORA00937-Not a single group-group funtion'. Could anyone please tell me where I am going wrong?
You can't add a max and group by, use this following query to get the result, if you want max then use the second query, SCOTT#research 17-APR-15> select c.empno,Sum(p.sal) as "Amount" 2 from empp p, emp c 3 where c.empno in (select empno from emp) 4 and p.empno = c.empno 5 group by c.empno 6 ; EMPNO Amount ---------- ---------- 7782 2450 7839 5000 7844 1500 7698 2850 7521 1250 7902 3000 7566 2975 7654 1250 7788 3000 7934 1300 7499 1600 7876 1100 234 800 7900 950 14 rows selected. select max("Amount") from ( select c.empno,Sum(p.sal) as "Amount" from empp p, emp c where c.empno in (select empno from emp) and p.empno = c.empno group by c.empno) MAX("AMOUNT") ------------- 5000