对现有SQL结果执行查询?从SQL结果的子集

时间:2018-04-30 20:22:44

标签: php mysql sql pdo

我有一个遍历所有订单历史记录的脚本。打印结果需要几分钟,但我注意到我执行了几个类似的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/>";
}

这样的事情可能吗?它更快吗?我现有的方法既缓慢又丑陋,我想要更快的方法来做到这一点。

2 个答案:

答案 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操作的索引。在索引中包含statustotal列将允许从索引完全满足查询。 (使用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值。

相关问题