【问题标题】:Distinct on one column only in Oracle仅在 Oracle 中的一列上区分
【发布时间】:2013-05-03 01:57:09
【问题描述】:

我想在下表中使用 distinct,但仅限于“PlayerID”列。这是我目前拥有的:

   MATCHID   PLAYERID     TEAMID MATCHDATE STARTDATE
---------- ---------- ---------- --------- ---------
        20          5          2 14-JAN-12 01-JUN-11
        20          5          4 14-JAN-12 01-JUN-10
        20          7          4 14-JAN-12 01-JUN-11
        20          7          2 14-JAN-12 01-JUN-10
        20         10          4 14-JAN-12 01-JUN-11
        20         11          2 14-JAN-12 01-JUN-10
        20         13          2 14-JAN-12 01-JUN-11
        20         16          4 14-JAN-12 01-JUN-10
        20         17          4 14-JAN-12 01-JUN-10
        20         18          4 14-JAN-12 01-JUN-10
        20         19          2 14-JAN-12 01-JUN-11

这就是我想要的,以便显示每个“PlayerID”的最高“StartDate”并忽略下一行:

   MATCHID   PLAYERID     TEAMID MATCHDATE STARTDATE
---------- ---------- ---------- --------- ---------
        20          5          2 14-JAN-12 01-JUN-11
        20          7          4 14-JAN-12 01-JUN-11
        20         10          4 14-JAN-12 01-JUN-11
        20         11          2 14-JAN-12 01-JUN-10
        20         13          2 14-JAN-12 01-JUN-11
        20         16          4 14-JAN-12 01-JUN-10
        20         17          4 14-JAN-12 01-JUN-10
        20         18          4 14-JAN-12 01-JUN-10
        20         19          2 14-JAN-12 01-JUN-11

当前 SQL:

SELECT pi.MatchID, pi.PlayerID, t.TeamID, m.MatchDate, pf.StartDate
FROM Plays_In pi, Match m, Plays_A pa, Team t, Plays_For pf, Made_Up_Of muo, Season s
WHERE pi.MatchID = m.MatchID
AND m.MatchID = pa.MatchID
AND pa.TeamID = t.TeamID
AND pf.PlayerID = pi.PlayerID
AND pf.TeamID = t.TeamID
AND muo.MatchID = pi.MatchID
AND muo.SeasonID = s.SeasonID
AND pi.MatchID = '&match_id'
AND m.MatchDate >= pf.StartDate
ORDER BY pi.MatchID ASC, pi.PlayerID ASC, pf.StartDate DESC;

这是一个 Oracle 数据库。

提前致谢。

【问题讨论】:

标签: sql database oracle


【解决方案1】:

几点...

  • 除非您使用 Made_Up_OfSeason 的连接来过滤掉行,否则您不需要这些表。我把它们留在了这里;如果需要,您可以重新添加它们。

  • Mark Tickner 是正确的,您应该使用 ANSI JOIN 语法。它的好处(除了标准之外)是它将连接逻辑与正在连接的表正确放置。一旦你习惯了它,我想你会发现它更可取。

  • 您真正追求的是每个PlayerID 的最大pf.StartDate,它非常适合分析ROW_NUMBER() 函数。 PARTITION BY pi.PlayerID ORDER BY pf.StartDate DESC 基本上会将值 1 分配给每个玩家最近排序日期的行。外部过滤掉除1 排名之外的所有行。

  • 您还可以使用 RANK()DENSE_RANK() 分析函数分配排名,但如果玩家在最近的日期有平局,那么所有平局的日期都将排名第一,您将获得该播放器的多行。在这种情况下,您只希望每个玩家有一排,请改用ROW_NUMBER()

把它们放在一起,你就得到了:

SELECT MatchID, PlayerID, TeamID, MatchDte, StartDate FROM (
  SELECT
    pi.MatchID,
    pi.PlayerID,
    t.TeamID,
    m.MatchDate,
    pf.StartDate,
    ROW_NUMBER() OVER (PARTITION BY pi.PlayerID ORDER BY pf.StartDate DESC) AS StartDateRank
  FROM Plays_In pi
  INNER JOIN Match m ON pi.MatchID = m.MatchID
  INNER JOIN Plays_A pa ON m.MatchID = pa.MatchID
  INNER JOIN Team t ON pa.TeamID = t.TeamID
  INNER JOIN Plays_For pf ON pf.PlayerID = pi.PlayerID AND pf.TeamID = t.TeamID
  WHERE pi.MatchID = '&match_id'
  AND m.MatchDate >= pf.StartDate
)
WHERE StartDateRank = 1
ORDER BY MatchID, PlayerID

