MySQL使用表的行值加入三个表

时间:2012-08-06 23:09:26

标签: mysql join pivot case

这很快变得复杂,我开始质疑数据库设计。 该应用程序的基本概念是:

  1. 用户帐户
  2. 功能
  3. 访问级别
  4. 因此,用户对每个功能都有不同的访问级别。我想是相当基本和普遍的应用。

    架构:

    CREATE TABLE `user_accounts` (
      `id` int(11) NOT NULL AUTO_INCREMENT,
      `user_login` varchar(100) COLLATE utf8_unicode_ci NOT NULL,
      `user_password` varchar(60) COLLATE utf8_unicode_ci NOT NULL,
      `user_fname` varchar(100) COLLATE utf8_unicode_ci NOT NULL,
      `user_lname` varchar(100) COLLATE utf8_unicode_ci NOT NULL,
      `user_group` varchar(32) COLLATE utf8_unicode_ci NOT NULL DEFAULT 'Default',
      PRIMARY KEY (`id`),
      UNIQUE KEY `user_login` (`user_login`)
    ) ENGINE=InnoDB  DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci ;
    
    INSERT INTO `user_accounts` VALUES(1, 'email@example.com', 'secret', 'Example', 'Name', 'Admin');
    INSERT INTO `user_accounts` VALUES(2, 'john@example.com', 'secret', 'John', 'Doe', 'Trainer');
    INSERT INTO `user_accounts` VALUES(3, 'jane@example.com', 'secret', 'Jane', 'Doe', 'Default');
    
    CREATE TABLE `user_access_meta` (
      `id` int(11) NOT NULL AUTO_INCREMENT,
      `type` varchar(50) COLLATE utf8_unicode_ci NOT NULL,
      PRIMARY KEY (`id`),
      UNIQUE KEY `type` (`type`)
    ) ENGINE=InnoDB  DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
    
    INSERT INTO `user_access_meta` VALUES(1, 'type_1');
    INSERT INTO `user_access_meta` VALUES(2, 'type_2');
    INSERT INTO `user_access_meta` VALUES(3, 'type_3');
    INSERT INTO `user_access_meta` VALUES(4, 'type_4');
    INSERT INTO `user_access_meta` VALUES(5, 'type_5');
    INSERT INTO `user_access_meta` VALUES(6, 'type_6');
    
    CREATE TABLE `user_access_levels` (
      `id` int(11) NOT NULL AUTO_INCREMENT,
      `user_login` varchar(100) COLLATE utf8_unicode_ci NOT NULL,
      `type` varchar(50) COLLATE utf8_unicode_ci NOT NULL,
      `level` int(1) NOT NULL,
      PRIMARY KEY (`id`),
      UNIQUE KEY `user_login_2` (`user_login`,`type`),
      KEY `user_login` (`user_login`)
    ) ENGINE=InnoDB  DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci ;
    
    INSERT INTO `user_access_levels` VALUES(1, 'email@example.com', 'type_1', 1);
    INSERT INTO `user_access_levels` VALUES(2, 'email@example.com', 'type_2', 1);
    INSERT INTO `user_access_levels` VALUES(3, 'email@example.com', 'type_3', 0);
    INSERT INTO `user_access_levels` VALUES(4, 'email@example.com', 'type_5', 2);
    INSERT INTO `user_access_levels` VALUES(5, 'john@example.com', 'type_2', 1);
    INSERT INTO `user_access_levels` VALUES(6, 'john@example.com', 'type_3', 1);
    INSERT INTO `user_access_levels` VALUES(7, 'john@example.com', 'type_5', 3);
    INSERT INTO `user_access_levels` VALUES(8, 'jane@example.com', 'type_4', 1);
    

    这些表实际上有很多字段,并且它们之间有外键约束,但是我已经为这个例子对它们进行了条纹化处理。它们也可单独用于其他目的。

    我已经成功地将所有三个表连接到一个用户:

    SELECT
    ua.`user_fname`,
    uam.`type`,
    ual.`level`
    FROM `user_access_meta` uam
    LEFT JOIN `user_access_levels` ual
    ON ual.`user_login` = 'email@example.com'
    AND uam.`type` = ual.`type`
    JOIN `user_accounts` ua
    ON ua.`user_login` = 'email@example.com';
    

    输出:

    | USER_FNAME |   TYPE |  LEVEL |
    --------------------------------
    |    Example | type_1 |      1 |
    |    Example | type_2 |      1 |
    |    Example | type_3 |      0 |
    |    Example | type_4 | (null) |
    |    Example | type_5 |      2 |
    |    Example | type_6 | (null) |
    

    即使这不是理想的,但我能想到的只是它的目的。


    现在,我需要做的是选择所有用户,包括他们的访问级别。它看起来像这样:

    | USER_FNAME |  type_1 |  type_2 |  type_3 |  type_4 |  type_5 |  type_6 |
    --------------------------------------------------------------------------
    |    Example |       1 |       1 |       0 |  (null) |       2 |  (null) |
    |       John |  (null) |       1 |       1 |  (null) |       3 |  (null) |
    |       Jane |  (null) |  (null) |  (null) |       1 |  (null) |  (null) |
    

    我觉得这可能不是最好的设计,但我采用这种设计的原因是我可以轻松添加和删除功能,甚至可以暂时禁用它们。

    重新考虑设计吗?是否有可能通过这种设计得到我正在寻找的结果?

    我把它放在SQL Fiddle上。 http://sqlfiddle.com/#!2/bb313/2/0

