0

我做了功课并在网上和 StackOverflow 中查找信息,但我没有找到问题的答案。我在看:

从表的行创建两个列(不明白) MySQL 连接同一个表以列方式显示行(复杂)(不同且问题似乎不一样) 从表的行创建两个列

我用它来构建我的查询:

如何在 MySQL 中将行显示为列?

我有一个表格“课程”,如下所示:

ID  title
---------
1   Math
2   Latin
3   English
4   Science 

我有第二个表“结果”,它存储了每节课通过的测试用户的类型。目前有两种类型的测试:o 和 w(将来可能会有更多)。表格是这样的:

lesson_id  usr_id  test_type   course_id
----------------------------------------
1          100       o          1
1          100       w          1
1          24        o          1
1          36        w          1

在上表中,用户 100 通过了数学测试 o 和 w。在上表中,用户 24 通过了数学测试 o。在上表中,用户 36 通过了数学测试 w。

现在我想获得用户 100 的报告,我想有类似的东西:

ID  title      o       w
------------------------------
1    Math       TRUE    TRUE
2    Latin      FALSE   FALSE
3    English    FALSE   FALSE
4    Science    FALSE   FALSE

对于用户 36:

ID  title      o       w
------------------------------
1    Math       FALSE    TRUE
2    Latin      FALSE   FALSE
...

对于用户 24:

ID  title      o       w
------------------------------
1    Math       TRUE    FALSE
2    Latin      FALSE   FALSE
...

任何其他用户:

ID  title      o       w
------------------------------
1    Math       FALSE   FALSE
2    Latin      FALSE   FALSE
...

我可以通过如下查询来完成此操作:

SELECT l.id, l.title, COALESCE(s.o,FALSE) AS o, COALESCE(t.w, FALSE) AS w FROM lessons l 
LEFT JOIN (
    SELECT r.lesson_id,
    CASE
        WHEN r.test_type='o' THEN TRUE
        ELSE FALSE
    END AS o
    FROM results r WHERE r.itype='o' AND r.usr_id=100
    ) AS s ON l.id=s.lesson_id 
LEFT JOIN (
    SELECT rr.lesson_id,
    CASE
        WHEN rr.test_type='w' THEN TRUE
        ELSE FALSE
    END AS w 
    FROM results rr WHERE rr.test_type='w' AND rr.usr_id=100
    ) AS t ON l.id=t.lesson_id
WHERE l.course_id=1 ORDER BY l.id ASC;

虽然这段代码似乎可以工作(仍在忙于测试它),但我不喜欢它,因为它需要两个连接,并且每个连接都是由一个表中的选择组成的,将来可能会变得非常大。此查询将经常运行

  1. 你能建议一个更好的方法来做到这一点(更好可能意味着更聪明、更直接、更快或其他设计......或任何其他建议:-))?

  2. 如果我需要坚持这一点,您能否推荐参考资料,说明如何设置最佳索引以快速运行此查询?链接,您使用的文章?

  3. 在创建更多 test_types 的地方会发生什么?我的查询只使用 w 和 o 如果明天还有更多呢?有没有通用的方法来做这种事情?

任何帮助/建议都非常受欢迎,

菲利普

4

2 回答 2

1

您正在尝试做的事情PIVOT在其他数据库中称为 a 。不幸的是,MySQL 没有,因此您必须使用聚合函数和CASE语句创建一个。

如果你知道你只有两个test_type值,那么你可以硬编码:

select l.id,
  l.title,
  max(case when test_type = 'o' then 'true' else 'false' end) as o,
  max(case when test_type = 'w' then 'true' else 'false' end) as w
from lessons l
left join results r
  on l.id = r.lesson_id
  and r.usr_id = 100  -- the user id will change for each person
group by l.id, l.title

请参阅带有演示的 SQL Fiddle

现在,如果您想动态执行此操作,这意味着您事先不知道test_types要转置的数量,那么您应该查看以下文章:

动态数据透视表(将行转换为列)

您的代码如下所示:

SET @sql = NULL;
SELECT
  GROUP_CONCAT(DISTINCT
    CONCAT(
      'MAX(CASE WHEN test_type = ''',
      test_type,
      ''' THEN ''true'' ELSE ''false'' END) AS ',
      test_type
    )
  ) INTO @sql
FROM Results;


SET @sql 
  = CONCAT('SELECT l.id,
      l.title, ', @sql, ' 
     from lessons l
     left join results r
       on l.id = r.lesson_id
       and r.usr_id = 100
     group by l.id, l.title');

PREPARE stmt FROM @sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;

请参阅带有演示的 SQL Fiddle

于 2012-09-07T15:02:21.167 回答
0

我认为这是一个简单的聚合查询。聚合是按课程 ID 和标题。然后,计算只是旋转 o 和 w 的结果:

select l.lesson_id, l.title,
       max(case when r.test_type = 'o' then 'TRUE' else 'FALSE' end) as o,
       max(case when r.test_type = 'w' then 'TRUE' else 'FALSE' end) as w
from lessons l left outer join
     results r
     on l.lesson_id = r.lesson_id
where r.user_id = <whatever>
group by l.lesson_id, l.title
order by 1

用户在这个级别选择是一个问题,因为它需要来自结果,由于左外连接,结果可能为NULL。这是解决问题的变体:

select l.lesson_id, l.title,
       max(case when r.test_type = 'o' then 'TRUE' else 'FALSE' end) as o,
       max(case when r.test_type = 'w' then 'TRUE' else 'FALSE' end) as w
from lessons l left outer join
     results r
     on l.lesson_id = r.lesson_id and
        r.user_id = <whatever>
group by l.lesson_id, l.title
order by 1

通过将条件移至“on”子句,我们保证将保留所有课程。

于 2012-09-06T21:06:58.527 回答