最后一点:基于WHERE pi.MatchID = '&match_id',您可能正在使用PHP 作为前端,并使用mysql 函数来执行查询。如果是这样,请查看 mysqliPDO,因为它们会保护您免受 SQL 注入。 mysql 函数(已正式弃用)不会。


附录:关于ROW_NUMBER 的更多信息,非常感谢@AndriyM。

使用ROW_NUMBER,如果玩家有多个最近日期的行,则只会将其中一行指定为ROW_NUMBER = 1,并且该行将或多或少随机选择。下面是一个示例,其中玩家的最近日期是 2013 年 5 月 1 日,并且该玩家有三行包含该日期:

pi.MatchID  pi.PlayerID  pf.StartDate
----------  -----------  ------------
      100         1000   05/01/2013 <-- could be ROW_NUMBER = 1
      101         1000   04/29/2013
      105         1000   05/01/2013 <-- could be ROW_NUMBER = 1
      102         1000   05/01/2013 <-- could be ROW_NUMBER = 1 
      107         1000   04/18/2013

请注意,上面的行中只有 一个 将被分配ROW_NUMBER = 1,并且可以是其中任何一个。 Oracle 将做出决定,而不是您。

如果这种不确定性是一个问题,请按附加列排序以获得明显的赢家。对于此示例,将使用最高的pi.MatchID 来确定“真实”ROW_NUMBER = 1

-- replace `ROW_NUMBER...` in the query above with this:
    ROW_NUMBER() OVER (
      PARTITION BY pi.PlayerID
      ORDER BY pf.StartDate DESC, pi.MatchID DESC) AS StartDateRank

现在,如果与最高 pf.StartDate 相同,Oracle 会在具有最高 pf.StartDate 的行子集中查找最高 pi.MatchID。事实证明,只有一行满足这个条件:

pi.MatchID  pi.PlayerID  pf.StartDate
----------  -----------  ------------
      100         1000   05/01/2013
      101         1000   04/29/2013
      105         1000   05/01/2013 <-- is ROW_NUMBER = 1: highest MatchID for
                                     -- most recent StartDate (5/1/2013)
      102         1000   05/01/2013
      107         1000   04/18/2013 <-- not considered: has the highest MatchID but isn't
                                     -- in the subset with the most recent StartDate

【讨论】:

  • 谢谢,稍后试试。关于 php 部分,我在 matchID 上使用它只是为了测试目的,以将结果限制为更具可读性的内容,并在发送查询后将其输入到 Oracle
  • 我喜欢你对OVER 子句如何工作的简洁解释,以及一方面RANKDENSE_RANK 与另一方面ROW_NUMBER 之间的区别。不过,关于后者,我确实认为还值得一提的是,当存在平局时,ROW_NUMBER 会不确定地分配排名,因此,如果需要可预测,则其ORDER BY 子句应该是足够具体以解决所有关系。 (我的意思是,我确实考虑过自己编辑它,但我害怕破坏你解释的非常好的流程。)
  • 感谢@AndriyM - 很好的反馈和很好的观点!我添加了一个关于平局的部分。
【解决方案2】:

你可以使用 rank() 函数。

SELECT * FROM (
    SELECT pi.MatchID, pi.PlayerID, t.TeamID, m.MatchDate, pf.StartDate,
     rank() over (partition by pi.PlayerID order by m.MatchDate desc, rowid) as RNK
    FROM Plays_In pi, Match m, Plays_A pa, Team t, Plays_For pf, Made_Up_Of muo, Season s
    WHERE pi.MatchID = m.MatchID
    AND m.MatchID = pa.MatchID
    AND pa.TeamID = t.TeamID
    AND pf.PlayerID = pi.PlayerID
    AND pf.TeamID = t.TeamID
    AND muo.MatchID = pi.MatchID
    AND muo.SeasonID = s.SeasonID
    AND pi.MatchID = '&match_id'
    AND m.MatchDate >= pf.StartDate
) WHERE RNK = 1
ORDER BY MatchID ASC, PlayerID ASC, StartDate DESC;

【讨论】:

  • 非常感谢,等我回家我再试试
【解决方案3】:

也许使用INTERSECT,然后使用GROUP BY找出MAX(StartDate)

【讨论】:

    猜你喜欢
    • 2016-06-26
    • 2017-10-10
    • 2018-04-15
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多