MySQL:在与表 A 的联接结果中获取表 B 的行作为列。有比这更聪明/更好的方法吗?

标签 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?

我有一个表“类(class)”,如下所示:

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 如果明天有更多怎么办?有没有通用的方法来做这种事情?

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

菲利普

最佳答案

您尝试执行的操作在其他数据库中称为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

关于MySQL:在与表 A 的联接结果中获取表 B 的行作为列。有比这更聪明/更好的方法吗?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/12308112/

相关文章:

php - 在php中显示相关表中的数据

php - 带有 "WHERE title like %post%"的 MySQL 查询返回一个空结果集

php - 查询更新更新列表中未提及的时间戳列

bash - 使用 awk 将第二列移动到第一列

c# - Asp.NET Entity Framework 。如何自动生成主键?

python - Pandas 从列表中重命名 df 行

matrix - Prolog - 从列表列表中获取元素

sql - 帮助 MySQL 查询?

一列上的 MySQL DISTINCT 和 LEFT JOIN

sql - 根据给定的开放日期和关闭日期,生成一段时间内开放门票的计数