SQL JOIN difficulty - seem to need a way of limiting rows in a join condition

∥☆過路亽.° 提交于 2019-12-02 03:58:39

问题


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

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!