【问题标题】:SQL - columns for different categoriesSQL - 不同类别的列
【发布时间】:2010-08-31 17:23:21
【问题描述】:

我是 SQL 新手。我有一个包含不同考试数据的数据库,例如:

Student Test Grade
--------------------
St1    T1   A
St2    T1   B
St3    T1   B
St1    T2   B
St2    T2   B
St3    T2   A
St1    T3   A
St2    T3   C
St3    T3   B

然后,我想使用测试(T1、T2 和 T3)作为列打印报告:

Student  T1   T2   T3
----------------------
St1      A    B    A
St2      B    B    C
St3      B    A    B

我尝试了不同的方法,但我一直不知道如何生成这样的打印输出。任何帮助表示赞赏!

【问题讨论】:

  • 使用“代码”按钮进行格式化可以让您使用固定宽度的字体,帮助这些表格排列整齐并更具可读性

标签: sql sqlite pivot


【解决方案1】:

用途:

  SELECT t.student,
         MAX(CASE WHEN t.test = 'T1' THEN t.grade END) AS T1,
         MAX(CASE WHEN t.test = 'T2' THEN t.grade END) AS T2,
         MAX(CASE WHEN t.test = 'T3' THEN t.grade END) AS T3
    FROM TABLE t
GROUP BY t.student

【讨论】:

  • 嗨,你太棒了!非常感谢!结果很完美!干杯! -迈克
【解决方案2】:

不久前我问了一个类似的question。您需要类似于数据透视表的东西,但是 SQLite 中没有(据我所知)。

