使GROUP_CONCAT查询更有效
我有以下查询。这个想法是,它让我知道什么groups
,以及随后users
,有权访问每个component_instance
。我不知道是否有更好的方法来做到这一点的查询是相当缓慢的,但它确实方便我每次处理此表时有这些额外列:使GROUP_CONCAT查询更有效
SELECT component_instances.*,
GROUP_CONCAT(DISTINCT IF(permissions.view, groups.id, NULL)) AS view_group_ids,
GROUP_CONCAT(DISTINCT IF(permissions.edit, groups.id, NULL)) AS edit_group_ids,
GROUP_CONCAT(DISTINCT IF(permissions.view, users.id, NULL)) AS view_user_ids,
GROUP_CONCAT(DISTINCT IF(permissions.edit, users.id, NULL)) AS edit_user_ids
FROM `component_instances`
LEFT OUTER JOIN permissions ON permissions.component_instance_id = component_instances.id
LEFT OUTER JOIN groups ON groups.id = permissions.group_id
LEFT OUTER JOIN groups_users ON groups_users.group_id = groups.id
LEFT OUTER JOIN users ON users.id = groups_users.user_id
GROUP BY component_instances.id
ORDER BY (case when component_instances.ancestry is null then 0 else 1 end), component_instances.ancestry, position
权限表是像这样(原谅了Rails!):
create_table "permissions", :force => true do |t|
t.integer "component_instance_id"
t.integer "group_id"
t.boolean "view", :default => false
t.boolean "edit", :default => false
end
该类型的权限是edit
和view
。一个组可以被分配一个或两个。权限也是递归的,如果在component_instance
上没有组权限,我们必须检查它的祖先以找到设置权限的第一个(如果有的话)。这使得一个查询非常重要,因为我可以将此查询与gem提供的选择逻辑(物化路径树)结合起来。
更新
因为我已经发现了这个查询的基准速度快:
SELECT component_instances.*,
GROUP_CONCAT(DISTINCT view_groups.id) AS view_group_ids,
GROUP_CONCAT(DISTINCT edit_groups.id) AS edit_group_ids,
GROUP_CONCAT(DISTINCT view_users.id) AS view_user_ids,
GROUP_CONCAT(DISTINCT edit_users.id) AS edit_user_ids
FROM `component_instances`
LEFT OUTER JOIN permissions ON permissions.component_instance_id = component_instances.id
LEFT OUTER JOIN groups view_groups ON view_groups.id = permissions.group_id AND permissions.view = 1
LEFT OUTER JOIN groups edit_groups ON edit_groups.id = permissions.group_id AND permissions.edit = 1
LEFT OUTER JOIN groups_users view_groups_users ON view_groups_users.group_id = view_groups.id
LEFT OUTER JOIN groups_users edit_groups_users ON edit_groups_users.group_id = edit_groups.id
LEFT OUTER JOIN users view_users ON view_users.id = view_groups_users.user_id
LEFT OUTER JOIN users edit_users ON edit_users.id = edit_groups_users.user_id
GROUP BY component_instances.id
ORDER BY (case when component_instances.ancestry is null then 0 else 1 end), component_instances.ancestry, position
下面是一个解释上面的查询和表CREATE语句:
+----+-------------+---------------------+--------+-----------------------------------------------+--------------------------------------------+---------+--------------------------------------------+------+------------------------------------------------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+---------------------+--------+-----------------------------------------------+--------------------------------------------+---------+--------------------------------------------+------+------------------------------------------------------+
| 1 | SIMPLE | component_instances | ALL | PRIMARY,index_component_instances_on_ancestry | NULL | NULL | NULL | 119 | "Using temporary; Using filesort" |
| 1 | SIMPLE | permissions | ALL | NULL | NULL | NULL | NULL | 6 | "Using where; Using join buffer (Block Nested Loop)" |
| 1 | SIMPLE | view_groups | eq_ref | PRIMARY | PRIMARY | 4 | 05707d890df9347c.permissions.group_id | 1 | "Using where; Using index" |
| 1 | SIMPLE | edit_groups | eq_ref | PRIMARY | PRIMARY | 4 | 05707d890df9347c.permissions.group_id | 1 | "Using where; Using index" |
| 1 | SIMPLE | view_groups_users | ref | index_groups_users_on_group_id_and_user_id | index_groups_users_on_group_id_and_user_id | 5 | 05707d890df9347c.view_groups.id | 1 | "Using index" |
| 1 | SIMPLE | edit_groups_users | ref | index_groups_users_on_group_id_and_user_id | index_groups_users_on_group_id_and_user_id | 5 | 05707d890df9347c.edit_groups.id | 1 | "Using index" |
| 1 | SIMPLE | view_users | eq_ref | PRIMARY | PRIMARY | 4 | 05707d890df9347c.view_groups_users.user_id | 1 | "Using index" |
| 1 | SIMPLE | edit_users | eq_ref | PRIMARY | PRIMARY | 4 | 05707d890df9347c.edit_groups_users.user_id | 1 | "Using index" |
+----+-------------+---------------------+--------+-----------------------------------------------+--------------------------------------------+---------+--------------------------------------------+------+------------------------------------------------------+
CREATE TABLE `component_instances` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`visible` int(11) DEFAULT '1',
`instance_id` int(11) DEFAULT NULL,
`deleted_on` date DEFAULT NULL,
`instance_type` varchar(255) DEFAULT NULL,
`component_id` int(11) DEFAULT NULL,
`deleted_root_item` int(11) DEFAULT NULL,
`locked_until` datetime DEFAULT NULL,
`theme_id` int(11) DEFAULT NULL,
`position` int(11) DEFAULT NULL,
`ancestry` varchar(255) DEFAULT NULL,
`ancestry_depth` int(11) DEFAULT '0',
`cached_name` varchar(255) DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `index_component_instances_on_ancestry` (`ancestry`)
) ENGINE=InnoDB AUTO_INCREMENT=121 DEFAULT CHARSET=utf8
CREATE TABLE `groups` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(255) NOT NULL DEFAULT '',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8
CREATE TABLE `groups_users` (
`group_id` int(11) DEFAULT NULL,
`user_id` int(11) DEFAULT NULL,
KEY `index_groups_users_on_group_id_and_user_id` (`group_id`,`user_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8
CREATE TABLE `permissions` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`component_instance_id` int(11) DEFAULT NULL,
`group_id` int(11) DEFAULT NULL,
`view` tinyint(1) DEFAULT '0',
`edit` tinyint(1) DEFAULT '0',
PRIMARY KEY (`id`),
KEY `edit_permissions_index` (`edit`,`group_id`,`component_instance_id`),
KEY `view_permissions_index` (`view`,`group_id`,`component_instance_id`)
) ENGINE=InnoDB AUTO_INCREMENT=28 DEFAULT CHARSET=utf8
CREATE TABLE `users` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`real_name` varchar(255) DEFAULT NULL,
`username` varchar(255) NOT NULL DEFAULT '',
`email` varchar(255) NOT NULL DEFAULT '',
`crypted_password` varchar(255) DEFAULT NULL,
`administrator` int(11) NOT NULL DEFAULT '0',
`password_salt` varchar(255) DEFAULT NULL,
`remember_token_expires` datetime DEFAULT NULL,
`persistence_token` varchar(255) DEFAULT NULL,
`disabled` tinyint(1) DEFAULT NULL,
`time_zone` varchar(255) DEFAULT NULL,
`login_count` int(11) DEFAULT NULL,
`failed_login_count` int(11) DEFAULT NULL,
`last_request_at` datetime DEFAULT NULL,
`current_login_at` datetime DEFAULT NULL,
`last_login_at` datetime DEFAULT NULL,
`current_login_ip` varchar(255) DEFAULT NULL,
`last_login_ip` varchar(255) DEFAULT NULL,
`perishable_token` varchar(255) NOT NULL DEFAULT '',
PRIMARY KEY (`id`),
UNIQUE KEY `index_users_on_username` (`username`),
KEY `index_users_on_perishable_token` (`perishable_token`)
) ENGINE=InnoDB AUTO_INCREMENT=12 DEFAULT CHARSET=utf8
的ORDER BY
来自ancestry gem但如果有更好的方法来做到这一点,我很乐意submi作为对他们的拉动请求。
NULL
首先被放置(可以使用COALESCE
替换NULL
而不是使用额外的排序列)。第二件事是减少连接,因为最后两个是我们连接的ID。
SELECT
component_instances.*,
GROUP_CONCAT(DISTINCT view_groups.id) AS view_group_ids,
GROUP_CONCAT(DISTINCT edit_groups.id) AS edit_group_ids,
GROUP_CONCAT(DISTINCT view_groups_users.user_id) AS view_user_ids,
GROUP_CONCAT(DISTINCT edit_groups_users.user_id) AS edit_user_ids
FROM
`component_instances`
LEFT OUTER JOIN permissions
ON permissions.component_instance_id = component_instances.id
LEFT OUTER JOIN groups view_groups
ON view_groups.id = permissions.group_id AND permissions.view = 1
LEFT OUTER JOIN groups edit_groups
ON edit_groups.id = permissions.group_id AND permissions.edit = 1
LEFT OUTER JOIN groups_users view_groups_users
ON view_groups_users.group_id = view_groups.id
LEFT OUTER JOIN groups_users edit_groups_users
ON edit_groups_users.group_id = edit_groups.id
GROUP BY
component_instances.id
ORDER BY
component_instances.ancestry, -- MySQL was sorting the NULL values already correctly
position
;
谢谢Maraca,对不起,我先接受了其他用户的回答,因为我以为是你!我扭转了这一点。你是对的,NULL被放在第一位。我怀疑它支持另一种数据库类型的代码可能作为祖先库不仅仅用于MySQL。我可以重写那部分,所以我会这样做。 –
不幸的是,您的查询结果会导致view_user_ids和edit_user_ids与我的连接查询有不同的结果。他们都在同一时间执行,所以除非你想弄清楚为什么会这样,否则我很乐意接受没有子选择的简单答案。 –
我认为他们一定是。看看结果,就好像子选择只抓取第一组ID并忽略其余部分。额外的连接绝对按预期工作。 –
如果我们没有表结构和索引,那么优化查询几乎是不可能的。使用EXPLAIN
语句是查询优化的必要部分。
没有提到的信息,我可以对你的问题发表评论,你的ORDER BY
部分可以从一定的优化中受益。在条件下使用任何函数或语句都会导致灾难。同样在ORDER BY
中使用空字段也会导致问题。也许最简单的方法是在你的表中添加一个新字段,而不是当前的CASE
声明。
不要忘记,如果记录的数量相当多,那么在条件/顺序/分组依据中的任何字段上都有索引是非常必要的。
[更新]
您的查询是相当简单的。该EXPLAIN
的结果表明,只适合作为候选人要被索引的部分是:
CREATE INDEX inx4 ON permissions (`component_instance_id`, `group_id`, `edit`, `view`);
的EXPLAIN
的第二行显示,有没有在您的查询中使用表permissions
的索引。这是因为MySQL在使用索引时有几条规则:
- 每个表中只有一个索引可用于每个(子)查询。
- 任何索引只能在查询中提及其所有字段的情况下使用(如条件/按/ group by的顺序)。
考虑到您的查询以及提及表permissions
的所有四个字段这一事实,您需要在它们全部四个上都有一个索引或它没有用处。
然而,ORDER BY
可以受益于我前面提到的修正案。
谢谢梅赫兰,我已经添加了你所要求的额外细节。我绝对对ORDER BY语句感兴趣,请参阅更新的问题。我在上面的回答中解释了查询,而不是问题中的问题。 –
如果我是你,习惯于保留所有文本在问题中我会使用'UPDATE'行分隔每个更新,并将所有主题保留在问题部分。它使得阅读更加清晰。 – Mehran
谢谢梅赫兰,我已经更新了。我最初回答自己的问题,然后想到做一个赏金。 –
另外我想如果使用第二个版本,你可以省略最后两个连接,并在group_concat – maraca