繁体   English   中英

根据一个MySQL查询中的不同日期范围选择平均值语句

[英]select statement for averages based on different date ranges in one MySQL query

基本上我试图用这些数据制作图表。 我可以将我的查询放入PHP中的while循环以获得每个平均值,但我更希望这是通过一个查询生成一个结果表来完成的。

<?php 

date_default_timezone_set('America/Los_Angeles');

include('../connect.php');

$subcategory = 'T-Shirts';

$date = date('Y-m-d', strtotime('-29 days'));
$today = date("Y-m-d");

$subcategory = mysqli_real_escape_string($conp, $subcategory);

echo "<table border=\"1\">";
echo "<tr>";
echo "<th>date</th>";
echo "<th>average</th>";
echo "</tr>";

while (strtotime($date) <= strtotime($today)) {

    $from_date = date ("Y-m-d", strtotime("-29 day", strtotime($date)));

    $query = $conp->query("SELECT ROUND(SUM(OutCount)/30) AS 'average' FROM inventory
    LEFT JOIN item
    ON inventory.itemcode = item.itemcode
    WHERE item.subcategory = '$subcategory'
    AND TrDateTime BETWEEN '$from_date' AND '$date' AND transactiontype like 'OUT_%'"); 

    if($query->num_rows){       
        while($row = mysqli_fetch_array($query, MYSQL_ASSOC)){                      
            if(!empty($row['average'])){
                $average = $row['average'];
            }else{
                $average = "N/A";
            }
        }                       
        mysqli_free_result($query);                             
    }else{
        $average = "N/A";
    }

    $date = date ("Y-m-d", strtotime("+1 day", strtotime($date)));

    echo "<tr>";
    echo "<td>" . $date . "</td>";
    echo "<td>" . $average . "</td>";
    echo "</tr>";
}

echo "</table>";

?>

我获取过去30天(包括今天)的所有日期以及截至该日期前29天的平均销售额。

+------------+----------+  
| date       | average  |  
+------------+----------+  
| 2015-04-09 | 222      |  
| 2015-04-10 | 225      |  
| 2015-04-11 | 219      |  
| ...        | ...      |  
+------------+----------+  

我能够以这种方式获得我需要的所有东西,但是在这种情况下运行29次查询,MySQL会更快。 我开始提出一个MySQL程序,但我不确定当我尝试用PHP调用它时它会有多好用。

DELIMITER //
    CREATE PROCEDURE average_daily_sales()
    BEGIN

        SET @today = CURDATE();
        SET @date_var = CURDATE() - INTERVAL 29 DAY;
        SET @from_date = @date_var - INTERVAL 29 DAY;
        SET @to_date = @from_date + INTERVAL 29 DAY;

        label1: WHILE @date_var < @today DO

            SELECT      DATE_FORMAT(trdatetime, '%Y-%m-%d') as 'date', ROUND(SUM(OutCount)/30) AS 'average'
            FROM        inventory
            LEFT JOIN   item
            ON          inventory.itemcode = item.itemcode
            WHERE       item.subcategory = 'T-Shirts'
            AND         trdatetime BETWEEN @from_date - INTERVAL 29 DAY AND @to_date
            AND         transactiontype like 'OUT_%';

            SET @date_var = @date_var + INTERVAL 1 DAY;

        END WHILE label1;    

    END; //
DELIMITER ;

最后,我更喜欢一个常规的MySQL语句,我可以用它来一次性生成所需的结果表。 任何帮助将不胜感激。

如果您创建日历表并使用一系列日期值填充该日历表,例如

CREATE TABLE cal (dt DATE NOT NULL PRIMARY KEY) ;
INSERT INTO cal VALUES ('2015-04-01'),('2015-04-02'),('2015-04-03'), ... ;

你可以在这样的查询中使用它作为行源:

SELECT cal.dt
     , ( -- correlated subquery references value returned from cal
         SELECT ROUND(SUM(n.OutCount)/30)
           FROM inventory n
           JOIN item t
             ON t.itemcode = n.itemcode
          WHERE t.subcategory = 'foo'
            AND n.TrDateTime >= cal.dt + INTERVAL -28 DAY
            AND n.TrDateTime <  cal.dt + INTERVAL 1 DAY
            AND n.transactiontype LIKE 'OUT_%'
       ) AS `average`
  FROM cal
 WHERE cal.dt >= '2015-04-01'
   AND cal.dt <  '2015-05-01'
 ORDER BY cal.dt

创建cal日历表不是必需的。 我们可以使用内联视图并为其指定cal的别名。 例如,在上面的查询中,我们可以替换此行:

  FROM cal

有了这个:

  FROM ( SELECT DATE('2015-04-01') AS dt
         UNION ALL SELECT DATE('2015-04-02')
         UNION ALL SELECT DATE('2015-04-03')
         UNION ALL SELECT DATE('2015-04-04')
         UNION ALL SELECT DATE('2015-04-05')
       ) cal

或者,如果你有一个行源可以给你一个连续的整数系列,从零开始你可以从基准日期开始制造你的日期值,例如

   FROM ( SELECT '2014-04-01' + INTERVAL i.n DAY
            FROM source_of_integers i
           WHERE i.n >= 0
             AND i.n < 31
           ORDER BY i.n
        ) cal

一些说明:

原始查询显示外部( LEFT )连接,但WHERE子句中的等式谓词否定了连接的“外部性”,它等同于内部连接。

查询中的某些列引用不合格。 最佳实践是限定所有列引用,然后读者可以了解哪些列来自哪些表,而无需读者熟悉哪些列在哪些表中。 当将具有相同名称的列添加到查询中引用的另一个表时,这还可以保护语句在将来不会中断(带有“模糊列”错误)。)

