Использование MySQL для создания ежедневных отчетов о продажах с заполненными пробелами, сгруппированных по валютам - PullRequest
1 голос
/ 30 апреля 2010

Я пытаюсь создать, как мне кажется, сравнительно простой отчет для интернет-магазина, используя MySQL 5.1.45

Магазин может принимать оплату в нескольких валютах. Я создал несколько примеров таблиц с данными и пытаюсь сгенерировать простой набор табличных результатов, сгруппированных по дате и валюте, чтобы я мог построить эти цифры.

Я хочу видеть каждую валюту, доступную на дату, с 0 в результате, если в тот день не было продаж в этой валюте. Если я могу заставить это работать, я хочу сделать то же самое, но также сгруппированный по идентификатору продукта.

В приведенных мной выборочных данных есть только 3 валюты и 2 идентификатора продукта, но на практике их может быть любое количество.

Я могу правильно группировать по дате, но затем, когда я добавляю группировку по валюте, мой запрос не возвращает то, что я хочу.

Я основал свою работу на этой статье .

Мой отчетный запрос, сгруппированный только по дате:

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');

Ответы [ 2 ]

3 голосов
/ 30 апреля 2010

Вам будет трудно получить желаемые результаты, если вы не введете в систему фиктивный ордер для каждой валюты на каждый день (что можно легко сделать в процедуре fill_calendar для тестирования).

Сейчас вам нужно присоединиться к календарю, ордерам и валюте, используя общую ссылку; но такой ссылки нет (у вас есть ссылки из календаря на заказы и заказы на валюту, но ничего от календаря к валюте).

Если вы создали эти фиктивные заказы, вам не нужно будет изменять схему; сами данные предоставят необходимую ссылку. В противном случае вам, вероятно, придется немного изменить схему.

2 голосов
/ 30 апреля 2010

Я публикую это как ответ, так как он может быть довольно большим для комментария. Спасибо Марку за то, что он указал мне правильное направление. Ответ Марка сработал, но означал изменение схемы таблицы 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 для подзапроса, который, по моему мнению, не очень эффективен, но он работает с этим небольшим набором данных - я сгенерирую еще немного данных и посмотрю, как это происходит.

...