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的副本。我看看能不能弄明白。