【发布时间】:2017-02-06 14:18:14
【问题描述】:
我有以下查询来获取一年中 52 周的数据的平均值如下:
$dates = array();
$firstDate = date("Y-m-d", strtotime('first day of January 2016'));
$lastDate = date("Y-m-d", strtotime('last day of December 2016'));
for($i=strtotime($firstDate); $i<=strtotime($lastDate); $i+=86400 *7){
array_push($dates, date("Y-m-d", strtotime('monday this week', $i)));
}
for($i = 0; $i < count($dates); $i++){
$sql = "SELECT pr_products.product,
CONCAT(YEAR('".$dates[$i]."'),'-',LPAD(WEEK('".$dates[$i]."'),2,'0')) AS Week,
SUM(IF(sw_sowing.type = 'SW', sw_sowing.quantity,0)) AS PlantSowing,
SUM(IF(ROUND(DATEDIFF(TIMESTAMPADD(DAY,(6 WEEKDAY('".$dates[$i]."')),'".$dates[$i]."'), sw_sowing.date)/7) >= pr_products.week_production AND sw_sowing.type = 'SW',sw_sowing.quantity,0)) AS production
FROM (
SELECT max(sw_sowing.id) AS id
FROM sw_sowing
WHERE sw_sowing.status != 0
AND sw_sowing.id_tenant = :id_tenant
AND sw_sowing.status = 100
AND sw_sowing.date <= TIMESTAMPADD(DAY,(6-WEEKDAY('".$dates[$i]."')),'".$dates[$i]."')
GROUP BY sw_sowing.id_production_unit_detail
) AS sw
INNER JOIN sw_sowing ON sw_sowing.id = sw.id
INNER JOIN pr_products ON pr_products.id = sw_sowing.id_product
INNER JOIN pr_varieties ON sw_sowing.id_variety = pr_varieties.id
INNER JOIN pr_lands ON pr_lands.id = sw_sowing.id_land
WHERE pr_varieties.code != 1
AND sw_sowing.id_product = 1
AND sw_sowing.status = 100
GROUP BY pr_products.product
HAVING plantSowing > 0
ORDER BY pr_products.product";
}
我最初声明了两个变量,$firstdate 是开始日期,$lastDate 是结束日期。
然后我做一个 for 遍历这两个日期,并将每周星期一的日期保存在一个数组中。
然后我遍历那个新数组以每周获取我需要的数据。
注意:在查询中,变量 $dates[$i] 是每周的星期一日期。
无论如何,该查询运行良好,因为它为我提供了一年中 52 周所需的数据。问题是这需要一段时间。
我已经在mysql中索引了表,我改进了一点但还不够,查询实际上并不重,平均每个周期需要0.60秒。
我想知道是否有可能删除我正在做的事情并在查询中添加我不知道的WHERE,它比较两个日期并为我带来数据,或者是否有任何改进查询的方法。
我已经用答案的建议更新了查询:
$data = array();
$start = new DateTime('first monday of January 2016');
$end = new DateTime('last day of December 2016');
$datePeriod = new DatePeriod($start , new DateInterval('P7D') , $end);
$sql = "SELECT product AS product,
Week AS label,
ROUND(SUM(harvest)/SUM(production),2) AS value
FROM (
(
SELECT pr_products.product,
CONCAT(YEAR(:dates),'-', LPAD(WEEK(:dates1),2,'0')) AS Week,
SUM(IF(sw_sowing.type = 'SW', sw_sowing.quantity,0)) AS PlantSowing,
SUM(IF(ROUND(DATEDIFF(TIMESTAMPADD(DAY,(6-WEEKDAY(:dates2)),:dates3), sw_sowing.date)/7) >= pr_products.week_production AND sw_sowing.type = 'SW',sw_sowing.quantity,0)) AS production,
0 AS Harvest
FROM (
SELECT max(sw_sowing.id) AS id
FROM sw_sowing
WHERE sw_sowing.status != 0
AND sw_sowing.date <= TIMESTAMPADD(DAY,(6-WEEKDAY(:dates4)),:dates5)
GROUP BY sw_sowing.id_production_unit_detail
) AS sw
INNER JOIN sw_sowing ON sw_sowing.id = sw.id
INNER JOIN pr_products ON pr_products.id = sw_sowing.id_product
INNER JOIN pr_varieties ON sw_sowing.id_variety = pr_varieties.id
WHERE pr_varieties.code != 1
AND sw_sowing.id_product = 1
AND sw_sowing.status = 100
AND sw_sowing.id_tenant = :id_tenant
GROUP BY pr_products.product
HAVING plantSowing > 0
ORDER BY pr_products.product
)
UNION ALL
(
SELECT pr_products.product,
CONCAT(YEAR(:dates6),'-', LPAD(WEEK(:dates7),2,'0')) AS Week,
0 AS plantSowing,
0 AS Production,
SUM(pf_harvest.quantity) AS Harvest
FROM pf_harvest
INNER JOIN pr_products ON pr_products.id = pf_harvest.id_product
INNER JOIN pr_varieties ON pr_varieties.id = pf_harvest.id_variety
INNER JOIN pf_performance ON pf_performance.id = pf_harvest.id_performance
WHERE pf_harvest.date BETWEEN TIMESTAMPADD(DAY,(0-WEEKDAY(:dates8)),:dates9)
AND TIMESTAMPADD(DAY,(6-WEEKDAY(:dates10)),:dates11)
AND pr_varieties.code != 1
AND pf_harvest.id_product = 1
AND pf_performance.status = 100
AND pf_harvest.id_tenant = :id_tenant1
GROUP BY pr_products.product
ORDER BY pr_products.product
)
) AS sc
GROUP BY product, label
ORDER BY label";
$statement = $this->db->prepare($sql);
$id_tenant = $this->getIdTenant();
foreach($datePeriod AS $dates){
$values = [
':dates' => $dates->format('Y-m-d'),
':dates1' => $dates->format('Y-m-d'),
':dates2' => $dates->format('Y-m-d'),
':dates3' => $dates->format('Y-m-d'),
':dates4' => $dates->format('Y-m-d'),
':dates5' => $dates->format('Y-m-d'),
':dates6' => $dates->format('Y-m-d'),
':dates7' => $dates->format('Y-m-d'),
':dates8' => $dates->format('Y-m-d'),
':dates9' => $dates->format('Y-m-d'),
':dates10' => $dates->format('Y-m-d'),
':dates11' => $dates->format('Y-m-d'),
':id_tenant' => $id_tenant,
':id_tenant1' => $id_tenant
];
$result = $this->db->executePrepared($statement , $values);
$data[] = $result->fetchAll();
}
$this -> jsonReturnSuccess($data);
【问题讨论】:
-
在我看来,当您开发通过相同 sql 进行迭代的查询时,您遇到了常见的 N+1 问题。 stackoverflow.com/questions/97197/…。我对您的建议是重构您的查询以使用 weekday() 函数来识别星期一(星期一返回为 0)并将所有行检索到一个数据集中。我没有分析您的查询,也不太了解您的数据集,无法为您重构它,但这应该是可能的。将数据放入数组后,您可以循环遍历它以在 PHP 中获取所需的数据。
-
我正在尝试重构我的查询,我想删除 for,但我认为这很难,谢谢你的建议!
-
如果您可以为正在连接的表提供一些示例数据以及结果输出的前几行,我可以试一试。
标签: php mysql sql prepared-statement