3 个答案:

答案 0 :(得分:1)

我对您的桌面设计有一些建议,然后如何以您想要的格式获取数据。

首先在数据库设计上,我建议的更改在表user_access_levels中。改变你的表格:

CREATE TABLE `user_access_levels` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `user_id` int(11) NOT NULL,
  `type_id` int(11) NOT NULL,
  `level` int(1) NOT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `user_id_2` (`user_id`,`type_id`),
  KEY `user_id` (`user_id`)
) ENGINE=InnoDB  DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci ;

如果您只能存储user_logintype,则无需在此表格中存储user_idtype_id。将这两者用作各自表的外键。

然后以您想要的格式获取数据。 MySQL没有PIVOT函数,因此您需要使用带有聚合函数的CASE语句。

select ua.user_fname, 
  MIN(CASE WHEN uam.type = 'type_1' THEN ual.level END) type_1,
  MIN(CASE WHEN uam.type = 'type_2' THEN ual.level END) type_2,
  MIN(CASE WHEN uam.type = 'type_3' THEN ual.level END) type_3,
  MIN(CASE WHEN uam.type = 'type_4' THEN ual.level END) type_4,
  MIN(CASE WHEN uam.type = 'type_5' THEN ual.level END) type_5,
  MIN(CASE WHEN uam.type = 'type_6' THEN ual.level END) type_6
FROM user_accounts ua
LEFT JOIN user_access_levels ual
  ON ua.id = ual.user_id
LEFT JOIN user_access_meta uam
  ON ual.type_id = uam.id
group by ua.user_fname

查看SQL Fiddle with a Demo

如果您提前知道要获取值的类型列,则此版本将起作用。但是如果它是未知的,那么你可以使用预处理语句动态生成它。

以下是使用预准备语句的查询版本:

SET @sql = NULL;
SELECT
  GROUP_CONCAT(DISTINCT
    CONCAT(
      'MIN(case when type = ''',
      type,
      ''' then level end) AS ',
      replace(type, ' ', '')
    )
  ) INTO @sql
FROM user_access_meta;

SET @sql = CONCAT('SELECT ua.user_fname, ', @sql, ' FROM user_accounts ua
LEFT JOIN user_access_levels ual
  ON ua.id = ual.user_id
LEFT JOIN user_access_meta uam
  ON ual.type_id = uam.id
group by ua.user_fname');

PREPARE stmt FROM @sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;

查看SQL Fiddle with Demo

答案 1 :(得分:0)

虽然我不熟悉MySQL的细节,但在我看来,你正在描述一个非常基本的数据透视表查询示例。您正在寻找的内容对我来说似乎是合理的,所以我不认为根据您在此处显示的内容,我会过于担心重新访问数据模型。您可能会发现将“级别”放回“类型”表,基于ol'锯“Normalize til hit hurts,denormalize直到它工作:)”

只需0.02美元。祝你好运。

答案 2 :(得分:0)

我通常使用BIGINT列并使用位屏蔽来设置值。

例如level1 = 2,level2 = 4,level3 = 8,level4 = 16等。

授予某人level1和level2访问权限:

update user set access_level = 2 & 4

有人有二级访问权限吗?

select 1 from user where access_level | 2 AND user_id = ?