MySQL按日期汇总值,比较今年和去年

时间:2016-02-03 06:40:00

标签: mysql

假设我有一张满是订单的桌子。每天可以有多个订单,总金额不同:

# Create orders table
CREATE TABLE orders (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `order_date` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
  `total` decimal(9,2) DEFAULT NULL,
  PRIMARY KEY (`id`)
);

我想为这个表创建一堆随机测试数据,所以我创建了一个存储过程:

# Create test data for orders table
DELIMITER $$
CREATE PROCEDURE prepare_orders_data()

BEGIN
  DECLARE i INT DEFAULT 0;
  DECLARE j INT DEFAULT FLOOR(RAND() * 10 + 1); # Number of order entries in one day
                                                # randomized between 1 and 10 days

  # Create 2 years worth of data from 2014-01-01 to 2015-12-31
  WHILE i < (365*2) DO
    WHILE j > 0 DO
      INSERT INTO orders(order_date, total) 
      VALUES (DATE_ADD('2014-01-01', INTERVAL 1*i DAY), RAND() * 100);

      SET j = j - 1;
    END WHILE;

    SET j = FLOOR(RAND() * 10 + 1); #Random number of days from 1-10
    SET i = i + 1;
  END WHILE;

END$$
DELIMITER ;

CALL prepare_orders_tlano_data();

