【问题标题】:Perform query on existing SQL result? Find result from subset of SQL result对现有 SQL 结果执行查询?从 SQL 结果的子集中查找结果
【发布时间】:2018-04-30 20:22:43
【问题描述】:

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

这样的事情可能吗?它更快吗?我现有的方法既慢又丑,我想要一种更快的方法。

【问题讨论】:

  • 您可以将查询结果存储在临时表中,然后对该表执行其他查询以执行进一步过滤。

标签: php mysql sql pdo


【解决方案1】:

我们可以遍历表来获取所有用户的所有三个聚合:

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 合并到查询中,就像在原始代码中一样。但只运行一个查询而不是三个单独的查询。


我们可以将它与第一个查询结合起来,将其制成一个内联视图,将其包装在括号中并作为行源放入 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'

我不确定名为“之前”销售的列...这是返回所有已发货的订单,而不考虑比较任何日期(订单日期、履行日期、发货日期),我们通常会将其与“之前”是什么意思的概念。


跟进

注意到问题被修改,从用户所有订单的计数中删除条件“status = 'shipped'”...

我会注意到,我们可以将条件从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

【讨论】:

  • 我认为您的代码中有一些拼写错误:SUM(s.total &lt;&gt; 0) AS cnt_prior_sales 应该是COUNT()。另一个也一样...
  • 不是错字。 条件聚合 这是 MySQL 的简写。将为每一行计算表达式s.total &lt;&gt; 0,并返回 1(为真)、0(为假)或 NULL。获取该表达式的 SUM() 与获取该条件为真的行数相同。更便携的符合 ANSI 标准的版本SUM(CASE WHEN s.total &lt;&gt; 0 THEN 1 WHEN s.total = 0 THEN 0 ELSE NULL END)
  • 感谢您的回复。关于您的编辑,status = 'shipped' 仍然存在,只是在stmt1 中。在我的伪代码中,其他查询由 stmt1 组成,因此隐含了 status = 'shipped'
  • @gator:我没能回答你问的问题...一旦stmt1 被执行,不,不可能对stmt1 的结果运行查询。可以将该集合实现为临时表或派生表(将 SQL 包装在括号中,并在 FROM 子句中引用该表代替表)。外部查询(在同一 SQL 语句中)可以从派生表中查询。但这并没有解决同时获取聚合(计数)详细信息行的问题。在内联视图中进行聚合可以让这些行“匹配”到外部查询中的详细行。
  • @gator,将整个表达式包装在IFNULL( expr, 0) 中。这相当于更便携的符合 ANSI 标准的 CASE WHEN expr IS NULL THEN 0 ELSE expr END 。我使用 MySQL 速记版本,因为我不想重复 expr 两次。)...IFNULL( SUM(s.status = 'shipped' AND s.total &lt;&gt; 0) ,0) AS ...
【解决方案2】:

一旦从数据库返回结果,您就不能在它们之上运行 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 值。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2023-03-10
    • 2018-10-17
    • 1970-01-01
    • 2014-10-15
    • 1970-01-01
    • 1970-01-01
    • 2020-05-04
    相关资源
    最近更新 更多