我有一个遍历所有订单历史记录的脚本。打印结果需要几分钟,但我注意到我执行了几个类似的SQL语句,我想知道你是否可以对现有的SQL结果进行另一个查询。
例如:
-- first SQL request
SELECT * FROM orders
WHERE status = 'shipped'
然后,在foreach循环中,我想从此结果中查找信息。我天真的方法是执行这三个查询。请注意与上述查询的相似性。
-- grabs customer's LTD sales
SELECT SUM(total) FROM orders
WHERE user = :user
AND status = 'shipped'
-- grabs number of orders customer has made
SELECT COUNT(*) FROM orders
WHERE user = :user
AND status = 'shipped'
AND total != 0
-- grabs number of giveaways user has won
SELECT COUNT(*) FROM orders
WHERE user = :user
AND status = 'shipped'
AND total = 0
当我寻找的结果是第一个查询的子集时,我最终多次查询同一个表。我想在不执行更多SQL调用的情况下从第一个查询中获取信息。一些伪代码:
$stmt1 = $db->prepare("
SELECT * FROM orders
WHERE status = 'shipped'
");
$stmt1->execute();
foreach($stmt1 as $var) {
$username = $var['username'];
$stmt2 = $stmt1->workOn("
SELECT SUM(total) FROM this
WHERE user = :user
");
$stmt2->execute(array(
':user' => $username
));
$lifesales = $stmt2->fetchColumn();
$stmt3 = $stmt1->workOn("
SELECT COUNT(*) FROM this
WHERE user = :user
AND total != 0
");
$stmt3->execute(array(
':user' => $username
));
$totalorders = $stmt3->fetchColumn();
$stmt4 = $stmt1->workOn("
SELECT COUNT(*) FROM this
WHERE user = :user
AND total = 0
");
$stmt4->execute(array(
':user' => $username
));
$totalgaws = $stmt4->fetchColumn();
echo "Username: ".$username;
echo "<br/>Lifetime Sales: ".$lifesales;
echo "<br/>Total Orders: ".$totalorders;
echo "<br/>Total Giveaways: ".$totalgaws;
echo "<br/><br/>";
}
这样的事情可能吗?它更快吗?我现有的方法既缓慢又丑陋,我想要更快的方法来做到这一点。
答案 0 :(得分:4)
我们可以通过表格进行一次传递,以获取所有用户的所有三个聚合:
SELECT s.user
, SUM(s.total) AS `ltd_sales`
, SUM(s.total <> 0) AS `cnt_prior_sales`
, SUM(s.total = 0) AS `cnt_giveaways`
FROM orders s
WHERE s.status = 'shipped'
GROUP
BY s.user
在大型设备上这将是昂贵的。但是,如果我们对所有订单都需要,对于所有用户来说,这可能比单独的相关子查询更快。
前导列为user
的索引将允许MySQL使用GROUP BY
操作的索引。在索引中包含status
和total
列将允许从索引完全满足查询。 (使用status
列上的等式谓词,我们还可以尝试使用status
作为前导列的索引,然后是user
列,然后是total
。
如果我们只需要一小部分用户使用此结果,例如我们只从第一个查询中获取前10行,然后运行单独的查询可能会更快。我们只需将条件WHERE s.user = :user
合并到查询中,就像在原始代码中一样。但只运行一个查询而不是三个单独的查询。
我们可以将它与第一个查询结合起来,方法是将其放入内联视图中,将其包装在parens中并作为行源放入FROM
子句
SELECT o.*
, t.ltd_sales
, t.cnt_prior_sale
, t.cnt_giveaways
FROM orders o
JOIN (
SELECT s.user
, SUM(s.total) AS `ltd_sales`
, SUM(s.total <> 0) AS `cnt_prior_sales`
, SUM(s.total = 0) AS `cnt_giveaways`
FROM orders s
WHERE s.status = 'shipped'
GROUP
BY s.user
) t
ON t.user = o.user
WHERE o.status = 'shipped'
我不确定那个名为&#34;之前&#34;销售......这将返回所有已发货的订单,而不考虑比较任何日期(订单日期,履行日期,发货日期),我们通常将这些日期与先前的&#34;之前的概念相关联。装置
关注
注意到问题已被修改,删除条件&#34; status = 'shipped'
&#34;来自用户的所有订单的计数......
我将注意到我们可以将条件从WHERE
子句移动到条件聚合中。
并非OP需要所有这些结果,而是作为示范......
SELECT s.user
, SUM(IF(s.status='shipped',s.total,0)) AS `ltd_sales_shipped`
, SUM(IF(s.status<>'shipped',s.total,0)) AS `ltd_sales_not_shipped`
, SUM(s.status='shipped' AND s.total <> 0) AS `cnt_shipped_orders`
, SUM(s.status='canceled') AS `cnt_canceled`
, SUM(s.status='shipped' AND s.total = 0) AS `cnt_shipped_giveaways`
FROM orders s
GROUP
BY s.user
答案 1 :(得分:1)
从数据库返回结果后,您无法在它们之上运行SQL。但是,您可以将它们存储在临时表中,以便重复使用它们。
https://dev.mysql.com/doc/refman/8.0/en/create-temporary-table.html https://dev.mysql.com/doc/refman/8.0/en/create-table-select.html https://dev.mysql.com/doc/refman/8.0/en/insert-select.html
您需要创建一个临时表,并插入select语句中的所有数据,然后您可以在该表上运行查询。不确定它对你的情况有多大帮助。
对于您的特定情况,您可以执行以下操作:
select user, (total = 0) as is_total_zero, count(*), sum(total)
from orders
where status = 'shipped'
group by user, total = 0
但是你必须做一些额外的求和才能得到第二个查询的结果,它会给你每个用户的总和,因为它们会分成两个不同的组,并且具有不同的is_total_zero值。