既然我有数据,我想要做的是按日期获得订单总和的总和,并将它们与上一年的订单总和进行比较。前一年定义为当前日期之前的52周(我想查看由星期几确定的日期,例如:感恩节,黑色星期五,复活节等。大多数情况下,日期将是比较是休息一天(例如:2015-04-08与2015-09-08相比)。

我已经找到了解决方案,但我不相信它是最佳的。这是因为我基本上加入了同一个表,我认为有更快的方法来做到这一点。这是解决方案:

# Get totals for dates and dates from 52 weeks previous 
# (same weekday approximately 1 year in the past)
SELECT
    DATE(thisYear.order_date) AS ThisYearOrderDate,
    DATE(lastYear.order_date) AS LastYearOrderDate,
    YEAR(thisYear.order_date) as ThisYear,
    YEAR(lastYear.order_date) as LastYear,
    DAYNAME(thisYear.order_date) AS DayName,
    SUM(thisYear.total) AS ThisYearTotal,
    lastYear.total AS LastYearTotal
FROM orders thisYear 
    INNER JOIN (
        SELECT 
            order_date as order_date,
            SUM(total) as total
        FROM orders
        GROUP BY order_date
    ) lastYear
ON DATE_ADD(thisYear.order_date, INTERVAL -52 WEEK) = lastYear.order_date
GROUP BY thisYear.order_date;

以下是它将返回的示例(结果将因存储过程中的随机数据而有所不同):

+--------------+--------------+----------+---------------+---------------+
| ThisYearDate | LastYearDate | DayName  | ThisYearTotal | LastYearTotal |
+--------------+--------------+----------+---------------+---------------+
| 2015-01-01   | 2014-01-02   | Thursday | 363.56        | 11.26         |
| 2015-01-02   | 2014-01-03   | Friday   | 137.62        | 189.76        |
| 2015-01-03   | 2014-01-04   | Saturday | 399.40        | 257.42        |
| 2015-01-04   | 2014-01-05   | Sunday   | 502.80        | 336.38        |
| 2015-01-05   | 2014-01-06   | Monday   | 107.59        | 466.79        |
+--------------+--------------+----------+---------------+---------------+

有人能想出一种不同的方法来实现这一目标吗?

修改
我看了一下@Used_By_Already的解决方案并重写了一下,给它的输出与我的表相同:

SELECT
    (CASE WHEN order_date < '2014-12-31'
         THEN DATE_ADD(order_date, INTERVAL 52 WEEK)
         ELSE order_date END) as ThisYearOrderDate,
    (CASE WHEN order_date < '2014-12-31'
         THEN order_date
         ELSE DATE_ADD(order_date, INTERVAL -52 WEEK) END) as LastYearOrderDate,
     DAYNAME(order_date) DayName,
     SUM(CASE WHEN order_date >= '2014-12-31'
         THEN total ELSE 0 END) as ThisYearTotal,
     SUM(CASE WHEN order_date < '2014-12-31'
         THEN total ELSE 0 END) as LastYearTotal
FROM orders
GROUP BY ThisYearOrderDate;

这很好用,运行速度比我的解决方案快。我唯一担心的是它需要这些日期过滤器,它只能在一年的范围内工作,因为ThisYearOrderDate和LastYearOrderDate之间的任何重叠都会导致一些误导性条目:

+--------------+--------------+-----------+---------------+---------------+
| ThisYearDate | LastYearDate | DayName   | ThisYearTotal | LastYearTotal |
+--------------+--------------+-----------+---------------+---------------+
| 2014-12-31   | 2014-01-01   | Wednesday | 18.01         | 253.56        |
| 2015-01-01   | 2014-01-02   | Thursday  | 363.56        | 11.26         |
| ...          | ...          | ...       | ...           | ...           |
| 2015-12-30   | 2014-12-31   | Wednesday | 380.71        | 0.00          |
| 2015-12-31   | 2015-01-01   | Thursday  | 400.36        | 0.00          |
+--------------+--------------+-----------+---------------+---------------+

1 个答案:

答案 0 :(得分:1)

在此日期和组中使用案例表达式,并对两个总和使用类似的案例表达式(条件聚合)。

select
      case when order_date < '2016-01-01' then order_date + INTERVAL 52 WEEKS else order_date end as an_order_date_pair
    , SUM(case then order_date  < '2016-01-01' then total end) as prev_yr
    , SUM(case then order_date >= '2016-01-01' then total end) as this_yr
from orders
group by
      case when order_date < '2016-01-01' then order_date + INTERVAL 52 WEEKS else order_date end

我无法在sqlfiddle下运行MySQL,所以使用Postgres完成了以下操作,但你会看到语法非常类似于此需要:

SQL Fiddle PostgreSQL 9.3架构设置:

CREATE TABLE Orders
    (order_date timestamp , total decimal(12,3))
;

INSERT INTO Orders
    (order_date, total)
VALUES
    ('2014-01-02 00:00:00', 11.26),
    ('2014-01-03 00:00:00', 189.76),
    ('2014-01-04 00:00:00', 257.42),
    ('2014-01-05 00:00:00', 336.38),
    ('2014-01-06 00:00:00', 466.79),
    ('2015-01-01 00:00:00', 363.56),
    ('2015-01-02 00:00:00', 137.62),
    ('2015-01-03 00:00:00', 399.40),
    ('2015-01-04 00:00:00', 502.80),
    ('2015-01-05 00:00:00', 107.59)
;

查询1

select
      case when order_date < '2015-01-01'
          then order_date + INTERVAL '52 WEEKS'
          else order_date
      end as an_order_date_pair
    , SUM(case when order_date < '2015-01-01'
          then total else 0
       end) as prev_yr
    , SUM(case when order_date >= '2015-01-01'
          then total else 0
       end) as this_yr
from orders
group by
      case when order_date < '2015-01-01'
          then order_date + INTERVAL '52 WEEKS'
          else order_date
      end

<强> Results

|        an_order_date_pair | prev_yr | this_yr |
|---------------------------|---------|---------|
| January, 01 2015 00:00:00 |   11.26 |  363.56 |
| January, 05 2015 00:00:00 |  466.79 |  107.59 |
| January, 02 2015 00:00:00 |  189.76 |  137.62 |
| January, 04 2015 00:00:00 |  336.38 |   502.8 |
| January, 03 2015 00:00:00 |  257.42 |   399.4 |