MySql - Sql-запрос или процедура для создания отчета путем транспонирования строк в столбцы

Это первый раз - я пытаюсь сделать что-то подобное - так что терпите меня. Это на MySql.

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

Это текущий запрос, который я выполняю

select u.email,t.topic_name,tu.date_created as 'date completed'
from topic_user tu
join topic t ON tu.topic_id = t.topic_id
join user u ON tu.user_id = u.user_id

который вернет такие результаты, как

email         |    topic_name      |    date completed
[email protected] |    ABC             |    03/01/2012
[email protected] |    DEF             |    03/02/2012
[email protected] |    ABC             |    03/08/2012
[email protected] |    GHI             |    03/08/2012
[email protected] |    ABC             |    03/02/2012
[email protected] |    XYZ             |    03/10/2012

Я хочу создать отчет так, чтобы имена тем были заголовками столбцов, а дата их заполнения - значениями.

email         |    ABC      |    DEF      |    GHI      |    JKL      |    XYZ     
[email protected] | 03/08/2012  | 03/02/2012  | 03/08/2012  |    null     |    null
[email protected] | 03/02/2012  |    null     |    null     |    null     |  03/10/2012

Несколько моментов, на которые следует обратить внимание:

1) Все названия тем будут взяты из таблицы тем - даже если они не были заполнены учащимися - значение должно отображаться как null

2) В случае студента [email protected] - он дважды изучал тему ABC - но в отчете должна быть указана самая последняя дата.

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

Буду признателен за любую помощь, которую вы можете предоставить. Большое спасибо


person Gublooo    schedule 12.03.2012    source источник
comment
Исправлены ли названия тем? Могут ли они быть жестко запрограммированы внутри sql? Гарантировано ли вам, что у вас будет только один date_completed для каждого имени темы в электронном письме?   -  person Mike Ryan    schedule 12.03.2012
comment
Да, названия тем фиксированы в том смысле, что при создании курса таблица тем заполняется именами. Новые темы добавляются по мере необходимости. Нет, как я уже упоминал, один и тот же студент может изучать одну и ту же тему несколько раз, но в отчете должна быть указана последняя дата. Если вы посмотрите на студента [email protected] - он дважды закончил тему ABC, но в отчете указана последняя дата. Спасибо   -  person Gublooo    schedule 12.03.2012


Ответы (2)


Я не тестировал это, и мой опыт работы с MySQL ограничен, но я надеюсь, что это то, что вам нужно. Он динамически создает оператор SELECT с помощью функции GROUP_CONCAT, а затем выполняет его (это бит, который я не уверен в том, как это сделать в MySQL).

SET @SQL = (
SELECT  CONCAT('SELECT Email,', GROUP_CONCAT(SelectText), ' FROM Topic_User tu INNER JOIN Users u ON u.User_ID = tu.User_ID GROUP BY Email')
FROM    (   SELECT  CONCAT(' MAX(CASE WHEN Topic_ID = ', Topic_ID, ' THEN tu.Date_Created END) AS `', Topic_Name, '`') AS SelectText
            FROM    Topic
        )  AS d);

PREPARE stmt FROM @SQL;
EXECUTE stmt

Конечно, если ваши темы меняются не очень регулярно, вы можете просто использовать:

SELECT  Email,
        MAX(CASE WHEN Topic_ID = 1 THEN tu.Date_Created END) AS ABC,
        MAX(CASE WHEN Topic_ID = 2 THEN tu.Date_Created END) AS DEF,
        MAX(CASE WHEN Topic_ID = 3 THEN tu.Date_Created END) AS GHI,
        MAX(CASE WHEN Topic_ID = 4 THEN tu.Date_Created END) AS JKL,
        MAX(CASE WHEN Topic_ID = 5 THEN tu.Date_Created END) AS XYZ
FROM    Topic_User tu
        INNER JOIN users u
            ON u.User_ID = tu.User_ID
GROUP BY Email

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

person GarethD    schedule 12.03.2012
comment
Хорошее решение. Он должен работать нормально, пока находится в MySQL ›= 5.0.13 - person nnichols; 12.03.2012
comment
Спасибо - мне нравится решение, когда темы меняются нерегулярно - но в базе данных около 90 лишних тем, и мне пришлось бы добавить все эти случаи. Ваше первое решение динамического создания было бы идеальным, но я не могу заставить его работать - я получаю сообщение об ошибке - у вас есть ошибка в синтаксисе SQL; проверьте руководство, которое соответствует версии вашего сервера MySQL, чтобы найти правильный синтаксис для использования рядом с 'to Banking, MAX (CASE WHEN topic_id = 3 THEN tu.date_created END) AS T' в строке 1 - person Gublooo; 12.03.2012
comment
Я полагаю, это связано с topic_name с пробелом внутри. Я отредактировал решение, чтобы решить эту проблему. Если это не так, попробуйте выбрать @SQL перед его запуском, это покажет вам сгенерированный запрос и поможет вам определить, где возникает ошибка. - person GarethD; 12.03.2012
comment
Если вы измените его, чтобы использовать обратные кавычки вместо одинарных кавычек, он будет работать, когда в тексте есть одинарные кавычки. - person nnichols; 12.03.2012
comment
@nnichols Хорошая мысль. Я сделал это изменение сейчас. - person GarethD; 12.03.2012
comment
Я думаю, что проблема связана с ограничением длины group_concat - я распечатал запрос, и он обрезается, и я получаю сообщение об ошибке 1260: 1 строка (строки) были вырезаны Group_Concat () - person Gublooo; 12.03.2012
comment
Попробуйте увеличить максимальную длину для GROUP_CONCAT - SET SESSION group_concat_max_len = val; - person nnichols; 12.03.2012
comment
да, это помогло - это именно тот результат, который я искал. Вы, ребята, просто великолепны - GarethD большое спасибо за ваше время, и nnichols благодарны за ваш вклад - я понятия не имел, с чего начать. Огромное спасибо :) - person Gublooo; 12.03.2012

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

SELECT
    u.email,
    MAX(IF(t.topic_name = 'ABC', tu.date_created, NULL)) AS 'ABC',
    MAX(IF(t.topic_name = 'DEF', tu.date_created, NULL)) AS 'DEF',
    etc
FROM topic_user tu
INNER JOIN topic t
    ON tu.topic_id = t.topic_id
INNER JOIN user u
    ON tu.user_id = u.user_id
GROUP BY u.user_id;

Итак, на выбранном вами серверном языке вы должны динамически создавать список полей на основе первого запроса списка тем.

Вы можете немного уменьшить накладные расходы на этот запрос, используя topic_ids вместо topic_names, чтобы вы могли перетащить соединение в таблицу тем -

SELECT
    u.email,
    MAX(IF(tu.topic_id = 1, tu.date_created, NULL)) AS 'ABC',
    MAX(IF(tu.topic_id = 2, tu.date_created, NULL)) AS 'DEF',
    etc
FROM topic_user tu
INNER JOIN user u
    ON tu.user_id = u.user_id
GROUP BY u.user_id;
person nnichols    schedule 12.03.2012
comment
Спасибо, nichols - я смотрю, есть ли способ избежать перечисления 90 нечетных названий тем в запросе и есть ли способ динамически вытаскивать их из таблицы тем - person Gublooo; 12.03.2012
comment
Метод @Gublooo с подготовленным оператором определенно лучше. - person nnichols; 12.03.2012