2013-06-11 2 views
2

ОК, поэтому я столкнулся с этой чрезвычайно сложной проблемой, и поскольку я не являюсь гуру с MySQL, мне определенно нужен ваш вклад.Сложная проблема с запросами 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, left outer join и т. Д. ... lol) ... Однако это выглядит намного сложнее. Есть идеи?

+3

Не используйте FLOAT для этого. Это (почти наверняка) десятичные числа, а не числа с плавающей запятой. – Strawberry

+0

@Strawberry Well Я сделал это - для целей примера - для представления значений (цен), которые могут быть даже похожими на «200.30», а не целыми числами ... –

+0

Да. Это десятичные числа. Ох и ограничения внешнего ключа не имеют никакого значения в MyISAM, поэтому давайте проигнорируем их пока ... – Strawberry

ответ

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 | 
+---------------------+-------+---------+ 

... и sqlfiddle того же: http://www.sqlfiddle.com/#!2/22a1d/1

+0

Использование 'WHERE YEAR (date) = 2013' вместо сопоставления в диапазоне дат менее эффективно в долгосрочной перспективе. Аналогично, вы можете использовать вычисляемый столбец 'MONTH (date) month' в GROUP вместо повторного применения' MONTH() '. Наконец, ваша версия подзапроса, в то время как только требуемая дата, указанная один раз, выберет все данные за все периоды времени, ведущие к этому, становясь все более медленными. –

+0

Хотя верно, если у ОП был набор данных размера, который вы планируете, чтобы проблема стала Я сомневаюсь, что они попросят нас о помощи! – Strawberry

+0

После того, как вы помогли с некоторыми другими наборами данных, я предпочел бы не делать этого предположения, поскольку я не хочу быть тем, кто его очищает, когда набор данных становится слишком большим для обработки из-за неэффективных запросов! –

1

Проще говоря, если реструктуризация является нежелательным, то я хотел бы сделать простой запрос, чтобы определить стоимость заказов, возвратов и продаж по отдельности, а затем присоединиться к их вместе. Это можно сделать с помощью UNION и подзапросов, как в следующем примере: SQLFiddle

Я также взял на себя смелость заменить FLOAT для DECIMAL. Вероятно, есть место для улучшения индексов и т. Д., Но это должно поставить вас на правильный путь для определения сумм. Если вы посмотрите на подзапрос, вы увидите, что выбор ORDER и RETURN выбирает отрицательное значение в соответствии с вашим требованием.

Одна потенциальная ошибка заключается в том, что любые записи, для которых была удалена запись из Продукта, не будут включены. Этого можно избежать, изменив соединения Product в LEFT JOINs и соответствующим образом обработав значение NULL для product_category_id. Решил добавить это в последний пример, хотя, если строки из Product являются NEVER удалены, то 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; 
Смежные вопросы