跟进

就个人而言,对于有限数量的日期值,我会使用不引用表的内联视图。 我有PHP代码为我生成该查询。

有了一个开始日期,比如'2015-04-10',我会把这个日期值和格式化为一个查询,相当于这样做:

$cal = "SELECT DATE('2015-04-10') AS dt" ;

然后我旋转一个循环,并将该日期值增加1天。 每次循环时,我都会向$cal附加下一个日期的选择,通过循环三次运行的净效果相当于这样做:

$cal .= " UNION ALL SELECT DATE('2015-04-11')";
$cal .= " UNION ALL SELECT DATE('2015-04-12')";
$cal .= " UNION ALL SELECT DATE('2015-04-13')";

作为一个不太吸引人的选择,我们可以不断重复开始日期的相同值,只需增加一个整数值,让MySQL为我们做日期数学。

$cal .= " UNION ALL SELECT '2015-04-10' + INTERVAL 1 DAY";
$cal .= " UNION ALL SELECT '2015-04-10' + INTERVAL 2 DAY";
$cal .= " UNION ALL SELECT '2015-04-10' + INTERVAL 3 DAY";

然后,我只是将$cal查询作为内联视图查询滑入SQL文本。 像这样的东西:

$sql = "SELECT cal.dt
             , ( SELECT IFNULL(ROUND(SUM
                 ,0) AS average_
          FROM ( " . $cal . " ) cal
          LEFT
          JOIN item ON ... ";

无论如何,这是我采用的方法,如果这是有限数量的日期值(几十个左右),如果我只是偶尔运行此查询,而不是反复使用此查询锤击数据库服务器,对于每个请求。)如果我要敲击服务器,我将创建并维护一个真正的cal表,而不是在每个查询中产生实现派生表的开销。

您是否拥有该范围内每个不同日期的数据? 如果是这样,这是一个稍微复杂的连接操作,但非常可行。

您可以按如下方式获取所需的日期范围:

        SELECT DISTINCT
               DATE(trdatetime)- INTERVAL 30 DAY AS startdate,
               DATE(trdatetime)                  AS enddateplus1
          FROM inventory
         WHERE trdatetime >= NOW() - INTERVAL 31 DAY

调试此查询。 请查看以确保获得所需的每个日期范围。

然后,您可以将此加入到您的业务查询中

  SELECT dates.startdate, 
         ROUND(SUM(OutCount)/30) AS 'average'
   FROM (
        SELECT DISTINCT
               DATE(trdatetime)- INTERVAL 30 DAY AS startdate,
               DATE(trdatetime)                  AS enddateplus1
          FROM inventory
         WHERE trdatetime >= NOW() - INTERVAL 31 DAY
        ) dates
   LEFT JOIN inventory  ON i.trdatetime >= dates.startdate
                       AND i.trdatetime <  dates.enddateplus1 
   LEFT JOIN  item ON  i.itemcode = item.itemcode
  WHERE item.subcategory = 'T-Shirts'
    AND transactiontype like 'OUT_%'
  GROUP BY dates.startdate

如果您的库存数据稀少,也就是说,您没有所有日期的交易,那么您的日期查询将缺少某些行。

有一种方法可以填补那些缺失的行。 但这是一个痛苦的s 阅读本文以获取更多信息。 http://www.plumislandmedia.net/mysql/filling-missing-data-sequences-cardinal-integers/

请注意, BETWEEN在过滤DATETIMETIMESTAMP值方面确实非常糟糕。

来自@OllieJones和@ spencer7593的建议要么每天都要进行“事务”才能使用SELECT DISTINCT DATE(trdatetime) ,需要创建另一个表,或者需要生成派生表。

SELECT DISTINCT DATE(trdatetime)对我来说不是一个选项,因为我没有日常交易。

@ spencer7593建议的混合PHP和MySQL示例将很好地生成派生表。 最后,静态版本需要大约1.8秒才能得到结果。 问题是你需要额外的PHP来生成这个...(参见@ spencer7593答案)

SELECT cal.dt
     , ( -- correlated subquery references value returned from cal
         SELECT ROUND(SUM(n.OutCount)/30)
           FROM inventory n
           JOIN item t
             ON t.itemcode = n.itemcode
          WHERE t.subcategory = 'foo'
            AND n.TrDateTime >= cal.dt + INTERVAL -28 DAY
            AND n.TrDateTime <  cal.dt + INTERVAL 1 DAY
            AND n.transactiontype LIKE 'OUT_%'
       ) AS `average`
  FROM ( SELECT DATE('2015-04-01') AS dt
        UNION ALL SELECT DATE('2015-04-02')
        UNION ALL SELECT DATE('2015-04-03')
        UNION ALL SELECT DATE('2015-04-04')
        UNION ALL SELECT DATE('2015-04-05')
        UNION ALL SELECT DATE('2015-04-06')
etc...
       ) cal
 WHERE cal.dt >= '2015-04-01'
   AND cal.dt <  '2015-05-01'
 ORDER BY cal.dt

我试图使用另一个@ spencer7593答案。 我按照他的建议创建了一个“整数来源”表,数字为0-31。 这种方法花了1.8秒多一点。

SELECT cal.sd, cal.ed
     , ( -- correlated subquery references value returned from cal
         SELECT ROUND(SUM(n.OutCount)/30)
           FROM inventory n
           JOIN item t
             ON t.itemcode = n.itemcode
          WHERE t.subcategory = 'foobar'
            AND n.TrDateTime >= cal.ed + INTERVAL -30 DAY
            AND n.TrDateTime <  cal.ed + INTERVAL 1 DAY
            AND n.transactiontype LIKE 'OUT_%'
       ) AS `average`
  FROM ( SELECT (CURDATE() + INTERVAL -30 DAY) + INTERVAL i.n DAY as `ed`, (((CURDATE() + INTERVAL -30 DAY) + INTERVAL i.n DAY) + INTERVAL - 30 DAY) as `sd`
            FROM source_of_integers i
           WHERE i.n >= 0
             AND i.n < 31
           ORDER BY i.n
        ) cal
WHERE cal.ed >= CURDATE() + INTERVAL -29 DAY
   AND cal.ed <=  CURDATE()
 ORDER BY cal.ed;

这些日期需要一个行源,但实际上还没有办法解决这个问题。 最后我做了一张表...

CREATE TABLE cal (
    dt DATE NOT NULL PRIMARY KEY
);

CREATE TABLE ints ( i tinyint );

INSERT INTO ints VALUES (0),(1),(2),(3),(4),(5),(6),(7),(8),(9);

INSERT INTO cal (dt)
SELECT DATE('2010-01-01') + INTERVAL a.i*10000 + b.i*1000 + c.i*100 + d.i*10 + e.i DAY
FROM ints a JOIN ints b JOIN ints c JOIN ints d JOIN ints e
WHERE (a.i*10000 + b.i*1000 + c.i*100 + d.i*10 + e.i) <= 3651
ORDER BY 1;

然后在它上面运行了一个稍微修改过的@ spencer7593答案..

SELECT cal.dt
     , ( -- correlated subquery references value returned from cal
         SELECT ROUND(SUM(n.OutCount)/30)
           FROM inventory n
           JOIN item t
             ON t.itemcode = n.itemcode
          WHERE t.subcategory = 'foo'
            AND n.TrDateTime >= cal.dt + INTERVAL -28 DAY
            AND n.TrDateTime <  cal.dt + INTERVAL 1 DAY
            AND n.transactiontype LIKE 'OUT_%'
       ) AS `average`
  FROM cal
WHERE cal.dt >= CURDATE() + INTERVAL -30 DAY
    AND cal.dt <  CURDATE()
ORDER BY cal.dt;

在我看来,我相信这是最干净(较少PHP)和最高性能的答案。

以下是我对库存表进行索引以大幅加快速度的方法:

ALTER TABLE inventory ADD KEY (ItemCode, TrDateTime, TransactionType);

感谢@OllieJones和@ spencer7593的所有帮助!

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM