sql - 如何优化复杂查询?

标签 sql mysql query-optimization greatest-n-per-group

我正在开发一个营销型系统。在首页上,其中一项要求是让销售人员看到他们当前拥有的销售机会数量。

即。

Birthdays     | 10
Anniversaries | 15
Introductions | 450
Recurring     | 249

问题是我正在UNION所有这些并且在某些情况下查询占用了 10 多秒。 (我们有缓存,所以这只是用户当天第一次登录时的问题)。

还有很多其他的标准:

  • 计数中应该只包含每个客户每种类型的最新一个(即,如果一个客户有两个介绍,则应该只计算一次 - 我正在使用 greatest-n-per-group 方法来完成此操作)
  • 对于生日和纪念日,日期应该是从今天起 +/- 7 天
  • 对于所有这些,只计算过去60天的记录
  • 这些记录需要与客户表相结合,以确保机会的销售人员与客户当前的销售人员匹配

这是生成的查询(很长):

SELECT 'Birthdays' AS `type`, COUNT(*) AS `num` 
FROM `opportunities` 
INNER JOIN `customers` 
    ON `opportunities`.`customer_id` = `customers`.`customer_id` 
    AND `opportunities`.`sales_person_id` = `customers`.`sales_person_id` 
LEFT JOIN `opportunities` AS `o2` 
    ON `opportunities`.`customer_id` = `o2`.`customer_id` 
    AND `opportunities`.`marketing_message` = `o2`.`marketing_message` 
    AND opportunities.communication_alert_date < o2.communication_alert_date 
WHERE ((`opportunities`.`org_code` = ?)) 
AND (opportunities.marketing_message = 'Birthday Alert') 
AND ((opportunities.communication_alert_date BETWEEN 
    DATE_SUB(NOW(), INTERVAL 7 DAY) AND DATE_ADD(NOW(), INTERVAL 7 DAY))) 
AND (opportunities.communication_alert_date >= DATE_SUB(NOW(), INTERVAL 60 DAY)) 
AND (o2.customer_id IS NULL) 

UNION ALL 

SELECT 'Anniversaries' AS `type`, COUNT(*) AS `num` 
FROM `opportunities` 
INNER JOIN `customers` 
    ON `opportunities`.`customer_id` = `customers`.`customer_id` 
    AND `opportunities`.`sales_person_id` = `customers`.`sales_person_id` 
LEFT JOIN `opportunities` AS `o2` 
    ON `opportunities`.`customer_id` = `o2`.`customer_id` 
    AND `opportunities`.`marketing_message` = `o2`.`marketing_message` 
    AND opportunities.communication_alert_date < o2.communication_alert_date 
WHERE ((`opportunities`.`org_code` = ?)) 
AND (opportunities.marketing_message = 'Anniversary Alert') 
AND ((opportunities.communication_alert_date BETWEEN 
    DATE_SUB(NOW(), INTERVAL 7 DAY) AND DATE_ADD(NOW(), INTERVAL 7 DAY))) 
AND (opportunities.communication_alert_date >= DATE_SUB(NOW(), INTERVAL 60 DAY)) 
AND (o2.customer_id IS NULL) 

UNION ALL 

SELECT 'Introductions' AS `type`, COUNT(*) AS `num` 
FROM `opportunities` 
INNER JOIN `customers` 
    ON `opportunities`.`customer_id` = `customers`.`customer_id` 
    AND `opportunities`.`sales_person_id` = `customers`.`sales_person_id` 
LEFT JOIN `opportunities` AS `o2` 
    ON `opportunities`.`customer_id` = `o2`.`customer_id` 
    AND `opportunities`.`marketing_message` = `o2`.`marketing_message` 
    AND opportunities.communication_alert_date < o2.communication_alert_date 
WHERE ((`opportunities`.`org_code` = ?)) 
AND ((opportunities.Intro_Letter = 'Yes')) 
AND (opportunities.communication_alert_date >= DATE_SUB(NOW(), INTERVAL 60 DAY)) 
AND (o2.customer_id IS NULL) 

UNION ALL 

SELECT 'Recurring' AS `type`, COUNT(*) AS `num` 
FROM `opportunities` 
INNER JOIN `customers` 
    ON `opportunities`.`customer_id` = `customers`.`customer_id` 
    AND `opportunities`.`sales_person_id` = `customers`.`sales_person_id` 
LEFT JOIN `opportunities` AS `o2` 
    ON `opportunities`.`customer_id` = `o2`.`customer_id` 
    AND `opportunities`.`marketing_message` = `o2`.`marketing_message` 
    AND opportunities.communication_alert_date < o2.communication_alert_date 
WHERE ((`opportunities`.`org_code` = ?)) 
AND ((opportunities.marketing_message != 'Anniversary Alert' 
AND opportunities.marketing_message != 'Birthday Alert' 
AND opportunities.Intro_Letter != 'Yes')) 
AND (opportunities.communication_alert_date >= DATE_SUB(NOW(), INTERVAL 60 DAY)) 
AND (o2.customer_id IS NULL)

我在 opportunities 表中有以下字段的索引:

  • 组织代码
  • 客户编号
  • 介绍信
  • 营销信息
  • sales_person_id
  • org_code, marketing_message
  • 组织代码、介绍信
  • org_code、marketing_message、Intro_Letter

如果能帮助优化这个,我们将不胜感激。如果需要,我愿意创建其他表或 View 。

最佳答案

一个好的开始是删除字符串比较并将它们放入一个分配了 ID 的表中,并在

的位置添加数字列
opportunities.marketing_message != 'Birthday Alert'

所以你会有...

[id]    [name]
1       Birthday Alert
2       Anniversary

数字比较总是更快,即使有索引。这样做还可以让您在未来轻松添加新类型。

这部分是多余的,您不需要 AND (opportunities.communication_alert_date >= DATE_SUB(NOW(), INTERVAL 60 DAY)) 因为它之前的子句会完成这项工作。

AND ((opportunities.communication_alert_date BETWEEN 
    DATE_SUB(NOW(), INTERVAL 7 DAY) AND DATE_ADD(NOW(), INTERVAL 7 DAY))) 
AND (opportunities.communication_alert_date >= DATE_SUB(NOW(), INTERVAL 60 DAY))

关于sql - 如何优化复杂查询?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/4219490/

相关文章:

SQL - 多个条目在 c1 中显示一个实例及其在 c2 中的对应映射

php - 查询匹配多个关键字并显示结果?

php - 如何从laravel中的mysql存储过程中获取多个结果集

sql - mysql统计性能

sql - Django select_related() 用于使用模型和过滤器进行多连接查询

php - PHP while 循环中包含大量数据的 rowspan

mysql - 帮我解决这个 MySql 游标代码

mysql - 优化 ORDER BY

mysql - 优化GROUP BY&ORDER BY查询

mysql - 加入群组的方式是?