MySQL查询的复杂问题

时间:2013-06-11 11:23:56

标签: mysql sql

好的,所以我面对这个极其复杂的问题,因为我不是MySQL的大师,我肯定需要你的意见。

假设我们有一个数据库,使用下面的代码创建(我正在粘贴创建代码 - 只是绝对必要的表 - 以避免粘贴所有表):

DROP TABLE IF EXISTS `Jeweller`.`Orders`;
CREATE TABLE `Jeweller`.`Orders` (
  `id` int(11) unsigned NOT NULL,
  `date` date DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8;

DROP TABLE IF EXISTS `Jeweller`.`Product_categories`;
CREATE TABLE `Jeweller`.`Product_categories` (
  `id` int(11) unsigned NOT NULL,
  `name` varchar(100) NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8;

DROP TABLE IF EXISTS `Jeweller`.`Product_orders`;
CREATE TABLE `Jeweller`.`Product_orders` (
  `order_id` int(11) unsigned NOT NULL,
  `product_id` int(11) unsigned NOT NULL,
  `quantity` int(11),
  `value` float,
  FOREIGN KEY (`order_id`) REFERENCES `Jeweller`.`Orders`(`id`),
  FOREIGN KEY (`product_id`) REFERENCES `Jeweller`.`Products`(`id`),
  CHECK (`quantity`>0),
  CHECK (`value`>0)
) ENGINE=MyISAM DEFAULT CHARSET=utf8;

DROP TABLE IF EXISTS `Jeweller`.`Product_returns`;
CREATE TABLE `Jeweller`.`Product_returns` (
  `sale_id` int(11) unsigned NOT NULL,
  `product_id` int(11) NOT NULL,
  `date` date DEFAULT NULL,
  `quantity` int(11),
  `value` float,
  FOREIGN KEY (`sale_id`) REFERENCES `Jeweller`.`Sales`(`id`),
  FOREIGN KEY (`product_id`) REFERENCES `Jeweller`.`Products`(`id`),
  CHECK (`quantity`>0),
  CHECK (`value`>0)
) ENGINE=MyISAM DEFAULT CHARSET=utf8;

DROP TABLE IF EXISTS `Jeweller`.`Product_sales`;
CREATE TABLE `Jeweller`.`Product_sales` (
  `sale_id` int(11) unsigned NOT NULL,
  `product_id` int(11) NOT NULL,
  `quantity` int(11),
  `value` float,
  FOREIGN KEY (`sale_id`) REFERENCES `Jeweller`.`Sales`(`id`),
  FOREIGN KEY (`product_id`) REFERENCES `Jeweller`.`Products`(`id`),
  CHECK (`quantity`>0),
  CHECK (`value`>0)
) ENGINE=MyISAM DEFAULT CHARSET=utf8;

DROP TABLE IF EXISTS `Jeweller`.`Products`;
CREATE TABLE `Jeweller`.`Products` (
  `id` int(11) unsigned NOT NULL,
  `product_category_id` int(11) NOT NULL,
  `seller_id` int(11) NOT NULL,
  `name` varchar(100) NOT NULL,
  `description` text,
  PRIMARY KEY (`id`),
  FOREIGN KEY (`product_category_id`) REFERENCES `Jeweller`.`Product_categories`(`id`),
  FOREIGN KEY (`seller_id`) REFERENCES `Jeweller`.`Sellers`(`id`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8;

DROP TABLE IF EXISTS `Jeweller`.`Sales`;
CREATE TABLE `Jeweller`.`Sales` (
  `id` int(11) unsigned NOT NULL,
  `date` date DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8;

现在,我们将利润定义为:

  • Sales - Returns - Orders

您将如何查询要获取的内容:

按月和product_category获利,仅适用于2013年。


出于测试目的,这里是完整的DB Creation code以及DB Population code(带有一些演示数据)。 (SQLFiddle link


P.S。

  • 实际代码有点不同(以上只是一个例子 - 尽管是100%忠诚的代码)

  • 经过多次尝试,我设法过滤了2013年的销售/订单/等等......我甚至设法按产品获得利润(虽然花了不少join秒,{{ 1}} s等...(lol)...然而,这看起来要复杂得多。有什么想法吗?

2 个答案:

答案 0 :(得分:1)

简单来说,如果重组是不可取的,那么我会做一个简单的查询来单独确定订单,退货和销售的价值,然后将它们加在一起。这可以使用UNION和子查询来完成,如以下示例所示:SQLFiddle

我也冒昧地将FLOAT交换为DECIMAL。索引等可能还有改进的余地,但这应该会让你在确定总和方面走得很好。如果查看子查询,您将看到ORDER和RETURN选择正在根据您的要求选择负值。

一个潜在的缺陷是,不会包含任何已删除产品记录的记录。通过将Product连接更改为LEFT JOIN并适当地处理product_category_id的NULL值,可以避免这种情况。决定将此添加到最新示例中,但如果Product中的行从未删除,则INNER JOIN就足够了

SELECT

  d.thisMonth,
  d.product_category_id,
  SUM(d.sumValue)

FROM (

  (
    -- Get the order value

    SELECT

      'order' AS valueType,
      MONTH(o.date) AS thisMonth,
      p.product_category_id,
      SUM(-po.value * po.quantity) AS sumValue

    FROM Orders o

    INNER JOIN Product_orders po
    ON po.order_id = o.id

    LEFT JOIN Products p
    ON p.id = po.product_id

    WHERE o.date BETWEEN '2013-01-01' AND '2013-12-31'

    GROUP BY
        thisMonth,
        product_category_id

  ) UNION ALL (

    -- Get the sales value

    SELECT

      'sale' AS valueType,
      MONTH(s.date) AS thisMonth,
      p.product_category_id,
      SUM(ps.value * ps.quantity) AS sumValue

    FROM Sales s

    INNER JOIN Product_sales ps
    ON ps.sale_id = s.id

    INNER JOIN Products p
    ON p.id = ps.product_id


    WHERE s.date BETWEEN '2013-01-01' AND '2013-12-31'

    GROUP BY
        thisMonth,
        product_category_id

  ) UNION ALL (

    -- Get the return value

    SELECT

      'return' AS valueType,
      p.product_category_id,
      MONTH(pr.date) AS thisMonth,
      SUM(-pr.value * pr.quantity) AS sumValue

    FROM Product_returns pr

    INNER JOIN Products p
    ON p.id = pr.product_id

    WHERE pr.date BETWEEN '2013-01-01' AND '2013-12-31'

    GROUP BY
        thisMonth,
        product_category_id

  )
) d
GROUP BY 
  d.thisMonth,
  d.product_category_id;

答案 1 :(得分:1)

以下是您的架构的近似值......

DROP TABLE IF EXISTS orders;
CREATE TABLE orders 
( order_id int(11) unsigned NOT NULL auto_increment
, date date DEFAULT NULL
, PRIMARY KEY (order_id)
) ENGINE=MyISAM DEFAULT CHARSET=utf8;

INSERT INTO orders VALUES
(NULL,'2013-01-01'),
(NULL,'2013-01-01'),
(NULL,'2013-02-02'),
(NULL,'2013-02-03'),
(NULL,'2013-03-05'),
(NULL,'2013-06-07');

DROP TABLE IF EXISTS product_orders;
CREATE TABLE product_orders 
( order_id int unsigned NOT NULL
, product_id int unsigned NOT NULL
, quantity int NOT NULL DEFAULT 1
, value DECIMAL(5,2)
, PRIMARY KEY(order_id,product_id)
) ENGINE=MyISAM DEFAULT CHARSET=utf8;

INSERT INTO product_orders VALUES
(1,101,1,100),
(1,102,1,50),
(2,101,2,200),
(3,101,1,100),
(4,102,2,100),
(4,103,3,150),
(5,104,1,300),
(6,102,1,50),
(6,103,2,100),
(6,104,1,300);

DROP TABLE IF EXISTS product_returns;
CREATE TABLE product_returns 
( sale_id int unsigned NOT NULL
, product_id int NOT NULL
, date date DEFAULT NULL
, quantity int NOT NULL DEFAULT 1
, value DECIMAL(5,2)
, PRIMARY KEY(sale_id,product_id)
) ENGINE=MyISAM DEFAULT CHARSET=utf8;

INSERT INTO product_returns VALUES
(21,101,'2013-01-04',2,200),
(22,102,'2013-03-06',1,50),
(22,103,'2013-05-08',1,50),
(23,104,'2013-06-09',1,300);


DROP TABLE IF EXISTS product_sales;
CREATE TABLE product_sales 
( sale_id int unsigned NOT NULL
, product_id int NOT NULL
, quantity int NOT NULL
, value DECIMAL(5,2)
, PRIMARY KEY(sale_id,product_id)
) ENGINE=MyISAM DEFAULT CHARSET=utf8;

INSERT INTO product_sales VALUES
(20,101,1,100),
(20,102,1,50),
(21,101,3,300),
(22,101,1,100),
(22,102,2,100),
(22,103,1,50),
(23,103,2,100),
(23,104,2,600);


DROP TABLE IF EXISTS products;
CREATE TABLE products 
( product_id int unsigned NOT NULL AUTO_INCREMENT
, product_category_id int NOT NULL
, name varchar(100) NOT NULL
, description text NULL
, PRIMARY KEY (product_id)
) ENGINE=MyISAM DEFAULT CHARSET=utf8;

INSERT INTO products VALUES
(101,1,'donuts','Mmm, donuts'),
(102,2,'buzz Cola','Mmm, donuts'),
(103,2,'duff beer','Can\'t get enough'),
(104,1,'Krusty-O\'s','Yum, yum');

DROP TABLE IF EXISTS sales;
CREATE TABLE sales
( sale_id int NOT NULL
, date date DEFAULT NULL
, PRIMARY KEY (sale_id)
) ENGINE=MyISAM DEFAULT CHARSET=utf8;

INSERT INTO sales VALUES
(20,'2013-01-12'),
(21,'2013-02-15'),
(22,'2013-03-17'),
(23,'2013-05-18');

...以及可能的查询...

SELECT p.product_category_id
     , MONTH(date) month
     , SUM(value) profit
  FROM 
     ( SELECT product_id,value, date 
         FROM product_sales ps
         JOIN sales s
           ON s.sale_id = ps.sale_id
        UNION ALL
       SELECT product_id,value*-1,date FROM product_returns
        UNION ALL
       SELECT product_id,value*-1,date
         FROM product_orders po
         JOIN orders o
           ON o.order_id = po.order_id
     ) x
  JOIN products p
    ON p.product_id = x.product_id
 WHERE YEAR(date) = 2013
 GROUP
    BY p.product_category_id
     , MONTH(date);

+---------------------+-------+---------+
| product_category_id | month | profit  |
+---------------------+-------+---------+
|                   1 |     1 | -400.00 |
|                   1 |     2 |  200.00 |
|                   1 |     3 | -200.00 |
|                   1 |     5 |  600.00 |
|                   1 |     6 | -600.00 |
|                   2 |     1 |    0.00 |
|                   2 |     2 | -250.00 |
|                   2 |     3 |  100.00 |
|                   2 |     5 |   50.00 |
|                   2 |     6 | -150.00 |
+---------------------+-------+---------+

......和一个相同的方形:http://www.sqlfiddle.com/#!2/22a1d/1