2015-07-28 68 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 

權限表是像這樣(原諒了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 

該類型的權限是editview。一個組可以被分配一個或兩個。權限也是遞歸的,如果在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作爲對他們的拉動請求。

+0

如果我是你,習慣於保留所有文本在問題中我會使用'UPDATE'行分隔每個更新,並將所有主題保留在問題部分。它使得閱讀更加清晰。 – Mehran

+0

謝謝梅赫蘭,我已經更新了。我最初回答自己的問題,然後想到做一個賞金。 –

+0

另外我想如果使用第二個版本,你可以省略最後兩個連接,並在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

謝謝Maraca,對不起,我先接受了其他用戶的回答,因爲我以爲是你!我扭轉了這一點。你是對的,NULL被放在第一位。我懷疑它支持另一種數據庫類型的代碼可能作爲祖先庫不僅僅用於MySQL。我可以重寫那部分,所以我會這樣做。 –

+0

不幸的是,您的查詢結果會導致view_user_ids和edit_user_ids與我的連接查詢有不同的結果。他們都在同一時間執行,所以除非你想弄清楚爲什麼會這樣,否則我很樂意接受沒有子選擇的簡單答案。 –

+0

我認爲他們一定是。看看結果,就好像子選擇只抓取第一組ID並忽略其餘部分。額外的連接絕對按預期工作。 –

2

如果我們沒有表結構和索引,那麼優化查詢幾乎是不可能的。使用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可以受益於我前面提到的修正案。

+0

謝謝梅赫蘭,我已經添加了你所要求的額外細節。我絕對對ORDER BY語句感興趣,請參閱更新的問題。我在上面的回答中解釋了查詢,而不是問題中的問題。 –