问题
I'm a bit stuck trying to figure out how to design an Oracle query, and although there are similar questions here none of them seem to quite address the issues I'm facing.
I have two tables and I want to join them:
PROJECT table
PROJECT_ID TITLE
101 First project
102 Second project
103 Third project
104 Fourth project
105 Fifth project
EVENT table
EVENT_ID PROJECT_FK EVENT_TYPE EVENT_DATE EVENT_DESC
201 101 301 2010-01-01 First event
202 101 301 2010-01-01 Second event
203 101 302 2010-01-02 Third event
204 102 301 2010-01-03 Fourth event
205 102 301 2010-01-04 Fifth event
206 104 301 2010-01-05 Sixth event
207 105 302 2010-01-06 Seventh event
I would like to get a list of each project's data (from the PROJECT table) along with details of the most recent event, but only events of a single type (all other events should be ignored.) One row, and only one row, should be returned for each project (so if multiple matching events have the same date either one is fine, and if there are no events nulls/blanks should be returned in the event fields.)
This is what the output might look like:
SELECT <???> WHERE PROJECT_ID IN (101, 102, 103, 105) /* for event type 301 only */
PROJECT_ID TITLE EVENT_DATE EVENT_DESC
101 First project 2010-01-01 First event
102 Second project 2010-01-04 Fifth event
103 Third project NULL NULL
105 Fifth project NULL NULL
I'm finding this quite tricky as the only examples I can find either assume that the max(date) is unique (but here selecting by that will return the wrong row) or they assume there is a lot of duplication so GROUP BY will work.
回答1:
Oracle 9i+, using ROW_NUMBER:
SELECT x.project_id,
x.title,
x.event_date,
x.event_desc
FROM (SELECT p.project_id,
p.title,
e.event_date,
e.event_desc,
ROW_NUMBER() OVER(PARTITION BY p.project_id
ORDER BY e.event_date) AS rank
FROM PROJECT p
LEFT JOIN EVENT e ON e.project_fk = p.project_id
AND e.event_type = 301
WHERE p.project_id IN (101,102,103)) x
WHERE x.rank = 1
Oracle 9i+, Using WITH and ROW_NUMBER:
WITH example AS (
SELECT p.project_id,
p.title,
e.event_date,
e.event_desc,
ROW_NUMBER() OVER(PARTITION BY p.project_id
ORDER BY e.event_date) AS rank
FROM PROJECT p
LEFT JOIN EVENT e ON e.project_fk = p.project_id
AND e.event_type = 301
WHERE p.project_id IN (101,102,103))
SELECT x.project_id,
x.title,
x.event_date,
x.event_desc
FROM example x
WHERE x.rank = 1
回答2:
Anytime you see something along the lines of "for event type 301 only" you should expect to see that same restriction either in a where clause or a having clause ("having" is basically a where clause for the results of a "group by").
So with that said, you might start off with something along the lines of the below and work backwords. WHERE EVENT_TYPE = 301;
Then you can fill in some basics like the table that field resides in e.g. SELECT * FROM EVENT WHERE EVENT_TYPE = 301;
Now you can start thinking about grouping together similar events according to the PROJECT_ID. To keep things simpler we'll just use the PROJECT_FK since it's the equivalent of PROJECT.PROJECT_ID. SELECT PROJECT_FK FROM EVENT WHERE EVENT_TYPE = 301 GROUP BY PROJECT_FK;
Now all of the PROJECT_FK's in the EVENT table are grouped together but we don't have any information. We're looking for the details of a single event so we could work on choosing just a single EVENT_ID when there are multiple. MIN() and MAX() will both work for this. So you could write: SELECT PROJECT_FK, MIN(EVENT_ID) FROM EVENT WHERE EVENT_TYPE = 301 GROUP BY PROJECT_FK;
The query above gave us a single event for each PROJECT_FK in the EVENT table but none of the other event details. Let's join the result set above with the EVENT table and we're most of the way towards our answer. So SELECT table2.* FROM EVENT table1 JOIN ( SELECT PROJECT_FK, MIN(EVENT_ID) FROM EVENT WHERE EVENT_TYPE = 301 GROUP BY PROJECT_FK ) table2 ON table1.PROJECT_FK = table2.PROJECT_FK;
Now we just need to pull in the PROJECT table. Whenever you see "nulls/blanks should be returned in the event fields" you should think outer join. So you could just keep chaining table joins to get the result set you want. For example SELECT * FROM PROJECT LEFT OUTER JOIN ( SELECT table2.* FROM EVENT table1 JOIN ( SELECT PROJECT_FK, MIN(EVENT_ID) FROM EVENT WHERE EVENT_TYPE = 301 GROUP BY PROJECT_FK ) table2 ON table1.PROJECT_FK = table2.PROJECT_FK; ) ON PROJECT_ID = PROJECT_FK;
来源:https://stackoverflow.com/questions/4161226/sql-join-difficulty-seem-to-need-a-way-of-limiting-rows-in-a-join-condition