【讨论】:

    【解决方案3】:

    有几种方法可以做到这一点,这两种方法(在纯 SQL 中,而不是在生成 SQL 命令的代码中)都需要知道并固定列数。最简单的实现方式是:

    SELECT eg.Student,
    (SELECT Grade from ExamGrade eg1 WHERE eg1.Student = eg.Student AND Test = 'T1') AS T1
    (SELECT Grade from ExamGrade eg2 WHERE eg2.Student = eg.Student AND Test = 'T2') AS T2
    (SELECT Grade from ExamGrade eg3 WHERE eg3.Student = eg.Student AND Test = 'T3') AS T3
    FROM ExamGrade eg
    

    这几乎适用于包括 SQLite 在内的任何环境,并且可以使用标量值函数 GetTest() 使其更加优雅,该函数将获取学生和考试编号并返回成绩。但是,无论如何,这既不是高性能的,也不是封闭的;它将查询 N 次表的 N 次测试,如果您添加第 4 个测试,则必须更改此查询以将其包含在报告中。

    如果 Student 和 Test 的组合是唯一的,并且您正在使用具有 Pivot 功能的数据库(显然 SQLite 没有),您可以将 Pivot 查询与几乎任何聚合器(MAX/MIN具有单个值的集合的 /AVG/SUM 就是那个值)。 MSS2005 中的以下工作:

    SELECT Student, T1, T2, T3
    FROM (Select Student, Test, Grade FROM ExamGrade) As SourceQuery
    PIVOT (MAX(Grade) FOR Test IN (T1, T2, T3)) AS PivotTable
    

    这会更高效,也更优雅。列列表仍然无法动态确定 AFAIK,但如果您从应用程序代码进行此查询,或者使用 MS SQL Server 中的 sp_executesql 内置存储过程从另一个存储过程生成查询,它们生成起来很简单功能。

    【讨论】:

      【解决方案4】:

      我相信,如果您要扩展此系统以包含更多信息,那么您可以从重新设计数据库中受益,我会这样构建它:

      表名 = 粗体

      列名 = 斜体

      学生

      • SID(主键)
      • 有关学生的其他信息

      测试

      • TID(主键)
      • 有关测试的其他信息

      测试成绩

      • GID(主键)
      • TID(外键)
      • SID(外键)
      • 等级

      这种结构是基于一种称为数据库规范化的思想(如果你用谷歌搜索的话,你会得到很多信息)。我将在下面给你一个部分总结,但如果你要执行大量 SQL,你应该自己阅读:

      首先要知道的是,主键只是一个唯一标识符,它通常不是信息的一部分(但是,由于它是唯一的,因此每个数据的主键必须具有不同的值),以及一个外键是一种使用被引用者的主键从另一个表中的行引用一个表中的行的方法:例如,每个年级中的外键 SID 引用单个学生,基于他们的主键SID.

      例如学生一的 SID 为 1,他的所有测试在 SID 列中都有 1。学生 2、3、4 等也一样。

      规范化的基本思想是,所有唯一数据只存储一次,然后在使用它的其他地方引用(如果您看一下示例中键的结构,所有学生信息都存储在一张表,然后在他们的考试成绩中引用,而不是在每个成绩中重复)。

      要从这些表中检索您想要的内容,我会使用它(用 PHP 编写):

      $sql = 'SELECT * FROM Tests ORDER BY TID';
      $tempresult = mysql_query($sql);
      while($temprow = mysql_fetch_array($tempresult)){
          echo $temprow['TID'];
      }
      
      $sql = 'SELECT * FROM Students';
      $result = mysql_query($sql);
      while($row = mysql_fetch_array($result)){
         echo '\n'.$row['SID'];
         $sql = 'SELECT * FROM Grades WHERE SID='.$row['SID'].' ORDER BY TID';
         $result2 = mysql_query($sql);
         while($row2 = mysql_fetch_array($result2)){
            echo ' '.$rows['Grade'];
         }
      }
      

      您可以在 echo 语句中为此添加格式,也可以打印您选择添加的任何额外信息。如果您有任何问题,请提出。

      编辑:我已经阅读了其他人,并同意他们的方法很可能是优越的,我唯一不确定的是数据透视表是否可以扩展以处理不同数量的测试,如果可以(或者您不需要),那么我建议他们的方法,否则我觉得这可能会在您的应用程序中占有一席之地。

      【讨论】:

      • 非常感谢您提供详细信息!我非常感谢!谢谢! -迈克
      【解决方案5】:

      我想为@OMG_Ponies 的答案添加一些解释,因为它可能对 SQL 非超级用户(比如我自己)有用

      让我们创建一个示例表并添加虚拟数据:

      CREATE TABLE t (
         t_ID integer primary key autoincrement not null,
         student integer,
         test text,
         grade text
      );
      
      INSERT INTO t 
         (student, test, grade)
      VALUES
         ('St1', 'T1', 'A'),
         ('St2', 'T1', 'B'),
         ('St3', 'T1', 'B'),
         ('St1', 'T2', 'B'),
         ('St2', 'T2', 'B'),
         ('St3', 'T2', 'A'),
         ('St1', 'T3', 'A'),
         ('St2', 'T3', 'C'),
         ('St3', 'T3', 'B');
      

      所以我们有以下内容:

      t_ID student test grade
      -------------------------
       1   St1     T1     A
       2   St2     T1     B
       3   St3     T1     B
       4   St1     T2     B
       5   St2     T2     B
       6   St3     T2     A
       7   St1     T3     A
       8   St2     T3     C
       9   St3     T3     B
      

      使用语句case when ... then ... end 可以获得所需的列

      SELECT 
         t_ID,
         student,
         (case when t.test = 'T1' then t.grade end) as T1,
         (case when t.test = 'T2' then t.grade end) as T2,
         (case when t.test = 'T3' then t.grade end) as T3
      FROM t
         order by student
      

      结果

      t_ID student  T1    T2     T3
      ----------------------------------
      1    St1      A    NULL  NULL       
      4    St1     NULL   B    NULL   
      7    St1     NULL  NULL   A
      2    St2      B    NULL  NULL   
      5    St2     NULL   B    NULL   
      8    St2     NULL  NULL   C
      3    St3      B    NULL  NULL   
      6    St3     NULL   A    NULL   
      9    St3     NULL  NULL   B 
      

      但是,我们看到有必要按字段“学生”对结果进行分组。当我们分组时,我们必须指定一个聚合函数来指定保留哪个值,以防多行具有相同的“学生”值。在这种情况下,我们使用“max”函数来丢弃空值。

      SELECT 
         t_ID,
         student,
         max(case when t.test = 'T1' then t.grade end) as T1,
         max(case when t.test = 'T2' then t.grade end) as T2,
         max(case when t.test = 'T3' then t.grade end) as T3
      FROM t
      GROUP BY student
      ORDER BY student
      

      结果

      t_ID student  T1   T2   T3
      -----------------------------
      7    St1      A    B     A      
      8    St2      B    B     C  
      9    St3      B    A     B
      

      最后一点。由于我们还没有按 t_ID 进行分组,也没有为其指定聚合函数,因此您应该假设每行的 t_ID 值是每个组的随机值。小心点。

      【讨论】:

        【解决方案6】:

        试试这个

        SELECT Student, MAX(CASE WHEN Test = 'T1' THEN Grade END) AS T1,
           MAX(CASE WHEN Test = 'T2' THEN Grade END) AS T2,
           MAX(CASE WHEN Test = 'T3' THEN Grade END) AS T3 FROM tablename  GROUP BY Student
        

        使用您的表名而不是“tablename”。

        【讨论】:

          猜你喜欢
          • 2021-04-25
          • 1970-01-01
          • 2013-07-31
          • 2021-09-04
          • 1970-01-01
          • 1970-01-01
          • 2018-09-03
          • 1970-01-01
          • 2018-07-22
          相关资源
          最近更新 更多