【问题标题】:Use Access SQL to do a grouped ranking使用 Access SQL 进行分组排名
【发布时间】:2011-05-26 16:11:39
【问题描述】:

我如何按# 个客户按部门分组(包括关系)对销售人员进行排名?

例如,给定这张表,我想在右侧创建排名列。我应该如何在 Access 中执行此操作?

销售人员部门#客户排名 比尔 DeptA 20 1 泰德 DeptA 30 2 简 DeptA 40 3 比尔部门 B 50 1 玛丽部门 B 60 2

我已经知道如何用这个 SQL 代码做一个简单的排名。但我不知道如何修改它以接受分组。

Select Count(*) from [Tbl] Where [#Customers] <  [Tblx]![#Customers] )+1

此外,使用 SQL Server 的 Rank() 函数有很多答案,但我需要在 Access 中执行此操作。请给点建议?

【问题讨论】:

    标签: sql ms-access


    【解决方案1】:

    我通常从这里挑选技巧和想法,有时最终会从中创造出令人惊叹的东西!

    今天,(比如说过去一周),我一直在修改 Access 中的数据排名,并且尽我所能,我没想到我会做一些如此复杂的事情我一个星期搞清楚!我从两个主要网站挑选了花絮:

    1. https://usefulgyaan.wordpress.com/2013/04/23/ranking-in-ms-access/(看到那个聪明的 '>=' 部分,并且 self 加入?太棒了……它帮助我从一个查询中构建了我的解决方案,而不是 asonoftheMighty 上面建议的复杂方法(不是抹黑你……只是没有'暂时不想尝试;可能当我处理大数据时,我可能也想尝试一下……)
    2. 就在这里,来自上面的 Paul Abott ('and tbl.dept = tbl2.dept')...我在排名后迷路了,因为我放置 AND YearID = 1 等,然后排名最终只会发生在 sub-集,你猜对了,当 YearID = 1 时!但我有很多不同的场景……

    嗯,我讲这个故事部分是为了感谢提到的贡献者,因为我所做的对我来说是最复杂的排名之一,我认为它几乎可以在任何情况下帮助你,而且由于我从别人那里受益,我想在这里分享我希望对其他人也有帮助的东西。

    请原谅我不能在这里发布我的表结构,它有很多相关的表。我只会发布查询,因此如果您需要,您可以开发您的表格以完成这种查询。但这是我的场景:

    您有一所学校的学生。它们经过 1 到 4 级,可以在 A 流或 B 流中,或者当班级太小时没有。他们每人参加 4 门考试(这部分现在不重要了),所以你可以得到我案例的总分。而已。嗯??

    好的。让我们这样排列它们:

    我们想知道排名

    • 所有通过这所学校的学生(有史以来最好的学生)

    • 特定学年的所有学生(年度最佳学生)

    • 特定班级的学生(但请记住,学生将通过所有班级,因此基本上他/她在不同年份的每个班级中的排名)这是成绩单中出现的通常排名

    • 学生在他们的信息流中(以上评论适用)

    • 我还想知道我们在每个类别中对该学生进行排名的人群

    …全部在一个表/查询中。现在你明白了吗?

    (我通常喜欢在数据库/查询中进行尽可能多的“编程”,以提供视觉效果并减少我稍后必须修改的代码量。我实际上不会在我的应用程序中使用此查询:),但它让我知道在哪里以及如何将我的参数发送到它来自的查询,以及在我的 rdlc 中预期的结果)

    别担心,这里是:

    SELECT Sc.StudentID, Sc.StudentName, Sc.Mark, 
    (SELECT COUNT(Sch.Mark) FROM [StudentScoreRankTermQ] AS Sch WHERE (Sch.Mark >= Sc.Mark)) AS SchoolRank, 
    (SELECT Count(s.StudentID) FROM StudentScoreRankTermQ AS s) As SchoolTotal, 
    (SELECT COUNT(Yr.Mark) FROM [StudentScoreRankTermQ] AS Yr WHERE (Yr.Mark >= Sc.Mark) AND (Yr.YearID = Sc.YearID) ) AS YearRank, 
    (SELECT COUNT(StudentID) FROM StudentScoreRankTermQ AS Yt WHERE (Yt.YearID = Sc.YearID) ) AS YearTotal, 
    (SELECT COUNT(Cl.Mark) FROM [StudentScoreRankTermQ] AS Cl WHERE (Cl.Mark >= Sc.Mark) AND (Cl.YearID = Sc.YearID) AND (Cl.TermID = Sc.TermID)  AND (Cl.ClassID=Sc.ClassID)) AS ClassRank, 
    (SELECT COUNT(StudentID) FROM StudentScoreRankTermQ AS C WHERE (C.YearID = Sc.YearID) AND (C.TermID = Sc.TermID) AND (C.ClassID = Sc.ClassID) ) AS ClassTotal, 
    (SELECT COUNT(Str.Mark) FROM [StudentScoreRankTermQ] AS Str WHERE (Str.Mark >= Sc.Mark)  AND (Str.YearID = Sc.YearID) AND (Str.TermID = Sc.TermID)  AND (Str.ClassID=Sc.ClassID) AND (Str.StreamID = Sc.StreamID) ) AS StreamRank, 
    (SELECT COUNT(StudentID) FROM StudentScoreRankTermQ AS St WHERE (St.YearID = Sc.YearID) AND (St.TermID = Sc.TermID)  AND (St.ClassID = Sc.ClassID) AND (St.StreamID = Sc.StreamID) ) AS StreamTotal, 
    Sc.CalendarYear, Sc.Term, Sc.ClassNo, Sc.Stream, Sc.StreamID, Sc.YearID, Sc.TermID, Sc.ClassID
    FROM StudentScoreRankTermQ AS Sc
    ORDER BY Sc.Mark DESC;
    

    你应该得到这样的东西:

    +-----------+-------------+------+------------+-------------+----------+-----------+-----------+------------+------------+-------------+------+------+-------+--------+
    | StudentID | StudentName | Mark | SchoolRank | SchoolTotal | YearRank | YearTotal | ClassRank | ClassTotal | StreamRank | StreamTotal | Year | Term | Class | Stream |
    +-----------+-------------+------+------------+-------------+----------+-----------+-----------+------------+------------+-------------+------+------+-------+--------+
    | 1         | Jane        | 200  | 1          | 20          | 2        | 12        | 1         | 9          | 1          | 5           | 2017 | I    | 2     | A      |
    | 2         | Tom         | 199  | 2          | 20          | 1        | 12        | 3         | 9          | 1          | 4           | 2016 | I    | 1     | B      |
    +-----------+-------------+------+------------+-------------+----------+-----------+-----------+------------+------------+-------------+------+------+-------+--------+
    

    使用分隔符| 重构结果表

    只是关于表格的想法,每个学生都与一个班级相关。每个类都与年份有关。每个流都与一个类有关。每个术语与一年有关。每门考试都与一个学期和一个学生以及一个班级和一年有关;一个学生可以在 2016 年进入 1A 班并在 2017 年进入 2b 班,等等……

    我还要补充一点,这是一个测试版结果,我没有对其进行足够好的测试,我还没有机会创建大量数据来查看性能。我第一眼看到它就告诉我它很好。因此,如果您找到想要为我指明方向的原因或提醒,请在 cmets 中这样做,以便我继续学习!

    【讨论】:

      【解决方案2】:

      我知道这是一个旧线程。但是由于我在一个非常相似的问题上花了很多时间,并且在这里给出的以前的答案给了我很大的帮助,所以我想分享我发现的一种更快的方法。 (注意,它更复杂。)

      首先制作另一个名为“Individualizer”的表。这将有一个字段包含从 1 到您需要的最高排名的数字列表。

      接下来创建一个 VBA 模块并将其粘贴到其中:

      'Global Declarations Section.
      Option Explicit
      Global Cntr
      
      '*************************************************************
      ' Function:  Qcntr()
      '
      ' Purpose: This function will increment and return a dynamic
      ' counter. This function should be called from a query.
      '*************************************************************
      
      Function QCntr(x) As Long
         Cntr = Cntr + 1
         QCntr = Cntr
      End Function
      
      '**************************************************************
      ' Function:  SetToZero()
      '
      ' Purpose: This function will reset the global Cntr to 0. This
      ' function should be called each time before running a query
      ' containing the Qcntr() function.
      '**************************************************************
      
      Function SetToZero()
         Cntr = 0
      End Function
      

      将其保存为 Module1。

      接下来,像这样创建 Query1:

      SELECT Table1.Dept, Count(Table1.Salesperson) AS CountOfSalesperson
      FROM Table1
      GROUP BY Table1.Dept;
      

      像这样创建一个名为 Query2 的 MakeTable 查询:

      SELECT SetToZero() AS Expr1, QCntr([ID]) AS Rank, Query1.Dept, 
      Query1.CountOfSalesperson, Individualizer.ID 
      INTO Qtable1
      FROM Query1 
      INNER JOIN Individualizer 
         ON Query1.CountOfSalesperson >= Individualizer.ID;
      

      像这样创建另一个名为 Query3 的 MakeTable 查询:

      SELECT SetToZero() AS Expr1, QCntr([Identifier]) AS Rank, 
      [Salesperson] & [Dept] & [#Customers] AS Identifier, Table1.Salesperson, 
      Table1.Dept, Table1.[#Customers] 
      INTO Qtable2
      FROM Table1;
      

      如果您已经有另一个字段可以唯一标识每一行,则您不需要创建标识符字段。

      运行 Query2 和 Query3 以创建表。 像这样创建名为 Query4 的第四个查询:

      SELECT Qtable2.Salesperson, Qtable2.Dept, Qtable2.[#Customers], Qtable1.ID AS Rank
      FROM Qtable1 
      INNER JOIN Qtable2 ON Qtable1.Rank = Qtable2.Rank;
      

      Query4 返回您要查找的结果。

      实际上,您可能希望编写一个 VBA 函数来运行 Query2 和 Query3,然后通过放置在方便位置的按钮调用该函数。

      现在我知道这对于你给出的例子来说听起来非常复杂。但在现实生活中,我相信你的桌子比这更复杂。希望我的例子可以应用于您的实际情况。在我有超过 12,000 条记录的数据库中,这种方法是 FAR 最快的(例如:12,000 条记录需要 6 秒,而使用子查询方法对 262 条记录进行排序需要 1 分钟以上)。

      对我来说真正的秘密是 MakeTable 查询,因为除非您立即将结果输出到表中,否则这种排名方法是无用的。但是,这确实限制了它可以应用于的情况。

      附:我忘了提到在我的数据库中我没有直接从表中提取结果。这些记录在需要进行排名之前已经经过了一系列查询和多次计算。在我的情况下,这可能极大地导致了两种方法之间的速度差异。如果您直接从表中提取记录,您可能不会注意到几乎有那么大的改进。

      【讨论】:

      • 感谢您的贡献。郑重声明,您所说的“Individualizer”通常称为Numbers table
      • 其实当时的任务和我的例子差别不大,只是重复了几次。想象一下,查看一年的销售额,然后按按月和产品分组的总销售额对每个销售员进行排名。排名方法是整个过程的最大瓶颈,但是数据库的小规模(如果我记得的话和你的很接近)使它无法成为阻碍者。如果我必须在 Access 中再次执行此操作,我会记住您的建议。
      【解决方案3】:

      子查询的绝佳解决方案!除了巨大的记录集,子查询解决方案变得非常缓慢。使用 Self JOIN 更好(更快),请查看以下解决方案:self join

      SELECT tbl1.SalesPerson , count(*) AS Rank 
      FROM tbl AS tbl1 INNER JOIN tbl AS tbl2 ON tbl1.DEPT = tbl2.DEPT 
          AND tbl1.#Customers < tbl2.#Customers 
      GROUP BY tbl1.SalesPerson 
      

      【讨论】:

        【解决方案4】:

        要添加到此和任何其他相关 Access Ranking 或 Rank Tie Breaker how-tos 其他版本的 Access,如果您的 FROM 子句碰巧不包含表但查询是交叉表查询或在其他地方包含交叉表查询的查询。

        上面引用的代码在 SELECT 语句中使用了 SELECT 语句(子查询),

         "SELECT *, (select count(*) from tbl as tbl2 where tbl.customers > tbl2.customers and tbl.dept = tbl2.dept) + 1 as rank from tbl"
        

        将不起作用,并且总是无法在无法找到 "tbl.customers > tbl2.customers" 的部分代码上表达错误。

        在我过去的项目中,我引用的是查询而不是表,并且在该查询中我引用了交叉表查询,因此失败并产生错误。我能够通过首先从交叉表查询创建一个表来解决这个问题,当我在 FROM 子句中引用新创建的表时,它开始为我工作。

        所以在最后,通常您可以在 SELECT 语句的 FROM 子句中引用查询或表,就像之前在上面共享的那样进行排名,但要小心如果您引用的是查询而不是表,那查询必须是交叉表查询或引用另一个作为交叉表查询的查询。

        如果您碰巧引用了上面的语句并且您没有在自己的项目中的 FROM 子句中引用表,希望这可以帮助其他可能在寻找可能原因时遇到问题的人。此外,在 Access 中使用交叉表查询对别名执行子查询可能不是一个好主意或最佳实践,因此如果/可能的话,请远离那个。

        如果您觉得这很有用,并希望 Access 允许在 passthru 查询编辑器中使用滚动鼠标,请给我一个赞。

        【讨论】:

          【解决方案5】:
          SELECT *, (select count(*) from tbl as tbl2 where
          tbl.customers > tbl2.customers and tbl.dept = tbl2.dept) + 1 as rank from tbl
          

          只需将 dept 字段添加到子查询中...

          【讨论】:

          • 它是否正确订购它们?有趣 - 不知道 Access 可以做到这一点。请问什么版本?此外,除非您更新数据库中的字段,否则您将比您希望的更频繁地运行它。
          【解决方案6】:

          你需要做一些数学运算。我通常利用计数器字段和“偏移”字段的组合。你的目标是一个看起来像这样的表(#Customers 不是必需的,但会给你一个视觉上你做得很好):

          SalesPerson Dept #Customers Ctr Offset
          Bill        DeptA     20    1   1
          Ted         DeptA     30    2   1
          Jane        DeptA     40    3   1
          Bill        DeptB     50    4   4
          Mary        DeptB     60    5   4
          

          所以,要给出排名,你会做 [Ctr]-[Offset]+1 AS Rank

          1. SalesPersonDeptCtrOffset建一个表
          2. 插入到该表中,按Dept#Customers 排序(以便它们都正确排序)
          3. Offset 更新为MIN(Ctr),在Dept 上分组
          4. 执行数学计算以确定Rank
          5. 清理表格,以便下次再次使用它。

          【讨论】:

          • 我不知道它是否可行,但 pjabbott 的解决方案更易于使用并准备就绪。
          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2019-03-26
          • 2023-03-06
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多