使用 MySQL 生成按货币分组的填空每日销售报告

Posted

技术标签:

【中文标题】使用 MySQL 生成按货币分组的填空每日销售报告【英文标题】:Using MySQL to generate daily sales reports with filled gaps, grouped by currency 【发布时间】:2011-02-13 23:58:24 【问题描述】:

我正在尝试使用 mysql 5.1.45 为在线商店创建我认为相对基本的报告

商店可以接收多种货币的付款。 我已经创建了一些包含数据的示例表,并试图生成一个按日期和货币分组的简单表格结果集,以便我可以绘制这些数字。

我想查看每个日期可用的每种货币,如果当天没有使用该货币的销售,则结果为 0。 如果我能做到这一点,我也想做同样的事情,但也要按产品 ID 分组。

在我提供的示例数据中,只有 3 种货币和 2 个产品 ID,但实际上每种都可以有任意数量。

我可以按日期正确分组,但是当我添加按货币分组时,我的查询没有返回我想要的。

我的工作基于this article。

我的报告查询,仅按日期分组:

SELECT calendar.datefield AS date,
   IFNULL(SUM(orders.order_value),0) AS total_value
FROM orders 
RIGHT JOIN calendar ON (DATE(orders.order_date) = calendar.datefield)
WHERE (calendar.datefield BETWEEN (SELECT MIN(DATE(order_date)) FROM orders) AND  (SELECT MAX(DATE(order_date)) FROM orders))
GROUP BY date

现在按日期和货币分组:

SELECT calendar.datefield AS date, orders.currency_id,
   IFNULL(SUM(orders.order_value),0) AS total_value
FROM orders 
RIGHT JOIN calendar ON (DATE(orders.order_date) = calendar.datefield)
WHERE (calendar.datefield BETWEEN (SELECT MIN(DATE(order_date)) FROM orders) AND (SELECT MAX(DATE(order_date)) FROM orders))
GROUP BY date, orders.currency_id

我得到的结果(按日期和货币分组):

+------------+-------------+-------------+
| date       | currency_id | total_value |
+------------+-------------+-------------+
| 2009-08-15 |           3 |       81.94 |
| 2009-08-15 |          45 |       25.00 |
| 2009-08-15 |          49 |      122.60 |
| 2009-08-16 |        NULL |        0.00 |
| 2009-08-17 |          45 |       25.00 |
| 2009-08-17 |          49 |      122.60 |
| 2009-08-18 |           3 |       81.94 |
| 2009-08-18 |          49 |      245.20 |
+------------+-------------+-------------+

想要的结果

+------------+-------------+-------------+
| date       | currency_id | total_value |
+------------+-------------+-------------+
| 2009-08-15 |           3 |       81.94 |
| 2009-08-15 |          45 |       25.00 |
| 2009-08-15 |          49 |      122.60 |
| 2009-08-16 |           3 |        0.00 |
| 2009-08-16 |          45 |        0.00 |
| 2009-08-16 |          49 |        0.00 |
| 2009-08-17 |           3 |        0.00 |
| 2009-08-17 |          45 |       25.00 |
| 2009-08-17 |          49 |      122.60 |
| 2009-08-18 |           3 |       81.94 |
| 2009-08-18 |          45 |        0.00 |
| 2009-08-18 |          49 |      245.20 |
+------------+-------------+-------------+

我在测试中使用的架构和数据:

CREATE TABLE orders
(
  id INT PRIMARY KEY AUTO_INCREMENT,
  order_date DATETIME,
  order_id INT,
  product_id INT,
  currency_id INT,
  order_value DECIMAL(9,2),
  customer_id INT
);
INSERT INTO orders (order_date, order_id, product_id, currency_id, order_value, customer_id)
  VALUES
  ('2009-08-15 10:20:20', '123', '1', '45', '12.50', '322'),
  ('2009-08-15 12:30:20', '124', '1', '49', '122.60', '400'),
  ('2009-08-15 13:41:20', '125', '1', '3', '40.97', '324'),  
  ('2009-08-15 10:20:20', '126', '2', '45', '12.50', '345'),
  ('2009-08-15 13:41:20', '131', '2', '3', '40.97', '756'),

  ('2009-08-17 10:20:20', '3234', '1', '45', '12.50', '1322'),
  ('2009-08-17 10:20:20', '4642', '2', '45', '12.50', '1345'),
  ('2009-08-17 12:30:20', '23', '2', '49', '122.60', '3142'),

  ('2009-08-18 12:30:20', '2131', '1', '49', '122.60', '4700'),
  ('2009-08-18 13:41:20', '4568', '1', '3', '40.97', '3274'),  
  ('2009-08-18 12:30:20', '956', '2', '49', '122.60', '3542'),
  ('2009-08-18 13:41:20', '443', '2', '3', '40.97', '7556');

CREATE TABLE currency
  (
    id INT PRIMARY KEY,
    name VARCHAR(255)
  );
INSERT INTO currency (id, name)
  VALUES
  (3, 'Euro'),
  (45, 'US Dollar'),
  (49, 'CA Dollar');


CREATE TABLE calendar (datefield DATE);

  DELIMITER |
  CREATE PROCEDURE fill_calendar(start_date DATE, end_date DATE)
  BEGIN
    DECLARE crt_date DATE;
    SET crt_date=start_date;
    WHILE crt_date < end_date DO
      INSERT INTO calendar VALUES(crt_date);
      SET crt_date = ADDDATE(crt_date, INTERVAL 1 DAY);
    END WHILE;
  END |
  DELIMITER ;

