4

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!

J. Christian
  • 495
  • 1
  • 7
  • 14
  • 2
    This part is incomprehensible `without derived tables, sub-queries, joins, or a GROUP BY clause?` So you want to bake a cake without flour, milk or eggs? – RichardTheKiwi Mar 25 '11 at 22:01
  • Why don't you want to use a derived table or a sub-query? –  Mar 25 '11 at 22:03
  • I have special constraints. I am trying to implement this query as an end-user in Oracle Discoverer Plus, a GUI query builder. In Discoverer, I don't have access to the override the SQL or the EUL ("End-User-Layer") which controls all the joins. So, I'm looking for a workaround. – J. Christian Mar 25 '11 at 22:06
  • 2
    When faced with such tools, I find it easiest to just create a VIEW that gives the data required as a simple object for the tool to select. – RichardTheKiwi Mar 25 '11 at 22:08
  • I agree with Richard - create a view for this (basically the derived table in my example) –  Mar 25 '11 at 22:33
  • I totally agree. However, I can't create views because the EUL controls the views. I guess another constraint is that I'm trying not to get the administrator involved. Thanks for these solutions, though. Do we have a consensus, at least, that this is not possible with just (1) a modification of the SELECT or WHERE clauses and (2) only the most basic predicates (=,<>,IN,LIKE,) in the WHERE clause? – J. Christian Mar 25 '11 at 22:57
  • Bago - 100% certified. You don't need to use the EUL to create a view. Connect directly to Oracle and create it. – RichardTheKiwi Mar 25 '11 at 23:00
  • Shouldn't the RANK() be "ORDER BY period DESC" ? Isn't Period 2 (with Status G) the latest record for Employee ID 1? – beach May 05 '11 at 05:19

4 Answers4

4

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.

0

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)
RichardTheKiwi
  • 105,798
  • 26
  • 196
  • 262
0

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...

Ronnis
  • 12,593
  • 2
  • 32
  • 52
  • Unfortunately, this solution doesn't work within my constraints. See the updated question & question comments for more details. Thanks for thinking outside the box though! – J. Christian Mar 26 '11 at 00:04
  • 1
    The constraint with the grumpy DBA isn't a real one though. If the report is worth implementing the view should be created. Don't be the guy who wastes your companys money :) – Ronnis Mar 26 '11 at 00:23
  • 1
    I don't think it's fair to say that I am wasting my company's money. Now I realize that what I' asking for is a really bad hack in SQL, but maybe a slick workaround in Discoverer that would decrease the maintenance load. However, I didn't know enough about SQL to do this. I will edit the post accordingly to be a Discoverer-oriented post. – J. Christian Mar 26 '11 at 01:20
  • 1
    However, I also realize now that I should work with my DBA to come up with a best practice solution--instead of asking Stack Overflow to brainstorm solutions to my obscure problem. Thanks. – J. Christian Mar 26 '11 at 02:10
  • @Bago, I re-read my comment and realized it sounded rude, which wasn't my intention. I'm glad you decided to solve the problem together with your DBA. The two of you will come up with a good solution. I too invent constraints when there are none to save paperwork/meetings and sometimes also out of ignorance, but luckily the people around me poke me in the eye before I becomes the guy who wastes money ;) – Ronnis Mar 26 '11 at 19:53
0

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

  • Your answer could be improved with additional supporting information. Please [edit] to add further details, such as citations or documentation, so that others can confirm that your answer is correct. You can find more information on how to write good answers [in the help center](/help/how-to-answer). – Community Dec 30 '21 at 05:38