如何选择每组的 TOP 1 记录(分区)

Posted

技术标签:

【中文标题】如何选择每组的 TOP 1 记录(分区)【英文标题】:How to select the TOP 1 record per group (Partition) 【发布时间】:2015-05-15 10:32:53 【问题描述】:

我有一个名为 tblRoutes 的表,其中包含一个唯一的 from 和 to 路线列表(f = from;t = to):

| fCity  | fState | tCity  | tState |
|========|========|========|========|
|New York|   NY   | Miami  |   CA   |
|Houston |   TX   |New York|   NY   |
...

然后我有一个名为 tblCarrierRates 的表格,其中列出了运营商为旅行某些路线提供的一系列等级和费率:

| fCity  | fState | tCity  | tState | Tier | Rate | CarrID |  CarrName   |
|========|========|========|========|======|======|========|=============|
|New York|   NY   | Miami  |   CA   |   2  | $2.99|  ABCD  | Abracadabra |
|New York|   NY   | Miami  |   CA   |   1  | $3.00|  BUMP  | Bumpy Rides |
|Houston |   TX   |New York|   NY   |   2  | $4.00|  SLOW  |Slow Carriers|
|Houston |   TX   |New York|   NY   |   2  | $4.01|  ABCD  | Abracadabra |
...

对于 tblRoutes 中列出的每条独特路线,我正在寻找 tblCarrierRates 提供的 1 条“最佳”路线。

“最佳”的标准是最低Tier,其次是最低Rate

结果需要返回 tblCarrierRates 中显示的所有字段,因此基于上面 tblRoutes 中显示的 2 条路线,期望的结果是:

| fCity  | fState | tCity  | tState | Tier | Rate | CarrID |  CarrName   |
|========|========|========|========|======|======|========|=============|
|New York|   NY   | Miami  |   CA   |   1  | $3.00|  BUMP  | Bumpy Rides |
|Houston |   TX   |New York|   NY   |   2  | $4.00|  SLOW  |Slow Carriers|

我正在研究的方法是按升序排序Tier,然后Rate,然后是如何匹配 fCity、fState 的每个唯一组合的 TOP 1 记录、tCity 和 tState:

SELECT A.fCity, A.fState, A.tCity, A.tState, Q.Tier, Q.Rate, Q.CarrID, Q.CarrName
FROM tblRoutes As A LEFT JOIN 
    (SELECT TOP 1 B.CarrID, B.CarrName, B.fCity, B.fState, B.tCity, B.tState, B.Rate, B.Tier
    FROM tblCarrierRates As B
    ORDER BY tblCarrierRates.Tier ASC, tblCarrierRates.Rate ASC) As Q
ON (A.tState = Q.tState) AND (A.tCity = Q.tCity) AND (A.fState = Q.fState) AND (A.fCity = Q.fCity);

查询没有失败,但是你可能猜到了,我写的子查询 (Q) 只为 tblRoutes 中的每个路由返回一条记录而不是 1,所以结束结果是:

| fCity  | fState | tCity  | tState | Tier | Rate | CarrID |  CarrName   |
|========|========|========|========|======|======|========|=============|
|New York|   NY   | Miami  |   CA   |   1  | $3.00|  BUMP  | Bumpy Rides |
|Houston |   TX   |New York|   NY   |      |      |        |             |

...如您所见,休斯顿到纽约没有任何匹配项,因为我的子查询仅返回 1 个结果,而不是每条路线的 1 个。

我怎样才能达到我想要的结果?

【问题讨论】:

【参考方案1】:

我相信您正在寻找 Sql Server 和 Oracle 分析/窗口功能的等价物,例如 ROW_NUMBER() OVER (PARTITION .. ORDER BY),例如like so.

虽然这不是在 MS Access 中直接提供的,但我相信可以通过应用相关子查询来模拟 MS Access 中的行编号功能,该子查询计算具有相同“分区”的行数(定义为连接过滤器),其中每一行通过计算同一分区中的前行数进行排名,这些行数“低于”排序标准:

