【问题标题】:SQL aggregate functions and sortingSQL 聚合函数和排序
【发布时间】:2016-10-04 05:33:21
【问题描述】:

我还是 SQL 的新手,我正在研究整个子查询聚合以显示一些结果,并正在寻找一些建议:

表格可能类似于:

Customer: (custID,  name, address)
Account: (accountID, reward_balance)
Shop: (shopID, name, address)

关系表:

Holds (custID*, accountID*)
With (accountID*, shopID*)

如何找到reward_balance 最少的商店? (此时不需要客户信息)

我试过了:

SELECT accountID AS ACCOUNT_ID, shopID AS SHOP_ID, MIN(reward_balance) AS LOWEST_BALANCE
FROM Account, Shop, With
WHERE With.accountID = Account.accountID
AND With.shopID=Shop.shopID
GROUP BY
Account.accountID,
Shop.shopID
ORDER BY MIN(reward_balance);

这是一种非预期的方式:

ACCOUNT_ID | SHOP_ID | LOWEST_BALANCE
 1         |   1     |   10
 2         |   2     |   40
 3         |   3     |   100
 4         |   4     |   1000
 5         |   4     |   5000

您可以看到 Shop_ID 4 实际上有 6000 (1000+5000) 的余额,因为有两个客户在其中注册。我想我需要根据他们的余额对商店的最低余额求和,并从低到高显示。

我一直在尝试在显示之前汇总数据,但这就是我失败的地方:

