Background
Sample data set
#Employee
Id | Period | Status
---------------------
1 | 1 | L
1 | 2 | G
2 | 3 | L
I want a simple select query to yield employees' latest record (by period) only if the status='L'.
The results would look like this:
#Desired Results
Id | Period | Status | Sequence
-------------------------------
2 | 3 | L | 1
Naive attempt
Obviously, my naive attempt at a query does not work:
#select query
SELECT *, RANK() OVER (PARTITION BY id ORDER BY period ASC) sequence
FROM employees
WHERE status = 'L'
AND sequence = 1
Which results in the following:
#Naive (incorrect) Results
ID | Period | Status | Sequence
-------------------------------
1 | 1 | L | 1
2 | 3 | L | 1
Knowing the order that clauses are evaluated in SQL explains why it doesn't work.开发者_运维问答 Here is how my query is evaluated:
- Isolate rows where status='L'
- Rank the rows
- Isolate top rank row
I want the following:
- Rank rows
- Isolate the top ranked rows
- Isolate where status='L'
Questions
Is possible--with only a simple modification to the SELECT/WHERE clauses and using only basic predicate operators--to ensure that predicates based on analytic functions in the WHERE clause get evaluated before the non-aggregate predicates?
Anyone have other solutions that can be implemented as an end-user in Oracle Discoverer Plus?
Thanks!
Is it possible to do this without a sub-query
Technically the following is not a sub-query but a derived table
SELECT *
FROM (
SELECT *,
RANK() OVER (PARTITION BY id ORDER BY period ASC) sequence
FROM employees
) t
WHERE status = 'L'
AND sequence = 1
I can't think of a different solution to your problem.
The classic Group by
SELECT e.id, e.period, e.status, 1 sequence
FROM
(
SELECT id, min(period) period
FROM employees
GROUP BY id
) X
JOIN employees e on e.period=X.period and e.id=X.id
WHERE e.status = 'L'
Exists
select e.id, e.period, e.status, 1 sequence
FROM employees e
WHERE e.status = 'L'
AND NOT EXISTS (select *
from employees e2
where e2.id=e.id and e2.period>e.period)
I'll probably have to do a "Dobby" and slam my ear in the oven door and iron my hands for this...
You can create a function which evaluates the current row.
Note that this is inherently non-scalable. But I guess it's better than nothing.
Create the sample data:
--drop table employee purge;
create table employee(
id number not null
,period number not null
,status char(1) not null
,constraint employee_pk primary key(id, period)
);
insert into employee(id,period, status) values(1, 1, 'L');
insert into employee(id,period, status) values(1, 2, 'G');
insert into employee(id,period, status) values(2, 3, 'L');
commit;
Create the slowest function in the database:
create or replace function i_am_slow(
ip_id employee.id%type
,ip_period employee.period%type
)
return varchar2
as
l_count number := 0;
begin
select count(*)
into l_count
from employee e
where e.id = ip_id
and e.period = ip_period
and e.status = 'L'
and not exists(
select 'x'
from employee e2
where e2.id = e.id
and e2.period > e.period);
if l_count = 1 then
return 'Y';
end if;
return 'N';
end;
/
Demonstrates the use of the function:
select id, period, status
from employee
where i_am_slow(id, period) = 'Y';
ID PERIOD STATUS
---------- ---------- ------
2 3 L
Rushes towards the oven...
select * from (SELECT a.*, rank() OVER (ORDER BY period ASC) sequence from (select * from ( select 1 id, 1 period, 'L' status from dual union all select 1 id, 2 period, 'G' status from dual union all select 2 id, 3 period, 'L' status from dual ) where status = 'L' ) a ) where sequence = 1
精彩评论