Reputation: 652
I have a table with three fields: ID
, date
and action
. The three possible values of action
are X
, Y
and Z
. I want a query which, for each ID
in the table for which there ia a row with a Z
action, will return the most recent row for the same ID
with an X
action, if one exists.
Here is what I want to do in MySQL. I copy-and-pasted the code from sqlfiddle.com, so I know it works.
create table a (id int,
date int,
action varchar(1));
insert into a values(1, 1, 'X');
insert into a values(1, 2, 'X');
insert into a values(1, 3, 'Z');
insert into a values(2, 1, 'X');
insert into a values(2, 2, 'Y');
insert into a values(2, 3, 'Y');
insert into a values(2, 4, 'Z');
insert into a values(3, 1, 'X');
insert into a values(3, 2, 'Y');
insert into a values(3, 3, 'X');
insert into a values(3, 4, 'Z');
insert into a values(4, 3, 'X');
SELECT a.id,
max(a.date) as Xdate,
b.date as Zdate,
a.action FROM a,
(SELECT * FROM a WHERE a.action = 'Z') b
WHERE
a.date < b.date AND
a.ID = b.ID AND
a.action = 'X'
GROUP BY a.id;
I can't use the GROUP BY
clause with other fields like this in Oracle, so I did a nested subquery which found the maximum date for each ID separately, but it is very slow (I am expecting to get about 10^5 rows) and I was hoping that there was a better way to do it which would be faster. (I can't post my actual Oracle query at the moment because it does not run in SQLfiddle; it keeps complaining about rows being ambiguously defined.)
Can the above query be made to work in Oracle somehow? If not, is there an equivalent way to do it which will run in a reasonable time?
Upvotes: 0
Views: 3313
Reputation: 1269643
One problem trying to go between MySQL and Oracle is that Oracle is simply much more powerful.
You can solve your problem using analytic functions in Oracle. These are not available in MySQL.
Here is the Oracle solution:
select a.id, a.thedate as Xdate, a.ZDate
from (select a.*,
min(case when action = 'Z' then thedate end) over
(partition by id
order by thedate
rows between current row and unbounded following
) as Zdate,
row_number() over (partition by id, action
order by thedate desc
) as seqnum
from a
) a
where a.action = 'X' and a.seqnum = 1;
You can see it on SQL Fiddle here.
Upvotes: 0
Reputation: 2467
Here other solution:
WITH temp AS (
SELECT a.id, a.date_f, a.action FROM a WHERE a.action = 'Z'
)
SELECT a.id,
max(a.date_f) as Xdate,
temp.date_f as Zdate,
a.action
FROM a
INNER JOIN temp ON temp.id = a.id AND a.date_f < temp.date_f
WHERE a.action = 'X'
GROUP BY a.id,temp.date_f,a.action;
Note: date_f is the field date. SQL Fiddle Demo.
Upvotes: 1
Reputation: 62831
This works (although it may be able to be simplified):
SELECT a.id,
max(a."date") as Xdate,
b."date" as Zdate,
a.action
FROM a INNER JOIN
(SELECT *
FROM a
WHERE a.action = 'Z') b
ON a.ID = b.ID
WHERE a."date" < b."date"
AND a.action = 'X'
GROUP BY a.id, b."date", a.action
Please note, you need to use "
around reserved words -- in this case the column date
. You also need to add all of your fields to the GROUP BY
clause. MySQL allows it without, but Oracle does not.
Edit, simplified version:
SELECT a.Id, max(a."date"), b."date", a.action
FROM a
INNER JOIN a b ON a.Id = b.Id AND b.action = 'Z'
WHERE a.action = 'X'
AND a."date" < b."date"
GROUP BY a.Id, b."date", a.action
Upvotes: 2