SELECT shopID AS SHOP_ID, MIN(reward_balance) AS LOWEST_BALANCE
FROM (SELECT accountID, shopID, SUM(reward_balance) 
     FROM Account, Shop, With
     WHERE 
         With.accountID = Account.accountID
         AND With.shopID=Shop.shopID
         GROUP BY
             Account.accountID,
             Shop.shopID;

当我运行类似这样的语句时,我得到一个无效的标识符错误。

Error at Command Line : 1 Column : 24
Error report -
SQL Error: ORA-00904: "REWARD_BALANCE": invalid identifier
00904. 00000 -  "%s: invalid identifier"

所以我认为我的加入条件可能不正确并且聚合排序不正确,并且非常感谢任何一般性建议。

感谢您的长时间阅读!

【问题讨论】:

  • 尝试在您的内部选择中为SUM(reward_balance) As reward_balance 添加别名。
  • Hey Uporabnik003,我根据您的建议对其进行了编辑,但现在出现“列不明确定义”错误。
  • 我不熟悉 Oracle,但尝试将其命名为 sum_reward_balance,然后更正主要语句。也许它需要一个唯一的名称,而不是与原始列名相同。

标签: sql oracle aggregate


【解决方案1】:

一步一步解决这个问题。

我们将假设(并且我们可能应该检查一下)至少 reward_balance,它指的是与商店相关的所有 reward_balance 的total。而且我们不只是在寻找个人奖励余额最低的商店。

首先,获取每个商店的所有个人“reward_balance”。看起来查询需要涉及三个表...

SELECT s.shop_id
     , a.reward_balance
  FROM `shop` s
  LEFT
  JOIN `with` w
    ON w.shop_id = s.shop_id 
  LEFT
  JOIN `account` a
    ON a.account_id = w.account_id

这将为我们提供详细信息行、每家商店以及与该商店关联的个人奖励余额金额(如果有)。 (我们为此查询使用外连接,因为我们看不到任何保证商店将与至少一个帐户相关。即使对于这个用例来说是正确的,但在更一般的情况下并不总是正确的情况。)

一旦我们有了单独的金额,下一步就是将每个商店的金额合计。我们可以使用GROUP BY 子句和SUM() 聚合来做到这一点。

SELECT s.shop_id
     , SUM(a.reward_balance) AS tot_reward_balance
  FROM `shop` s
  LEFT
  JOIN `with` w
    ON w.shop_id = s.shop_id 
  LEFT
  JOIN `account` a
    ON a.account_id = w.account_id
 GROUP BY s.shop_id

此时,在 MySQL 中,我们可以添加一个 ORDER BY 子句来按 tot_reward_balance 的升序排列行,如果我们只想返回一行,则添加一个 LIMIT 1 子句。我们还可以处理 tot_reward_balance 为 NULL 的情况,用零代替 NULL。

SELECT s.shop_id
     , IFNULL(SUM(a.reward_balance),0) AS tot_reward_balance
  FROM `shop` s
  LEFT
  JOIN `with` w
    ON w.shop_id = s.shop_id 
  LEFT
  JOIN `account` a
    ON a.account_id = w.account_id
 GROUP BY s.shop_id
 ORDER BY tot_reward_amount ASC, s.shop_id ASC
 LIMIT 1

如果有两个(或更多)商店的 tot_reward_amount 最小值相同,则此查询仅返回其中一个商店。

Oracle 没有像 MySQL 那样的 LIMIT 子句,但是我们可以使用解析函数得到等效的结果(这在 MySQL 中不可用)。我们还将 MySQL IFNULL() 函数替换为 Oracle 等效的 NVL() 函数...

SELECT v.shop_id
     , v.tot_reward_balance
     , ROW_NUMBER() OVER (ORDER BY v.tot_reward_balance ASC, v.shop_id ASC) AS rn
  FROM ( 
         SELECT s.shop_id
              , NVL(SUM(a.reward_balance),0) AS tot_reward_balance
           FROM shop s
           LEFT
           JOIN with w
             ON w.shop_id = s.shop_id 
           LEFT
           JOIN account a
             ON a.account_id = w.account_id
          GROUP BY s.shop_id
       ) v
HAVING rn = 1

与 MySQL 查询一样,这最多返回一行,即使两个或多个商店的reward_balance 总数“最少”相同。

如果我们想退回所有 tot_reward_balance 最低的商店,我们需要采取稍微不同的方法。


构建查询的最佳方法是逐步细化;在这种情况下,首先获取每个商店的所有个人reward_amount。下一步是将个人的reward_amount 汇总为一个总数。接下来的步骤是挑选总奖励金额最低的行。

【讨论】:

  • 感谢 spencer7593 的精彩建议。我真的很感激花时间来演示步骤构建并包括 NULL 对象等的可能性。
  • @Ben:其他人可以一口气编写出多行、多步的 SQL 语句。我没那么聪明。我必须一步一步来,并在此过程中验证每一步。 “迭代查询开发”(我称之为)是我采用的方法,它对我有用。我认为它使我能够发现问题,并在此过程中解决它们。当我遇到问题时,我可以后退一步,然后去解决它。 (我不够聪明,无法调试“为什么这个巨大的 SQL 语句没有返回我期望的结果”而不对其进行逆向工程。我只是“正向工程”我的 SQL 语句。YMMV。
  • 您可能会发现我的 SQL 语句的格式很奇怪。这是一种适合我的格式。例如。我可以轻松地在我的 IDE 中突出显示内联视图查询并执行它。我可以很容易地看到内联视图查询返回了两列。 SQL 优化器不关心格式,它不关心额外的中断、额外的空白、parens 排队。我以这种方式格式化语句,以便在我必须返回它时对其进行破译。
【解决方案2】:

在 SQL Server 中,您可以尝试使用 CTE:

  ;with cte_minvalue as
    (
     select rank() over (order by Sum_Balance) as RowRank,
     ShopId,
     Sum_Balance
      from (SELECT Shop.shopID, SUM(reward_balance) AS Sum_Balance 
         FROM 
             With
             JOIN Shop ON With.ShopId = Shop.ShopId
             JOIN Account ON With.AccountId = Account.AccountId
         GROUP BY
            Shop.shopID)ShopSum
      )
      select ShopId, Sum_Balance from cte_minvalue where RowRank = 1

【讨论】:

  • 嘿,阿米特,感谢您的帮助,对不起,我是堆栈溢出的新手,没有注意到自动标记,但我使用的是 Oracle 而不是 SQL 服务器抱歉。我现在删除了不正确的标签。
猜你喜欢
  • 1970-01-01
  • 2018-10-12
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多