【问题标题】:mysql sum() on a joined subquery is returning an unexpected total连接子查询上的 mysql sum() 返回意外的总数
【发布时间】:2012-11-28 09:37:12
【问题描述】:

这个SQL fiddle 说明了我遇到的问题。

作为背景:我有工作、元素、角色小时数和费率。一个 Job 可以由几个 Elements 组成。一个元素(通常)由一个或多个角色小时(即一个角色和多个小时)组成。每个角色都有一个小时费率,该费率根据日期和作业的客户而有所不同。

在上面的查询中,我试图获得一份工作的财务明细:一份工作的所有元素的列表,以及它们的总成本。事实上,目前它也在按角色分解这些元素,但这并不是最终查询所必需的。

您可以看到“角色成本”列正确地将小时费率乘以预算小时数,以达到该角色的小计。但是,当我尝试对这些字段求和时(在“元素小计”列中),我得到......好吧,这不是我期望的数字。

我怀疑问题出在获取最新费率的子查询上,我已将其设置为separate SQL Fiddle here 以供参考。它为一个角色返回了多个可能的速率:当它重新连接到主查询中时,它会汇总太多行。

因此扭曲我的甜瓜的问题是:我需要匹配给定客户的“最佳”费率。也就是说,如果有一个匹配公司 ID 客户 ID 的费率,我想要那个。但如果没有,我只想要与公司 ID 匹配的那个。如果没有其中之一,我只想要该角色的“基本”费率。因此,我的联接中的所有“OR __ IS NULL”。

我不知道该怎么做是将它与“只返回一条记录”结合起来,我需要使 SUM() 部分工作。

为这篇长文道歉。如果你已经走到这一步,谢谢。

【问题讨论】:

  • 据我所知,将rates 表中的三个client_* 列中的每一个与jobs 表中的列进行比较时,有 4 种可能的结果:它们要么匹配,要么执行不匹配,一侧为NULL 或另一侧为NULL。在这三列中,有 64 种可能的结果。您正试图对这 64 种可能的结果进行排序,rates.date_from 列以某种方式被用来打破平局。但是,我有点不清楚该排序到底应该是什么:您的倒数第二段似乎与您的查询不一致。请澄清。
  • 我猜不一致是因为子查询没有做我想要的:)理想情况下它应该匹配费率表中的 single 记录,但究竟是哪一个取决于它找到的匹配项。为工作匹配率的“优先级”应该是:(1)同一公司、集团和客户(2)同一公司和集团(空客户)(2)同一公司(空集团和客户)客户匹配应始终优先于日期。因此,在所有三列上都匹配的旧记录应该优先于刚刚匹配的新记录,例如公司。

标签: mysql join subquery sum


【解决方案1】:

一种方法是correlated subquery

SELECT   e.id AS element_id,
         h.role,
         SUM(h.hours_budgeted) AS total_hours_budgeted,
         r.hourly_rate,
         e.pm_amount,
         e.revenue AS fixed_revenue,
         e.revenue_extra,
         SUM(h.hours_budgeted) * r.hourly_rate AS element_subtotal
FROM     job                    j
    JOIN job_element            e ON e.job     = j.id
    JOIN job_element_role_hours h ON h.element = e.id
    JOIN rate                   r ON r.id      = (
           SELECT   id
           FROM     rate
           WHERE    rate.role = h.role
                AND IFNULL(rate.client_company = j.client_company, TRUE)
                AND IFNULL(rate.client_group   = j.client_group  , TRUE)
                AND IFNULL(rate.client_contact = j.client_contact, TRUE)
           ORDER BY rate.client_company DESC,
                    rate.client_group   DESC,
                    rate.client_contact DESC,
                    rate.date_from      DESC
           LIMIT    1
         )
WHERE    j.id = 1
GROUP BY e.id, h.role

sqlfiddle 上查看。

但是,相关子查询效率低下并且可能很慢。正如手册所说:

将查询重写为连接可能会提高性能。

为此,必须获得groupwise maximum

SELECT   e.id AS element_id,
         h.role,
         SUM(h.hours_budgeted) AS total_hours_budgeted,
         r.hourly_rate,
         e.pm_amount,
         e.revenue AS fixed_revenue,
         e.revenue_extra,
         SUM(h.hours_budgeted) * r.hourly_rate AS element_subtotal
FROM     job                    j
    JOIN job_element            e ON e.job     = j.id
    JOIN job_element_role_hours h ON h.element = e.id
    JOIN rate                   r ON r.role    = h.role
           AND IFNULL(r.client_company = j.client_company, TRUE)
           AND IFNULL(r.client_group   = j.client_group  , TRUE)
           AND IFNULL(r.client_contact = j.client_contact, TRUE)
    JOIN (
      SELECT   j.client_company, j.client_group, j.client_contact, r.role,
               MAX(
                 IF(r.client_company <=> j.client_company, 1<<34, 0)
               | IF(r.client_group   <=> j.client_group  , 1<<33, 0)
               | IF(r.client_contact <=> j.client_contact, 1<<32, 0)
               | UNIX_TIMESTAMP(r.date_from)
               ) AS relevance
      FROM     rate r JOIN job j ON
                     IFNULL(r.client_company = j.client_company, TRUE)
                 AND IFNULL(r.client_group   = j.client_group  , TRUE)
                 AND IFNULL(r.client_contact = j.client_contact, TRUE)
      GROUP BY j.client_company, j.client_group, j.client_contact, r.role
    ) t     ON t.role = r.role
           AND t.client_company = j.client_company
           AND t.client_group   = j.client_group
           AND t.client_contact = j.client_contact
           AND t.relevance  = IF(r.client_company <=> j.client_company, 1<<34, 0)
                            | IF(r.client_group   <=> j.client_group  , 1<<33, 0)
                            | IF(r.client_contact <=> j.client_contact, 1<<32, 0)
                            | UNIX_TIMESTAMP(r.date_from)
WHERE    j.id = 1
GROUP BY e.id, h.role

sqlfiddle 上查看。

在这里,我通过计算相关性分数找到了与您的尝试类似的分组最大值。但是,我进行了一些操作,其中 234 表示client_company 上是否有匹配项,client_group 上的 233 和 232 在client_contact 上,32 个最低位表示速率的date_from——然后取最大相关性分数将产生最佳匹配的分数,再次加入rate 表可以得到hourly_rate 根据需要。

人们甚至可以进一步改进这一点,以避免计算相关性分数,方法是嵌套以按顺序在每列上找到分组最大值;但是,除非您遇到无法以任何其他方式解决的性能问题,否则可能不值得走这条路。您可以在my answer to another question 中看到该技术。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-04-08
    • 1970-01-01
    • 2013-01-04
    • 1970-01-01
    • 1970-01-01
    • 2011-12-03
    相关资源
    最近更新 更多