SELECT A.fCity, A.fState, A.tCity, A.tState, Q.Tier, Q.Rate, Q.CarrID, Q.CarrName, TheRank
FROM tblRoutes As A LEFT JOIN 
    (
      SELECT B.CarrID, B.CarrName, B.fCity, B.fState, B.tCity, B.tState, B.Rate, B.Tier, 
      (
        SELECT COUNT(*) + 1
        FROM  tblCarrierRates rnk 
        -- Partition Simulation (JOIN)
        WHERE B.fCity = rnk.fCity AND B.fState = rnk.fState 
              AND B.tCity = rnk.tCity AND B.tState = rnk.tState 
              -- ORDER BY Simulation
              AND (rnk.Tier < B.Tier OR 
                 (rnk.Tier = B.Tier AND rnk.Rate < B.Rate))) AS TheRank
      FROM tblCarrierRates As B) As Q
ON (A.tState = Q.tState) AND (A.tCity = Q.tCity) 
    AND (A.fState = Q.fState) AND (A.fCity = Q.fCity)
-- Now, you just want the top rank in each partition.
WHERE TheRank = 1;

请注意性能 - 将为每一行执行子查询。 此外,如果有平局,则将返回两行。

+1 是以行号 1 开始每个分区(因为在其分区中前面的行将为零)

编辑,取出cmets

SELECT A.fCity, A.fState, A.tCity, A.tState, Q.Tier, Q.Rate, Q.CarrID, Q.CarrName, TheRank
FROM tblRoutes As A LEFT JOIN 
    (
      SELECT B.CarrID, B.CarrName, B.fCity, B.fState, B.tCity, B.tState, B.Rate, B.Tier, 
      (
        SELECT COUNT(*) + 1
        FROM  tblCarrierRates rnk 
        WHERE B.fCity = rnk.fCity AND B.fState = rnk.fState 
              AND B.tCity = rnk.tCity AND B.tState = rnk.tState 
              AND (rnk.Tier < B.Tier OR 
                 (rnk.Tier = B.Tier AND rnk.Rate < B.Rate))) AS TheRank
      FROM tblCarrierRates As B) As Q
ON (A.tState = Q.tState) AND (A.tCity = Q.tCity) 
    AND (A.fState = Q.fState) AND (A.fCity = Q.fCity)
WHERE TheRank = 1

【讨论】:

这看起来像我所追求的,但我收到“FROM 子句中的语法错误”。它突出显示FROM tblCarrierRates As B) As Q 看来 Access 不喜欢我添加的 cmets 并中断了查询。我已经重复了我运行的确切工作查询。 我只得到 1 个结果。我已经取消了WHERE The Rank = 1,看起来它没有对 fCity、fState、tCity 和 tState 的每个独特组合进行排名 奇怪,我得到了两个,完全按照我发布的 SqlServer 小提琴 - 我使用了插入小提琴中的数据,以及 MS Access 中的上述查询 你是对的,它确实有效。我发布的表格和字段是我在计算机上实际获得的内容的缩写版本,因此我不得不对您的查询进行大量查找和替换。我想我一定没有正确转换它。【参考方案2】:

您的内部查询需要按城市和州分组。这将产生 1 每个城市州,允许外部连接加入这些字段。

独立调试您的内部查询,直到您看到您期望外部查询正常工作的结果。首先取出Top1,这样可以看到排序和分组工作正常。我会明确地将 ASC DESC 放在您的内部查询中,以便其他人知道您希望顶部工作的方向。

【讨论】:

【参考方案3】:

您可以尝试以下查询:-

SELECT fCity, fState, tCity, tState, MIN(Tier), MIN(Rate), CarrID, CarrName
FROM tblCarrierRates
GROUP BY fCity, fState, tCity, tState, CarrID, CarrName;

【讨论】:

不,这不会产生预期的结果。 我认为应该。虽然我没有执行它,但它应该是。 是的,2 个MINs 行不通,因为他们只专注于自己的领域 @GordThompson 我正在查看您在其他地方提供的关于排名的答案...我正在尝试应用它,但我无法表达我的最低等级和最低利率效果之后:A.LaneTier &gt;= B.LaneTier AND A.Rate &gt;= B.Rate***.com/questions/24405074/…

以上是关于如何选择每组的 TOP 1 记录(分区)的主要内容,如果未能解决你的问题,请参考以下文章

如何选择每组的第一行?

如何根据多个排序列选择每组的第一行?

如何选择每组的前两行并在一列中计算它们之间的差异?

从联接表中选择每组的最新数据

MySQL按顺序查找每组最近/最大的记录

如何在 SQL 中获取每组的最后一条记录