子查询上的Groupwise MAX()

时间:2013-02-12 20:48:32

标签: sql sql-server sqlite

我正在尝试计算返回表中的最大值,以及该表中的其他值。但是,我正在执行此操作的表不是“真正的”表,它是由子查询生成的表。这给了我一些问题,因为我不认为我可以加入它两次,而不重新指定整个子查询。

我目前有一个SQL Server解决方案,使用ROW_NUMBER() OVER (PARTITION BY providerId ORDER BY partnershipSetScore DESC) rnk,但我正在寻找一个DBMS不可知版本,如果可能的话,因为项目的单元测试在没有这个的Sqlite DB中运行功能。

以下是架构和我的SQL Server特定查询,以防它们有用:

课程:

  • int id
  • varchar name
  • int schoolId

学校:

  • int id
  • varchar name

伙伴关系:

  • int id
  • varchar partnershipName

SchoolPartnership:

  • int id
  • int schoolId
  • int partnershipId

以下是查询:

SELECT
    schoolId,
    partnershipId AS bestPartnershipSetId,
    partnershipScore AS bestPartnershipScore
FROM
(
    SELECT
        pp.schoolId,
        partnershipScores.partnershipId,
        partnershipScores.partnershipScore,
        ROW_NUMBER() OVER (PARTITION BY schoolId ORDER BY partnershipScore DESC) rnk
    FROM schoolPartnership pp
    INNER JOIN (
        SELECT
            pp.partnershipId,
            (
                (CASE WHEN SUM(CASE WHEN c.name LIKE '%French%' THEN 1 ELSE 0 END) > 0 THEN 1 ELSE 0 END)
                + (CASE WHEN SUM(CASE WHEN c.name LIKE '%History%' THEN 1 ELSE 0 END) > 0 THEN 1 ELSE 0 END)
            ) AS partnershipScore
        FROM schoolPartnership pp
        INNER JOIN course c ON c.schoolId = pp.schoolId
        GROUP BY partnershipId
    ) AS partnershipScores ON partnershipScores.partnershipId = pp.partnershipId
) AS schoolPartnershipScores
WHERE rnk = 1

如果您需要有关我正在尝试实现的内容的更多信息,请参阅Custom sorting algorithm for a large amount of data:此查询将是一个较大查询的子查询,该查询通过最合适的合作关系对学校进行排序。

4 个答案:

答案 0 :(得分:1)

也许,当谈到加入子查询两次时,你会想到这种技巧:

SELECT a.*
FROM atable a
INNER JOIN (
  SELECT
    col1,
    MAX(col2) AS max_col2
  FROM atable
  GROUP BY col1
) m
ON a.col1 = m.col1 AND a.col2 = m.max_col2
;

使用DBMS不可知的方式(至少在SQL Server和SQLite中工作)来完成这项工作如果这是关于单个表的话,那就完全没问题了

相反,你有一个子查询。但是,我看不到其他任何方法可以完成您的要求。因此,在这种情况下,我可以看到两个选项(一个可能在您的特定情况下不适用,但通常仍然是一个选项):

  1. 执行您要避免的操作,即专门为查找每个组的聚合值复制子查询,然后将其连接回相同的子查询,如上所述。

  2. 暂时保留子查询的结果,然后将上述技术应用于临时结果集。

  3. 第一种选择确实非常有吸引力,所以不那么好,因为希望第二种选择可能有效。

    第二个选项的一个问题是临时数据集在SQL Server和SQLite中的实现方式不同。在SQLite中,您使用CREATE TEMPORARY TABLE语句。 SQL Server在TEMPORARY语句的上下文中不支持CREATE TABLE关键字,而是在表名的开头使用特殊字符(#)来表示表实际上是临时表。

    因此,我能看到的唯一解决方法是使用普通表作为临时存储。您可以创建一次,然后在每次运行查询时删除其内容,就在插入临时结果集之前:

    DELETE FROM TempTable;
    INSERT INTO TempTable (
      schoolId,
      bestPartnershipSetId,
      bestPartnershipScore
    )
    SELECT
      pp.schoolId,
      partnershipScores.partnershipId,
      partnershipScores.partnershipScore,
    FROM
      ...
    ;
    SELECT ...
    FROM TempTable
    ...
    ;
    

    或者你可以创造&每次运行查询时都将其删除:

    CREATE TABLE TempTable (
      ...
    );
    INSERT INTO TempTable (...)
    SELECT ...
    FROM ...
    ;
    SELECT ...
    FROM TempTable
    ...
    ;
    DROP TABLE TempTable;
    

    请注意,在SQL Server中使用普通表作为临时存储不是并发友好的。如果这可能会造成问题,您可能不得不放弃此选项并最终获得第一个选项。 (但是,当您需要独立于平台的解决方案时,这可能是您必须支付的成本,尤其是当平台与SQL Server和SQLite不同时。)

答案 1 :(得分:0)

这是你想要的结构:

with t as (<subquery goes here>)
select t.*,
       max(col) over () as MaxVal
from t

有点难以看出它如何适合您的查询,因为我无法分辨基本子查询是什么。

对于不止一次加入子查询,可以使用SQL Server调用的“公用表表达式” - 上面的with子句来实现。大多数其他合理的数据库都支持这一点(MySQL和MS Access开始有两个值得注意的例外)。

答案 2 :(得分:0)

最与SQL无关的方法是使用'NON EXISTS':

SELECT * FROM schoolPartnership t1
WHERE NOT EXISTS 
       (SELECT * FROM schoolPartnership t2 
        WHERE t1.schoolId = t2.schoolId 
              AND t1.partnershipScore < t2.partnershipScore)

这将为您提供来自schoolPartnership的行,每个schoolId具有max partnershipScore。

答案 3 :(得分:0)

我无法找到解决方案(除了重复子查询,这是我试图避免的),因此我刚刚在PHP中为每个partnershipScore确定了MAX行,并抛弃了任何其他行。不是一个理想的解决方案,但由于我需要一个跨平台的方法,因此我没有太多其他选择。