Есть у меня в реальной работе задача. Уровень "продвинутый", лишних прошу не беспокоить. Некое решение я сделал, но чувствую, что есть другие достойные способы. Чтобы не подталкивать вас к определенному ходу мыслей, своё я покажу позже. Дано: Таблица "событий". Пускай это извещения о совершенных заказах. Код (Text): CREATE TABLE `events` ( `id` int(10) NOT NULL AUTO_INCREMENT, `event_time` DATETIME NOT NULL, `event_type` enum('order','pay') NOT NULL DEFAULT 'order', `order_no` varchar(10) NOT NULL DEFAULT '', `order_sum` decimal(10,2) NOT NULL DEFAULT '0', PRIMARY KEY (`id`), KEY (`order_no`), KEY (`event_type`,`event_time`) ) Любой заказ имеет одну запись типа 'order' и любое количество, от 0 до N записей 'pay'. Мы можем сгруппировать записи одного заказа по order_no, поле уникально указывает на заказ. Поле event_time у разных записей одного заказа будет разное, оно соответствует моменту оформления либо оплаты. Поле order_sum содержит 0 в записях типа order и не 0 в записях типа pay. Требуется: Создать выборку за определенный период записей вида Код (Text): День, когда был оформлен заказ | Количество созданных| Количество оплаченных если в какой-то день не было заказов, строчка должна быть, только с нулями. Если оплата произошла не в день оформления, а позже — данные всё равно относятся ко дню оформления. Умеренное участие PHP допустимо. Хорошее решение — только средствами SQL.
непонял. а как понять оплачен заказ полностью или нет? ведь сумма заказа при order равна нулю. а количество pay может быть любым и с любыми суммами. в какой момент мы узнаем что заказ оплачен полностью? order_type тоже никак не поможет. ибо там нет типа 'ОПЛАЧЕНО' или сумма платежа 'pay' всегда равна сумме заказа 'order'? тоесть оплачивать частями заказ нельзя? так ведь в самом заказе 'order' сумма == 0. непонятно чета
это не таблица заказов, это "события", ок? от таблицы требуется то, что я описал, не больше. я не прошу придумывать другие задачи.
заказ считается оплаченным если по нему была оплата(ы) на любую сумму. для зануд: бывают только положительные суммы оплаты ))) Добавлено спустя 3 минуты 28 секунд: "количество созданных" это количество уникальных order_no "количество оплаченных" это количество уникальных order_no, по которым был хотябы один факт оплаты.
набросал несколько игровых записей: http://sqlfiddle.com/#!2/8c80da Добавлено спустя 1 минуту: в примере даты ровные, только из-за моей лени. в реале время присутствует.
сорри, у меня щас тока оракл под рукой) первый вариант такой Код (PHP): WITH t AS ( -- orders SELECT 1 ID, to_date('05.03.2014 01:00:10','dd.mm.yyyy hh24:mi:ss') event_time, 'order' event_type, 'z1' order_no, 0 order_sum FROM dual UNION ALL SELECT 2 ID, to_date('05.03.2014 02:00:20','dd.mm.yyyy hh24:mi:ss') event_time, 'order' event_type, 'z2' order_no, 0 order_sum FROM dual UNION ALL SELECT 3 ID, to_date('06.03.2014 03:00:20','dd.mm.yyyy hh24:mi:ss') event_time, 'order' event_type, 'z3' order_no, 0 order_sum FROM dual UNION ALL SELECT 4 ID, to_date('07.03.2014 03:00:20','dd.mm.yyyy hh24:mi:ss') event_time, 'order' event_type, 'z4' order_no, 0 order_sum FROM dual UNION ALL SELECT 5 ID, to_date('07.03.2014 03:00:20','dd.mm.yyyy hh24:mi:ss') event_time, 'order' event_type, 'z5' order_no, 0 order_sum FROM dual UNION ALL SELECT 6 ID, to_date('10.03.2014 03:00:20','dd.mm.yyyy hh24:mi:ss') event_time, 'order' event_type, 'z6' order_no, 0 order_sum FROM dual UNION ALL SELECT 7 ID, to_date('10.03.2014 03:00:20','dd.mm.yyyy hh24:mi:ss') event_time, 'order' event_type, 'z7' order_no, 0 order_sum FROM dual -- pays UNION ALL SELECT 10 ID, to_date('05.03.2014 01:00:20','dd.mm.yyyy hh24:mi:ss') event_time, 'pay' event_type, 'z1' order_no, 100 order_sum FROM dual UNION ALL SELECT 11 ID, to_date('06.03.2014 02:00:20','dd.mm.yyyy hh24:mi:ss') event_time, 'pay' event_type, 'z2' order_no, 25 order_sum FROM dual UNION ALL SELECT 12 ID, to_date('07.03.2014 02:00:20','dd.mm.yyyy hh24:mi:ss') event_time, 'pay' event_type, 'z3' order_no, 10 order_sum FROM dual UNION ALL SELECT 13 ID, to_date('08.03.2014 02:00:20','dd.mm.yyyy hh24:mi:ss') event_time, 'pay' event_type, 'z3' order_no, 15 order_sum FROM dual UNION ALL SELECT 14 ID, to_date('10.03.2014 02:00:20','dd.mm.yyyy hh24:mi:ss') event_time, 'pay' event_type, 'z6' order_no, 55 order_sum FROM dual ) SELECT tt.dd ,( SELECT count(*) FROM t t1 WHERE t1.event_type IN ('order') AND to_char(t1.event_time,'dd.mm.yyyy')=tt.dd ) cnt_created ,( SELECT count(*) FROM t t1 WHERE t1.event_type IN ('order') AND to_char(t1.event_time,'dd.mm.yyyy')=tt.dd AND t1.order_no IN ( SELECT order_no FROM t WHERE event_type IN ('pay') ) ) cnt_payed from ( SELECT to_char(event_time,'dd.mm.yyyy') dd FROM t WHERE event_type IN ('order') AND event_time between to_date('01.03.2014','dd.mm.yyyy') and to_date('11.03.2014','dd.mm.yyyy') GROUP BY to_char(event_time,'dd.mm.yyyy') ORDER BY 1 ) tt результат 05.03.2014 2 2 06.03.2014 1 1 07.03.2014 2 0 10.03.2014 2 1 перевести в мускул вроде непроблема
Ночь не спал, потому могу слегка тупить ) http://sqlfiddle.com/#!2/6d4e7/2/0 вообще ни одной мысли на эту тему )
runcore, ты мог воспользоваться моей ссылкой на sqlfiddle. извини, но я не буду пробовать твой код. 20 штук union ?… romach, выглядит очень правдоподобно. цифры кажется верные, НО! 1) Код (Text): SELECT `event_time`, `order_no`, ... GROUP BY `order_no` работают обманчиво. откуда будет взят event_time ? это очень хрупкий код, непредсказуемый. 2) вот это не учитывается: 3) IMHO, с count(if()...) перебор. можно проще
сразу ты ссылку не дал. поэтому я начал деалть у себя. унионы это просто создание таблички тестовой. сам запрос внизу.
а это можно адекватно перевести на mysql? Добавлено спустя 1 минуту 46 секунд: кто убил sqlfiddle, признавайтесь! сперва был недоступен, а сейчас вижу джавовский бэктрейс. катастрофа блин.
1. Можно взять Min(event_time), т.е. время события 'order'. Оплата же не может быть произведена раньше заказа? 2. хз, мыслей нет как запросом это оформить. Подозреваю, что лучше это на пхп переложить, пробежаться циклом и вставить даты. Все же не свойственно для БД, выбирать то чего нет ) 3. Можно сократить на пару count: http://sqlfiddle.com/#!2/6d4e7/9/0 з.ы. не забудь итоговый вариант выложить )
1. точно. я так и делаю 2. можно изготовить последовательность дат. я даже подсказывал здесь недавно 3. ага. красота! не забуду. не факт, что он будет лучший. я остаюсь в выигрыше в любом случае: или самолюбие потешу, или чему-то научусть ) ладно, вот промежуточный результат без генератора дат - для сравнения: http://sqlfiddle.com/#!2/da9fed/13 генератор вместо первого вложенного запроса ставится.
Как я понял, там либо процедуры, либо временные таблицы. Чуйка подсказывает мне, что в данном случае это костыль и проще пыхом дополнить недостающие даты. Впрочем, возможно я недостаточно раскурил тему ) такое же мнение ) Добавлено спустя 2 минуты 5 секунд: хех, осталось только нагенерить тестовую таблицу и посмотреть чей вариант быстрее ))
один из вариантов: viewtopic.php?f=20&t=47888&p=380576#p380485 как исходник нужна последовательность чисел. я для отчетов держу такую от 0 до 9999, очень полезная штука можно и по другому, на переменной mysql как на счетчике — c любой базовой последовательностью, например можно служебную таблицу mysql взять достаточно большую Добавлено спустя 2 минуты 16 секунд: p.s. тут важнее красота, я считаю. если в отчете до сотни тысяч записей вообще не надо париться про скорость. это не то, что при каждом заходе пользователя генериться, тут допустимо пару сотых секунды подождать.
Код (PHP): SELECT t.* ,( SELECT count(*) FROM `events` e1 WHERE e1.`event_type`='order' AND DATE(e1.`event_time`)=t.dd AND e1.`order_no` IN ( SELECT e2.`order_no` FROM `events` e2 WHERE e2.`event_type`='pay' AND e2.`order_no`=e1.`order_no` ) ) `payed` FROM ( SELECT DATE(`event_time`) dd, count(`order_no`) `ordered` FROM `events` WHERE `event_type`='order' GROUP BY 1 ) t
спасибо! запрос работает, хотя план выполнения ужасен. по поводу "красоты" — он слишком уж в лоб сделан. для разового решения это нормально, а в постоянную работу я такие штуки не ставлю. это моё сугубое мнение.
еще гибрид Код (PHP): SELECT DATE(e3.`ev_time`) `dt` ,count(e3.`order_no`) `ORDERED` ,sum(e3.`p_sum`) `payed` from ( SELECT e2.`order_no` ,MIN(e2.`event_time`) `ev_time` ,IF(SUM(e2.`p_flag`)<>0,1,0) `p_sum` FROM ( SELECT e.`event_time`, e.`order_no` ,IF(e.`order_sum`>0,1,0) `p_flag` FROM `events` e ) e2 GROUP BY e2.`order_no` ) e3 GROUP BY 1
Ну видимо все желающие и могущие уже поучаствовали Подвожу итоги. Вариант romach мне понравился. По сути там та же техника, что и у меня — используется свойство функции COUNT(col) считать только не NULL значения col. Нет второй вложенной таблицы, но она появится если все-таки сделать "пустые даты". В моем варианте подготовка к этому уже есть. Вот и вся разница по сути. В варианте runcore вместо COUNT() есть SUM(). Так как суммируются единички, то результат аналогичен. Тоже вариант, почему бы нет. Все справились, в общем ) Завершаю квест полным решением. Один из вариантов генерации последовательности дат я нашел на stackoverflow: date range: no loops, procedures, or temp tables. Элегантно! Точно также можно генерировать любые числовые или временны́е последовательности: (start + i * step). Работает это быстро и не надо всякий раз создавать новую таблицу. Я у себя создал вьюшку как источник чисел, вот и на sqlfiddle сделаю через вьюшку. Код (Text): SELECT m.`date`, COUNT(e.`event_time`) AS `ordered`, COUNT(e.p) AS `paid` FROM ( SELECT (DATE('2014-03-03') + INTERVAL `number` DAY) AS `date` FROM `numbers` WHERE `number` < 14 ) AS m LEFT JOIN ( SELECT IF(SUM(`order_sum`) > 0, 1, null) AS p, MIN(`event_time`) AS `event_time` FROM `events` GROUP BY `order_no` ) AS e ON e.`event_time` BETWEEN m.`date` AND TIMESTAMP(m.`date`, '23:59:59') GROUP BY 1 http://sqlfiddle.com/#!2/7a84e/5