Как ГРУППИРОВАТЬ ПО последовательным данным (в данном случае по дате)

У меня есть таблица products и таблица sales, в которых хранится информация о том, сколько товаров было продано за каждую дату. Конечно, не все товары продаются каждый день.

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

Я хотел бы рассказать вам, сколько вещей я пробовал до сих пор, но единственные успешные (и медленные, рекурсивные) решения - это решения внутри моего приложения, а не внутри SQL, чего я и хочу.

Я также просмотрел несколько похожих вопросов на SO, но не нашел ни одного, который позволил бы мне получить четкое представление о том, что мне действительно нужно.

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

Я надеюсь, что кто-то здесь знает, как это сделать. Заранее спасибо за любые комментарии!

Франциско


person Francisco Zarabozo    schedule 26.04.2013    source источник
comment
Я думаю, вам нужны рекурсивные соединения, чтобы достичь того, что невозможно с MySQL. Можете ли вы создать процедуры хранения в базе данных?   -  person Benos    schedule 26.04.2013
comment
Да, сервер мой.   -  person Francisco Zarabozo    schedule 26.04.2013


Ответы (3)


http://sqlfiddle.com/#!2/20108/1

Вот процедура хранения, которая выполняет эту работу

CREATE PROCEDURE myProc()
BEGIN
    -- Drop and create the temp table
    DROP TABLE IF EXISTS reached;
    CREATE TABLE reached (
    sku CHAR(32) PRIMARY KEY,
    record_date date,
    nb int,
    total int)
   ENGINE=HEAP;

-- Initial insert, the starting point is the MAX sales record_date of each product
INSERT INTO reached 
SELECT products.sku, max(sales.record_date), 0, 0
FROM products
join sales on sales.sku = products.sku
group by products.sku;

-- loop until there is no more updated rows
iterloop: LOOP
    -- Update the temptable with the values of the date - 1 row if found
    update reached
    join sales on sales.sku=reached.sku and sales.record_date=reached.record_date
    set reached.record_date = reached.record_date - INTERVAL 1 day, 
        reached.nb=reached.nb+1, 
        reached.total=reached.total + sales.items;

    -- If no more rows are updated it means we hit the most longest days_sold
    IF ROW_COUNT() = 0 THEN
        LEAVE iterloop;
    END IF;
END LOOP iterloop;

-- select the results of the temp table
SELECT products.sku, products.title, products.price, reached.total as sales, reached.nb as days_sold 
from reached
join products on products.sku=reached.sku;

END//

Тогда вам просто нужно сделать

call myProc()
person Benos    schedule 26.04.2013
comment
Вау, спасибо. Я никогда не думал, что это займет так много времени, но это определенно быстрее и лучше, чем обработка на уровне приложения. Большое спасибо, что нашли время, чтобы написать этот ответ. :-) - person Francisco Zarabozo; 26.04.2013
comment
Возможно, есть более быстрый/чистый способ, но две недели назад у меня была почти такая же проблема, и я написал для нее процедуру. Было легко адаптировать его для вашего вопроса - person Benos; 26.04.2013

Решение на чистом SQL без процедуры хранения: Fiddle

SELECT sku
     , COUNT(1) AS consecutive_days
     , SUM(items) AS items
FROM
(
  SELECT sku
       , items
       -- generate a new guid for each group of consecutive date
       -- ie : starting with day_before is null
       , @guid := IF(@sku = sku and day_before IS NULL, UUID(), @guid) AS uuid
       , @sku := sku AS dummy_sku
  FROM 
  (
    SELECT currents.sku
         , befores.record_date as day_before
         , currents.items
    FROM sales currents
      LEFT JOIN sales befores 
        ON currents.sku = befores.sku 
        AND currents.record_date = befores.record_date + INTERVAL 1 DAY
    ORDER BY currents.sku, currents.record_date
  )  AS main_join
    CROSS JOIN (SELECT @sku:=0) foo_sku
    CROSS JOIN (SELECT @guid:=UUID()) foo_guid
) AS result_to_group
GROUP BY uuid, sku

Запрос на самом деле не такой сложный. Объявите переменные через cross join (SELECT @type:=0) type. Затем в выборках вы можете устанавливать значения переменных построчно. Это необходимо для имитации функции ранга.

person Cyril Gandon    schedule 26.04.2013
comment
Вот это да. Мне придется немного изучить, чтобы понять ваш запрос. Это будет первый раз, когда я буду использовать такие вещи, как items, @guid := IF(..., а также cross join. Спасибо, это кажется очень подходом. :-) - person Francisco Zarabozo; 26.04.2013
comment
@FranciscoZarabozo: это не так сложно. Протестируйте каждый подзапрос и посмотрите результат, тогда вы сможете легко понять, что происходит. - person Cyril Gandon; 26.04.2013
comment
Мне очень нравится этот подход, а как насчет производительности по сравнению с процедурой хранения, если таблица продаж со временем сильно растет? Ваше утверждение получает все куски последовательных дат, что здорово, но может быть проблемой для большой таблицы. В любом случае, большое спасибо за ваш вопрос, я многому научился, анализируя его. - person Benos; 26.04.2013

person    schedule
comment
Спасибо, но... это не учитывает записи только в последовательные даты, и при этом не учитывается как далекое прошлое, так как они только последовательные. - person Francisco Zarabozo; 26.04.2013