2015-07-28 3 views
15

У меня есть следующий запрос. Идея в том, что она позволяет мне знать, что 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 

таблица разрешений, как так (простите рельсам!):

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, нам нужно будет проверить его предков, чтобы найти первое, где установлены разрешения (если они есть). Это делает вопрос с одним вопросом весьма важным, потому что я могу затем объединить этот запрос с логикой выбора, которую обеспечивает драгоценный камень ancestry (материализованное дерево путей).

Update

Я с тех пор нашел этот запрос тесты быстрее:

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 

Вот EXPLAIN для приведенного выше запроса и таблицы 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, но если есть лучший способ сделать это, я был бы рад субмину t это как запрос тянуть к ним.

+0

В любом случае, если бы я был вами, я бы разделил каждое свое обновление, используя строку 'UPDATE', и сохранил все содержимое в вопросе. Это делает его более понятным для чтения. – Mehran

+0

Спасибо, Мехран, я обновил это. Сначала я решил ответить на свой вопрос, а затем подумал, чтобы сделать щедрость. –

+0

Также я думаю, что если вы используете 2-ю версию, вы можете опустить последние два соединения и использовать view_groups_users.user_id и edit_groups_users.user_id в group_concat – maraca

ответ

1

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 
; 
+0

Спасибо Марака, извините, что я принял других пользователей, прежде всего, потому что я думал, что это вы! Я отменил это. Вы правы, сначала NULL. Я подозреваю, что бит кода для поддержки другого типа базы данных, возможно, как библиотека предков не только для MySQL. Я могу переопределить эту часть, поэтому я сделаю это. –

+0

К сожалению, ваш запрос выше приводит к разным результатам для view_user_ids и edit_user_ids vs my joins query. Они оба выполняются примерно в одно и то же время, поэтому, если вы не хотите понять, почему это может быть, я был бы рад принять более простой ответ без подвыборов. –

+0

Думаю, они должны быть. Глядя на результаты, это похоже на то, что подзаголовок захватывает только первый идентификатор группы и игнорирует остальные. Дополнительные соединения определенно работают так, как ожидалось. –

2

Оптимизировать ваш запрос практически невозможно, если у вас нет структуры и индексов таблицы. Использование инструкции EXPLAIN является необходимой частью оптимизации запросов.

Без указанной информации, все, что я могу прокомментировать на ваш вопрос, состоит в том, что ваша часть ORDER BY может извлечь выгоду из некоторой оптимизации. Использование любых функций или операторов в состоянии всегда приведет к катастрофе. Также использование поля с нулевым значением в ORDER BY также приведет к проблемам. Возможно, самым простым способом было бы добавление нового поля в вашу таблицу с 0s и 1s вместо текущего CASE.

Не забывайте, что наличие индекса в любом поле в пределах условия/порядка по/группе всегда необходимо, если количество записей является значительным.

[UPDATE]

Ваш запрос довольно прост. EXPLAIN «s результат показывает, что только части пригодны в качестве кандидата быть проиндексированы являются:

CREATE INDEX inx4 ON permissions (`component_instance_id`, `group_id`, `edit`, `view`); 

EXPLAIN» вторая строка s показывает, что нет индекса таблицы permissions, используемой в запросе. Это связано с тем, что у MySQL есть несколько правил, когда они будут использовать индексы:

  • В каждом запросе (под-) может использоваться только один индекс каждой таблицы.
  • Любой индекс может использоваться только в том случае, если все его поля упоминаются в запросе (как в условиях/порядке по/группе).

Учитывая ваш запрос и тот факт, что указаны все четыре поля таблицы permissions, вам понадобится индекс для всех четырех из них или он бесполезен.

Тем не менее, ORDER BY может воспользоваться поправкой, о которой я упоминал ранее.

+0

Спасибо Мехран, я добавил дополнительные данные, которые вы запросили. Я определенно заинтересован в инструкции ORDER BY, см. Обновленный вопрос. Я ОБЪЯВИЛ запрос в ответе выше, а не в вопросе. –

Смежные вопросы