【问题标题】:Consolidate 2 queries with similar where clauses but different aggregate function targets合并 2 个具有类似 where 子句但不同聚合函数目标的查询
【发布时间】:2016-05-04 16:35:48
【问题描述】:

我有 2 个单独的查询可以正常工作。鉴于它们相似,我想将它们合并为一个高性能查询。看起来很简单,因为 where 子句是相似的。但是 sum、count 和 min 函数都适用于不同的行并妨碍了。

上下文:

  • 用户可以对位置进行评分(或评分)并获得积分
  • 当用户 B 首次提交分数时,用户 A 可以推荐用户 B 并获得推荐积分
  • 积分在特定日期后过期
  • 目标是建立一个用户排行榜以及他们在特定位置(地区/国家)的得分和推荐总分
  • 位置参数用“Massachusetts”、“United States”和 scoreDateTime 到期日期的硬值填充,不幸的是在两个选择子查询中重复。

问题:

如何重新组织以下查询以组合约束?必须有一种方法可以从特定日期后特定位置的分数列表开始。唯一的麻烦是获取用户 B 的第一个得分日期,并且仅在过期日期之后才向用户 A 提供推荐积分。

select scoring.userId, scoring.points + referring.points as leaderPoints
from (
    select   userId, sum(ratingPoints) as points
    from     scores s, locations l
    where    s.locationId = l.locationId and
             l.locationArea = 'Massachusetts' and
             l.locationCountry = 'United States' and
             s.scoreDateTime > '2016-04-16 18:50:53.154' and
             s.userId != 0
    group by s.userId
) as scoring

join (
    select u1.userId, count(*) * 20 as points
    from users u0
    join users u1 on u0.userId = u1.userId
    join users u2 on u2.referredByEmail = u1.emailAddress
    join scores s on u2.userId = s.userId
    join locations l on s.locationId = l.locationId
    where    l.locationArea = 'Massachusetts' and
             l.locationCountry = 'United States' and
             scoreDateTime = (
                 select min(scoreDateTime)
                 from   scores
                 where  userId = u2.userId
             ) and
             scoreDateTime >= '2016-04-16 18:50:53.154'
    group by u1.userId
) as referring on scoring.userId = referring.userId
order by leaderPoints desc
limit 10;

【问题讨论】:

    标签: mysql sql performance join


    【解决方案1】:

    这是未经测试的代码,但它应该可以解决问题。 Cross Apply 是为了可读性...它会损害性能,但这似乎不是一个特别需要处理的查询,所以我会保留它。

    请尝试一下,如果您有任何问题,请告诉我。

    SELECT  U.UserID, 
            ISNULL(SUM(CASE WHEN S.UserID IS NULL THEN 0 ELSE S.ratingPoints END), 0) AS [Rating Points], 
            ISNULL(SUM(CASE WHEN SS.userID IS NULL THEN 0 ELSE 20 END), 0) AS [Referral Points]
    FROM Users U
    LEFT OUTER JOIN scores S
        ON S.userID = U.userID
        AND S.scoreDateTime >= '2016-04-16 18:50:53.154'
    LEFT OUTER JOIN locations L
        ON S.locationID = L.locationID
        AND L.locationArea = 'Massachusetts'
        AND L.LocationCountry = 'United States'
    LEFT OUTER JOIN Users U2
        ON U2.referredByEmail = U.emailAddress
    LEFT OUTER JOIN scores SS
        ON SS.userID = U2.userID
    LEFT OUTER JOIN locations LL
        ON SS.locationID = LL.locationID
        AND LL.locationArea = 'Massachusetts'
        AND LL.locationCountry = 'United States'
        AND SS.scoreDateTime >= '2016-04-16 18:50:53.154'
        AND SS.scoreDateTime = 
            (
                SELECT MIN(scoreDateTime)
                FROM scores
                where userID = U2.userID
            )
    GROUP BY U.userID
    

    编辑:

    修改答案以删除交叉应用

    【讨论】:

    • 我正在使用 MySQL,显然 CROSS APPLY 似乎并不适用。我希望合并 locationArea、locationCountry 和 scoreDateTime 约束,因为它们是重复的。我到达该分数列表之外的唯一时间是获取被推荐用户的第一个分数日期时间的 min(scoreDateTime)。如果该日期在截止日期之后,我只想奖励推荐奖励积分。
    • @jmelvin 我已修改答案以删除 Cross Apply。
    【解决方案2】:

    感谢 Stan Shaw,但我无法让您的查询在 MySQL 上运行以测试结果。但是,我确实注意到了原始查询未涵盖的特殊情况。用户可以从他们自己没有提交分数的区域获得参考点。只要新用户在该区域得分,他们就会在该区域获得推荐积分。

    这是我使用的最后一个查询。我无法以看似高效的方式整合重复的 where 子句。

    select userId, sum(points) as leaderPoints
    from (
    
        select   s.userId, sum(s.ratingPoints) as points
        from     scores s, locations l
        where    s.locationId = l.locationId and  
                 l.locationArea = 'Georgia' and  
                 l.locationCountry = 'United States' and  
                 s.scoreDateTime >= '2016-04-05 03:00:00.000' and  
                 s.userId != 1
        group by userId
    
        union
    
        select   u1.userId, 20 as points
        from     users u0, users u1, users u2, scores s, locations l
        where    u0.userId = u1.userId and
                 u2.referredByEmail = u1.emailAddress and
                 u2.userId = s.userId and
                 s.locationId = l.locationId and
                 l.locationArea = 'Georgia' and  
                 l.locationCountry = 'United States' and  
                 scoreDateTime >= '2016-04-05 03:00:00.000' and
                 scoreDateTime = (
                     select min(scoreDateTime) 
                     from   scores  
                     where  userId = u2.userId  
                 )
    
    ) as pointsEarned
    group by userId
    order by leaderPoints desc
    limit 10
    order by leaderPoints desc  
    limit 100;
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2021-07-25
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多