MySQL:在表A的连接结果中将表B的ROWS作为COLUMNS。比这更聪明/更好的方法?

时间:2012-09-06 20:47:05

标签: mysql row left-join identity-column

我完成了我的作业,并在网上和StackOverflow中查找信息,但我没有找到我的问题的答案。我看了看:

Creating two colums from rows of a table(不明白) MySQL join same table display rows in columns way (complicated)(不同,问题似乎不一样) Creating two colums from rows of a table

我用它来构建我的查询:

How to Display rows as Columns in 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通过了Math的测试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 个答案:

答案 0 :(得分:1)

您尝试执行的操作在其他数据库中称为PIVOT。不幸的是,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 with Demo

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

Dynamic pivot tables (transform rows to columns)

您的代码如下所示:

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 with Demo

答案 1 :(得分:0)

我将此视为简单的聚合查询。聚合是由lesson_id和title。然后计算只是将结果转向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”条款,我们保证将保留所有课程。