1

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

  1. 用户帐户
  2. 特征
  3. 访问级别

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

架构:

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

4

3 回答 3

1

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

首先在数据库设计上,我建议的更改是在 table 中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 ;

当您可以只存储and时,无需将user_loginand存储在此表中。将这两个用作各自表的外键。typeuser_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

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

这是使用准备好的语句的查询版本:

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

于 2012-08-17T18:01:10.610 回答
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 = ?
于 2012-08-06T23:38:50.553 回答
0

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

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

于 2012-08-06T23:17:34.073 回答