CALL fill_calendar('2008-01-01', '2011-12-31');

【问题讨论】:

【参考方案1】:

除非您每天在系统中为每种货币输入一个虚拟订单(这可以在 fill_calendar 例程中轻松完成以进行测试),否则您会发现很难在那里获得您想要的结果。

现在,您想要的是使用通用链接加入日历、订单和货币;但没有这样的链接(您有从日历到订单和订单到货币的链接,但没有从日历到货币的链接)。

如果您创建了这些虚拟订单,则无需更改架构;数据本身将提供所需的链接。否则,您可能需要稍微更改架构。

【讨论】:

【参考方案2】:

我将其发布为答案,因为它可能对评论来说相当大。 感谢马克为我指明了正确的方向。 Mark 的回答有效,但意味着对 calendar 表进行架构更改,而我并不热衷于此,因为我将来可能需要报告更加灵活(例如,按 product_id分组>)

这可行——但它可能并不优雅。我将把这个问题“悬而未决”几天,看看是否有人能提出更好的解决方案。

其他架构和数据(添加产品表):

CREATE TABLE products
  (
    id INT PRIMARY KEY,
    name VARCHAR(255)
  );
INSERT INTO products (id, name)
  VALUES
  (1, 'Widget'),
  (2, 'Midget'),
  (3, 'Gidget');

现在使用这个查询我得到了我想要的答案:

SELECT cal.date AS date, currency.name AS currency, products.name AS product,
   IFNULL(SUM(orders.order_value),0) AS total_value
FROM orders 
RIGHT JOIN 
(
SELECT cal.datefield AS date, cur.id AS currency, prod.id AS product
FROM calendar cal
CROSS JOIN currency cur
CROSS JOIN products prod
) cal
 ON (DATE(orders.order_date) = cal.date)
    AND orders.currency_id = cal.currency
    AND orders.product_id = cal.product
JOIN currency ON cal.currency = currency.id
JOIN products ON cal.product = products.id
WHERE (cal.date BETWEEN (SELECT MIN(DATE(order_date)) FROM orders) AND (SELECT MAX(DATE(order_date)) FROM orders))
GROUP BY date, cal.currency,cal.product

这会为我提供所有日期的所有数据点,如果不存在则为零。

+------------+-----------+--------+-------------+
| date       | currency  | product| total_value |
+------------+-----------+--------+-------------+
| 2009-08-15 | Euro      | Widget |       40.97 |
| 2009-08-15 | Euro      | Midget |       40.97 |
| 2009-08-15 | Euro      | Gidget |        0.00 |
| 2009-08-15 | US Dollar | Widget |       12.50 |
| 2009-08-15 | US Dollar | Midget |       12.50 |
| 2009-08-15 | US Dollar | Gidget |        0.00 |
| 2009-08-15 | CA Dollar | Widget |      122.60 |
| 2009-08-15 | CA Dollar | Midget |        0.00 |
| 2009-08-15 | CA Dollar | Gidget |        0.00 |
| 2009-08-16 | Euro      | Widget |        0.00 |
| 2009-08-16 | Euro      | Midget |        0.00 |
| 2009-08-16 | Euro      | Gidget |        0.00 |
| 2009-08-16 | US Dollar | Widget |        0.00 |
| 2009-08-16 | US Dollar | Midget |        0.00 |
| 2009-08-16 | US Dollar | Gidget |        0.00 |
| 2009-08-16 | CA Dollar | Widget |        0.00 |
| 2009-08-16 | CA Dollar | Midget |        0.00 |
| 2009-08-16 | CA Dollar | Gidget |        0.00 |
| 2009-08-17 | Euro      | Widget |        0.00 |
| 2009-08-17 | Euro      | Midget |        0.00 |
| 2009-08-17 | Euro      | Gidget |        0.00 |
| 2009-08-17 | US Dollar | Widget |       12.50 |
| 2009-08-17 | US Dollar | Midget |       12.50 |
| 2009-08-17 | US Dollar | Gidget |        0.00 |
| 2009-08-17 | CA Dollar | Widget |        0.00 |
| 2009-08-17 | CA Dollar | Midget |      122.60 |
| 2009-08-17 | CA Dollar | Gidget |        0.00 |
| 2009-08-18 | Euro      | Widget |       40.97 |
| 2009-08-18 | Euro      | Midget |       40.97 |
| 2009-08-18 | Euro      | Gidget |        0.00 |
| 2009-08-18 | US Dollar | Widget |        0.00 |
| 2009-08-18 | US Dollar | Midget |        0.00 |
| 2009-08-18 | US Dollar | Gidget |        0.00 |
| 2009-08-18 | CA Dollar | Widget |      122.60 |
| 2009-08-18 | CA Dollar | Midget |      122.60 |
| 2009-08-18 | CA Dollar | Gidget |        0.00 |
+------------+-----------+--------+-------------+

这在子查询上使用了 JOIN,我认为它的性能不是很好,但它适用于这个小数据集 - 我将生成更多数据并看看它是如何进行的。

【讨论】:

以上是关于使用 MySQL 生成按货币分组的填空每日销售报告的主要内容,如果未能解决你的问题,请参考以下文章

MySQL = 按月份分组销售计数,然后将月份显示为名称(Jan、Feb...DEC)而不是数值(1、2、3-12)

在按周分组的 2 个日期之间在 MYSQL 中生成报告

Python - 尝试按年份分组并汇总销售数据时出错

MySQL 练习<4>

MySQL 练习<4>

Amazon Redshift - 按类别获取每周销售量