更改数据库架构或查询以返回给定时间段内的余额

时间:2017-03-06 21:30:38

标签: mysql sql

我想出了以下架构:

  CREATE TABLE products
  (
    id INT(10) UNSIGNED AUTO_INCREMENT NOT NULL,
    name VARCHAR(255) NOT NULL,
    quantity INT(10) UNSIGNED NOT NULL,
    purchase_price DECIMAL(8,2) NOT NULL,
    sell_price DECIMAL(8,2) NOT NULL,
    provider VARCHAR(255) NULL,
    created_at TIMESTAMP NULL,
    PRIMARY KEY (id)
  );

  # payment methods = {
  #   "0": "CASH",
  #   "1": "CREDIT CARD",
  #   ...
  # }
  CREATE TABLE orders
  (
    id INT(10) UNSIGNED AUTO_INCREMENT NOT NULL,
    product_id INT(10) UNSIGNED NOT NULL,
    quantity INT(10) UNSIGNED NOT NULL,
    payment_method INT(11) NOT NULL DEFAULT 0,
    created_at TIMESTAMP NULL,
    PRIMARY KEY (id),
    FOREIGN KEY (product_id) REFERENCES products(id)
  );

  # status = {
  #   "0": "PENDING"
  #   "1": "PAID"
  # }
  CREATE TABLE invoices
  (
    id INT(10) UNSIGNED AUTO_INCREMENT NOT NULL,
    price INT(10) UNSIGNED NOT NULL,
    status INT(10) UNSIGNED NOT NULL DEFAULT 0,
    created_at TIMESTAMP NULL,
    PRIMARY KEY (id)
  );

  # payment methods = {
  #   "0": 'CASH',
  #   "1": 'CREDIT CARD',
  #   ...
  # }
  CREATE TABLE bills
  (
    id INT(10) UNSIGNED AUTO_INCREMENT NOT NULL,
    name VARCHAR(255) NOT NULL,
    payment_method INT(10) UNSIGNED NOT NULL DEFAULT 0,
    price DECIMAL(8,2) NOT NULL,
    created_at TIMESTAMP NULL,
    PRIMARY KEY (id)
  );

以下查询选择余额:

SELECT ((orders + invoices) - bills) as balance
FROM
(
    SELECT SUM(p.sell_price * o.quantity) as orders
    FROM orders o
    JOIN products p
    ON o.product_id = p.id
) orders,
(
    SELECT SUM(price) as invoices
    FROM invoices
    WHERE status = 1
) invoices,
(
    SELECT SUM(price) as bills
    FROM bills
) bills;

它正在工作并返回正确的平衡,但我想使用Morris.js创建一个图表,我需要更改它以在给定的时间段内以这种格式返回每日或每月余额:

每日(2017-02-27至2017-03-01)

balance | created_at
--------------------------
600.00  | 2017-03-01
50.00   | 2017-02-28
450.00  | 2017-02-27

每月(2017-01至2017-03)

balance | created_at
--------------------------
200.00  | 2017-03
250.00  | 2017-02
350.00  | 2017-01

我需要在架构或查询中更改以这种方式返回结果?

http://sqlfiddle.com/#!9/2289a9/2

欢迎任何提示。提前致谢

1 个答案:

答案 0 :(得分:1)

在SELECT列表中包含created_at日期,在每个查询中包含GROUP BY子句。

抛弃旧学校逗号操作符以进行连接操作,并将其替换为LEFT JOIN。

要返回没有订单(或没有付款,或没有发票)的日期,我们需要一个单独的行来源,保证返回日期值。例如,我们可以使用内联视图:

SELECT d.created_dt
  FROM (  SELECT '2017-02-27' + INTERVAL 0 DAY AS created_dt 
          UNION ALL SELECT '2017-02-28'
          UNION ALL SELECT '2017-03-01'
       ) d 
 ORDER BY d.created_dt

内联视图只是一个选项。如果我们有一个calendar表包含我们感兴趣的三个日期的行,我们可以使用它。重要的是我们有一个查询可以保证使用我们想要返回的不同created_at日期值返回给我们正好三行。

一旦我们有了这个,我们可以添加LEFT JOIN来获得该日期“账单”的价值。

SELECT d.created_dt
     , b.bills
  FROM (  SELECT '2017-02-27' + INTERVAL 0 DAY AS created_dt 
          UNION ALL SELECT '2017-02-28'
          UNION ALL SELECT '2017-03-01'
       ) d
  LEFT
  JOIN ( SELECT DATE(bills.created_at) AS created_dt
              , SUM(bills.price)       AS bills
           FROM bills
          WHERE bills.created_at >= '2017-02-27'
            AND bills.created_at <  '2017-03-01' + INTERVAL 1 DAY
          GROUP BY DATE(bills.created_at)
       ) b
    ON b.created_dt = d.created_dt
 ORDER BY d.created_dt

将其扩展为添加另一个LEFT JOIN,以获取发票

SELECT d.created_dt
     , i.invoices
     , b.bills
  FROM (  SELECT '2017-02-27' + INTERVAL 0 DAY AS created_dt 
          UNION ALL SELECT '2017-02-28'
          UNION ALL SELECT '2017-03-01'
       ) d
  LEFT
  JOIN ( SELECT DATE(bills.created_at) AS created_dt
              , SUM(bills.price)       AS  bills
           FROM bills
          WHERE bills.created_at >= '2017-02-27'
            AND bills.created_at <  '2017-03-01' + INTERVAL 1 DAY
          GROUP BY DATE(bills.created_at)
       ) b
    ON b.created_dt = d.created_dt
  LEFT
  JOIN ( SELECT DATE(invoices.created_at) AS created_dt
              , SUM(invoices.price)       AS invoices
           FROM invoices
          WHERE invoices.status = 1
            AND invoices.created_at >= '2017-02-27' 
            AND invoices.created_at <  '2017-03-01' + INTERVAL 1 DAY
          GROUP BY DATE(invoices.created_at)
       ) i
    ON i.created_dt = d.created_dt
 ORDER BY d.created_dt

同样,我们可以LEFT JOIN到另一个内联视图,返回按DATE(created_at)分组的总orders

内联视图返回created_dt的不同值非常重要,每个日期值都有一行。

请注意,对于开发,测试和调试,我们可以独立执行内联视图查询。

当从LEFT JOIN没有返回匹配的行时,例如由于在该日期没有发票而没有从i返回匹配的行,查询将为表达式{{1返回NULL }}。要用零替换NULL,我们可以使用i.invoices函数或更多ANSI标准IFNULL函数。例如:

COALESCE

要每月获得结果,我们需要一个每月返回一行的日历查询。我们假设我们将返回一个DATE值作为该月的第一天。例如:

SELECT d.created_dt
     , IFNULL(i.invoices,0) AS invoices
     , COALESCE(b.bills,0)  AS bills
  FROM ...

内联视图查询需要SELECT d.created_month FROM ( SELECT '2017-02-01' + INTERVAL 0 DAY AS created_month UNION ALL SELECT '2017-03-01' ) d ORDER BY d.created_month ,因此它们会为每个月的每个值返回一个值。我的偏好是使用GROUP BY created_month函数返回从DATE_FORMAT派生的月份的第一天。但还有其他方法可以做到这一点。目标是为“2017-02-01”返回单行,为“2017-03-01”返回单行。请注意,created_at上的日期范围从'2017-02-01'延伸到(但不包括)'2017-04-01',因此我们获得整月的总数。

created_at