MySQL使用表的行值连接三个表

MySQL使用表的行值连接三个表,mysql,join,pivot,case,Mysql,Join,Pivot,Case,这很快变得复杂起来,我开始质疑数据库设计。 该应用程序的基本概念是: 用户帐户 特征 访问级别 因此,用户对每个功能都有不同的访问级别。我认为这是一个相当基本和常见的应用程序 模式: CREATE TABLE `user_accounts` ( `id` int(11) NOT NULL AUTO_INCREMENT, `user_login` varchar(100) COLLATE utf8_unicode_ci NOT NULL, `user_password` varchar

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

  • 用户帐户
  • 特征
  • 访问级别
  • 因此,用户对每个功能都有不同的访问级别。我认为这是一个相当基本和常见的应用程序

    模式:

    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小提琴上

    虽然我不熟悉MySQL的细节,但在我看来,您描述的是一个非常基本的pivot表查询示例。你所寻找的对我来说似乎是合理的,所以我认为根据你在这里展示的内容,我不会太在意重新访问数据模型。您可能会发现将“级别”放回“类型”表,基于ol'saw“规格化直到命中伤害,非规格化直到它工作:)”


    只有我的0.02美元。祝你好运。

    虽然我不熟悉MySQL的细节,但在我看来,你描述的是一个非常基本的pivot表查询示例。你所寻找的对我来说似乎是合理的,所以我认为根据你在这里展示的内容,我不会太在意重新访问数据模型。您可能会发现将“级别”放回“类型”表,基于ol'saw“规格化直到命中伤害,非规格化直到它工作:)”


    只有我的0.02美元。祝你好运。

    我通常使用BIGINT列并使用位掩码来设置值

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

    为某人提供1级和2级访问权限:

    update user set access_level = 2 & 4
    
    有人有2级权限吗

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

    我通常使用BIGINT列并使用位掩码来设置值

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

    为某人提供1级和2级访问权限:

    update user set access_level = 2 & 4
    
    有人有2级权限吗

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

    我对您的表设计以及如何以您想要的格式获取数据都有一些建议

    首先关于数据库设计,我建议的更改是在表
    user\u access\u 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 ;
    
    如果只需存储
    用户id
    类型id
    ,则无需在此表中存储
    用户登录名
    类型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
    

    如果您提前知道要获取其值的类型列,则此版本将起作用。但如果它未知,则可以使用准备好的语句动态生成它

    以下是使用预处理语句的查询版本:

    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;
    

    请参见a

    我对您的表设计以及如何以所需格式获取数据提出了一些建议

    首先关于数据库设计,我建议的更改是在表
    user\u access\u 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 ;
    
    如果只需存储
    用户id
    类型id
    ,则无需在此表中存储
    用户登录名
    类型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
    

    如果您提前知道要获取其值的类型列,则此版本将起作用。但如果它未知,则可以使用准备好的语句动态生成它

    以下是使用预处理语句的查询版本:

    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;
    

    谢谢你这么好的回答!我相信第二个版本可以工作,但是当我在phpMyAdmin中运行查询时,我得到了执行的
    未知准备语句处理程序(stmt),我只是对准备语句不太熟悉,不知道这意味着什么。我对它做了一些研究,但我觉得我可能走错了路,因为我将从PHP查询数据库。当我从PHP(PDO)调用它时,我从
    fetchAll()
    得到一个空数组,没有错误。谢谢你的回答,这非常有帮助,我学到了很多。@JDavis很遗憾,我现在手头没有MySQL的副本。我会看看能不能弄明白。谢谢你这么好的回答!我相信第二个版本可以工作,但是当我在phpMyAdmin中运行查询时,我得到了执行的
    未知准备语句处理程序(stmt),我只是对准备语句不太熟悉,不知道这意味着什么。我对它做了一些研究,但我觉得我可能走错了路,因为我将从PHP查询数据库。当我从PHP(PDO)调用它时,我从
    fetchAll()
    得到一个空数组,没有错误。谢谢你的回答,这非常有帮助,我学到了很多。@JDavis很遗憾,我现在手头没有MySQL的副本。我看看能不能弄明白。