动态地将行转换为MySQL中的列

时间:2013-12-14 04:46:48

标签: mysql sql yii pivot

我遇到了MySQL问题。我希望基于行拥有动态列。以下是详细信息

SELECT `marks`.`id` , `marks`.`studentID` , `marks`.`subjectID` , `marks`.`mark`
FROM `Mark` `marks`
LEFT OUTER JOIN `Student` `students` ON ( `students`.`id` = `marks`.`studentID` )
WHERE (
`students`.`classID` =1
)
LIMIT 0 , 30

My Output is 
+----+-----------+-----------+------+
| id | studentID | subjectID | mark |
+----+-----------+-----------+------+
|  1 |         1 |         1 |   20 |
|  2 |         1 |         2 |   36 |
|  3 |         2 |         1 |   47 |
|  4 |         2 |         2 |   43 |
+----+-----------+-----------+------+
4 rows in set (0.00 sec)


Output I need is 

+----+-----------+-----------+-----------+
| id | studentID | subject_1 | subject_2 |
+----+-----------+-----------+-----------+
|  1 |         1 |        20 |        36 |
|  2 |         2 |        47 |        43 |
+----+-----------+-----------+-----------+
4 rows in set (0.00 sec)

没有受试者可以非常依赖于“主题”表中的进入者。每个用户只需要一行显示所有标记。这是我使用的表结构。

--
-- Table structure for table `Mark`
--

CREATE TABLE IF NOT EXISTS `Mark` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `studentID` int(11) NOT NULL,
  `subjectID` int(11) NOT NULL,
  `mark` int(11) NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB

--
-- Table structure for table `Student`
--

CREATE TABLE IF NOT EXISTS `Student` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(45) NOT NULL,
  `classID` int(11) NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB

--
-- Table structure for table `Subject`
--

CREATE TABLE IF NOT EXISTS `Subject` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(45) NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB

谢谢你的到来。

3 个答案:

答案 0 :(得分:2)

您不能拥有动态列,至少不能动态生成SQL。您可以根据此答案在存储过程中构建SQL

MySQL pivot table query with dynamic columns

或者,通过在一个查询中选择不同的主题并使用该结果集构建检索您所追求的结果集的SQL,可以更简单地在应用程序代码中执行此操作。至少使用应用程序代码中的逻辑,您可以了解在结果集中将看到多少列。

答案 1 :(得分:0)

试试这个 sqlFiddle demo

SELECT `marks`.`studentID` ,
  GROUP_CONCAT(if(`marks`.`subjectID`=1,`marks`.`mark`,NULL)) AS subject_1,
  GROUP_CONCAT(if(`marks`.`subjectID`=2,`marks`.`mark`,NULL)) AS subject_2
FROM `Mark` `marks`
LEFT OUTER JOIN `Student` `students` ON ( `students`.`id` = `marks`.`studentID` )
WHERE (
`students`.`classID` =1
)
GROUP BY `marks`.`studentID`

如果有更多主题,只需添加更多具有不同GROUP_CONCAT

subjectID

下面是使用存储过程的示例,并使用游标动态构建查询 sqlFiddle

DROP PROCEDURE IF EXISTS  getMarks//
CREATE PROCEDURE getMarks (IN INclassID INT)
BEGIN
  -- First we declare all the variables we will need
  DECLARE loopSubjectId INT;
  DECLARE dynamicSql VARCHAR(5000);
  DECLARE finalSql VARCHAR(5000);
  -- flag which will be set to true, when cursor reaches end of table
  DECLARE exit_loop BOOLEAN;         

  -- Declare the sql for the cursor
  DECLARE example_cursor CURSOR FOR
    SELECT DISTINCT subjectID
    FROM Student s, Mark m
    WHERE s.id = m.studentID
    AND s.classID = INclassID;

  -- Let mysql set exit_loop to true, if there are no more rows to iterate
  DECLARE CONTINUE HANDLER FOR NOT FOUND SET exit_loop = TRUE;
  SET dynamicSql = '';
  SET finalSql = '';
  -- open the cursor
  OPEN example_cursor;

  -- marks the beginning of the loop
  example_loop: LOOP

    -- read the name from next row into the variable l_name
    FETCH  example_cursor INTO loopSubjectId;
    -- check if the exit_loop flag has been set by mysql, 
    -- if it has been set we close the cursor and exit 
    -- the loop
    IF exit_loop THEN
        CLOSE example_cursor;
        LEAVE example_loop;
    END IF;
    SET DynamicSql = CONCAT(DynamicSql,',GROUP_CONCAT(if(marks.subjectID=',loopSubjectId,',marks.mark,NULL)) AS subject_',loopSubjectId);
  END LOOP example_loop;
  SET finalSql = CONCAT('SELECT students.name,marks.studentID',DynamicSql,
                         ' FROM Mark marks
                           LEFT OUTER JOIN Student students ON (students.id = marks.studentID)
                           WHERE (students.classID=?)
                           GROUP BY marks.studentID');
  -- now we run set some variables and run the dynamically built query
  SET @finalSql = finalSql;
  SET @INClassId = INClassID;
  PREPARE stmt1 FROM @finalSql;
  EXECUTE stmt1 USING @INclassID;
END//

此代码结构可从this example

找到

答案 2 :(得分:0)

根据您所需的输出,我假设每个studentID有2行,并且2行中的每一行都有subjectID 1或2。

你可以尝试一下吗?

使用加入

SELECT t1.studentID, t1.mark AS subject_1, t2.mark AS subject_2
FROM
(
    SELECT `marks`.`studentID` , `marks`.mark
    FROM `Mark` as `marks`
    LEFT OUTER JOIN `Student` AS `students`
        ON ( `students`.`id` = `marks`.`studentID` AND subjectID = 1)
    WHERE `students`.`classID` =1
) t1 INNER JOIN 
(

    SELECT `marks`.`studentID` , `marks`.mark
    FROM `Mark` as `marks`
    LEFT OUTER JOIN `Student` AS `students`
        ON ( `students`.`id` = `marks`.`studentID` AND subjectID = 2)
    WHERE `students`.`classID` =1
) t2 ON t1.studentID = t2.studentID;

使用CROSS TABULATION

SELECT `marks`.`studentID`, 
    SUM(IF(subjectID = 1, mark, 0)) AS subject_1,
    SUM(IF(subjectID = 2, 0, mark)) AS subject_2
FROM `Mark` as `marks`
LEFT OUTER JOIN `Student` AS `students`
    ON ( `students`.`id` = `marks`.`studentID`)
WHERE `students`.`classID` =1
GROUP BY marks.studentID

JOINCROSS TABULATION是将垂直结果转换为水平结果的常规形式(我的知识有限)