交叉表的动态MySQL查询/视图

时间:2010-06-26 02:08:05

标签: mysql hibernate

我目前有一个带有以下sql的硬编码视图:

select username
    ,(case user_role.role_id when 1  then true else false end) as ROLE_SUPER 
    ,(case user_role.role_id when 2  then true else false end) as ROLE_ADMIN
    ,(case user_role.role_id when 3  then true else false end) as ROLE_VIEW
    ,(case user_role.role_id when 4  then true else false end) as ROLE_USER
    ,(case user_role.role_id when 5  then true else false end) as ROLE_EMAIL
    from user 
    left outer join user_role on user.id=user_role.user_id
    left outer join role on user_role.role_id = role.id;

我的问题是,是否可以从角色表中的记录动态生成角色列。

1 个答案:

答案 0 :(得分:8)

可以做你想做的事,但我不确定你想要的为什么。获得动态列别名后,您如何计划引用它们?也就是说,如果从数据库中提取列别名,那么您将如何使用它们呢?我可能错过了你问题背后的原因。

无论如何,我假设你有这样的结构:

CREATE TABLE `user` (
    `id` int(11) NOT NULL auto_increment,
    `username` varchar(255) default NULL,
    PRIMARY KEY  (`id`)
);

CREATE TABLE `role` (
    `id` int(11) NOT NULL auto_increment,
    `role` varchar(255) default NULL,
    PRIMARY KEY  (`id`)
);

CREATE TABLE `user_role` (
    `user_id` int(11),
    `role_id` int(11),
    PRIMARY KEY (`user_id`, `role_id`)
);

INSERT INTO `user` (`username`) VALUES
    ('Bob'), ('Alice'), ('Carol'), ('Dave'), ('Eve');

INSERT INTO `role` (`role`) VALUES
    ('Super'), ('Admin'), ('View'), ('User'), ('Email');

INSERT INTO `user_role` VALUES
    (1,1), (2,2), (3,3), (4,4), (5,5);

由此,您可以获取有关用户及其角色的信息:

SELECT username, role.id AS role_id, role.role AS role FROM user_role
JOIN user ON user.id = user_role.user_id
JOIN role ON role.id = user_role.role_id;

+----------+---------+-------+
| username | role_id | role  |
+----------+---------+-------+
| Bob      |       1 | Super |
| Alice    |       2 | Admin |
| Carol    |       3 | View  |
| Dave     |       4 | User  |
| Eve      |       5 | Email |
+----------+---------+-------+

您还可以为特定角色创建列别名:

SELECT username, (role.id = 1) AS Super FROM user_role
JOIN user ON user.id = user_role.user_id
JOIN role ON role.id = user_role.role_id;

+----------+-------+
| username | Super |
+----------+-------+
| Bob      |     1 |
| Alice    |     0 |
| Carol    |     0 |
| Dave     |     0 |
| Eve      |     0 |
+----------+-------+

但是,如果我正确理解您的问题,您要做的是从角色名称生成列别名。您不能将变量用作MySQL语句中的列别名,但您可以构造一个预准备语句:

SET @sql = (SELECT CONCAT(
    'SELECT username, ',
    GROUP_CONCAT('(role.id = ', id, ') AS ', role SEPARATOR ', '),
    ' FROM user_role ',
    'JOIN user ON user.id = user_role.user_id ',
    'JOIN role ON role.id = user_role.role_id;')
FROM role);

SELECT @sql;

+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| @sql                                                                                                                                                                                                                                    |
+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| SELECT username, (role.id = 1) AS Super, (role.id = 2) AS Admin, (role.id = 3) AS View, (role.id = 4) AS User, (role.id = 5) AS Email FROM user_role JOIN user ON user.id = user_role.user_id JOIN role ON role.id = user_role.role_id; |
+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+

正如您将从输出中看到的那样,生成一个包含SQL SELECT语句的字符串。您现在需要从该字符串创建一个预准备语句,并执行结果:

PREPARE stmt FROM @sql;
EXECUTE stmt;
+----------+-------+-------+------+------+-------+
| username | Super | Admin | View | User | Email |
+----------+-------+-------+------+------+-------+
| Bob      |     1 |     0 |    0 |    0 |     0 |
| Alice    |     0 |     1 |    0 |    0 |     0 |
| Carol    |     0 |     0 |    1 |    0 |     0 |
| Dave     |     0 |     0 |    0 |    1 |     0 |
| Eve      |     0 |     0 |    0 |    0 |     1 |
+----------+-------+-------+------+------+-------+

修改

为了更容易调用交叉表查询,可以将整个内容包装在存储过程中。在以下示例中,我无法使GROUP_CONCATSET @sql语句中工作,如上所述。相反,我不得不把它分成自己的变量。我不确定为什么这不起作用,但最终结果是一样的,代码可能不那么神秘:

DELIMITER //
DROP PROCEDURE IF EXISTS test.crosstab//
CREATE PROCEDURE test.crosstab()
BEGIN
    SET @cols = (SELECT GROUP_CONCAT(
        '(role.id = ', id, ') AS ', role
        SEPARATOR ', ') FROM role);
    SET @sql = CONCAT(
        'SELECT username, ',
        @cols,
        ' FROM user_role ',
        'JOIN user ON user.id = user_role.user_id ',
        'JOIN role ON role.id = user_role.role_id;');
    PREPARE stmt FROM @sql;
    EXECUTE stmt;
END;
//
DELIMITER ;

CALL test.crosstab();

+----------+-------+-------+------+------+-------+
| username | Super | Admin | View | User | Email |
+----------+-------+-------+------+------+-------+
| Bob      |     1 |     0 |    0 |    0 |     0 |
| Alice    |     0 |     1 |    0 |    0 |     0 |
| Carol    |     0 |     0 |    1 |    0 |     0 |
| Dave     |     0 |     0 |    0 |    1 |     0 |
| Eve      |     0 |     0 |    0 |    0 |     1 |
+----------+-------+-------+------+------+-------+