2010-03-17 63 views
1

我正在重寫SQL,它允許用戶搜索我們網站上的任何其他用戶並顯示他們的角色。MySQL搜索用戶及其角色

舉個例子,角色可以是「Writer」,「Editor」,「Publisher」。

每個角色都將用戶鏈接到發佈。

用戶可以在多個出版物中擔任多個角色。

例表設置:

"users" : user_id, firstname, lastname 
"publications" : publication_id, name 
"link_writers" : user_id, publication_id 
"link_editors" : user_id, publication_id 

當前僞SQL:

SELECT * FROM (
    (SELECT user_id FROM users WHERE firstname LIKE '%Jenkz%') 
    UNION 
    (SELECT user_id FROM users WHERE lastname LIKE '%Jenkz%') 
) AS dt 
JOIN (ROLES STATEMENT) AS roles ON roles.user_id = dt.user_id 

此刻我的角色的說法是:

SELECT dt2.user_id, dt2.publication_id, dt.role FROM (
    (SELECT 'writer' AS role, link_writers.user_id, link_writers.publication_id 
    FROM link_writers) 
    UNION 
    (SELECT 'editor' AS role, link_editors.user_id, link_editors.publication_id 
    FROM link_editors) 
) AS dt2 

原因包裹在UNION角色聲明子句是有些角色更加複雜,需要通過表連接才能找到publication_id和user_id。

舉個例子「出版商」可能翻過兩個表

"link_publishers": user_id, publisher_group_id 
"link_publisher_groups": publisher_group_id, publication_id 

因此,在這種情況下鏈接,查詢我的形成聯盟的一部分將是:

SELECT 'publisher' AS role, link_publishers.user_id, link_publisher_groups.publication_id 
FROM link_publishers 
JOIN link_publisher_groups ON lpg.group_id = lp.group_id 

我很有信心我的表格設置是好的(在研究佈局時,我被警告不要使用一個表格)。我的問題是用戶表中現在有100,000行,每個鏈接表中最多有70,000行。

用戶表中的初始查詢速度很快,但加入會降低速度。

我該如何才能加入相關角色?

--------------------------編輯------------------- --------------- explain http://img155.imageshack.us/img155/4758/stackusersearchjoins.gif

上面解釋(在新窗口中打開以查看完整分辨率)。

紅色底部是「WHERE firstname LIKE'%Jenkz%'」第三行搜索WHERE CONCAT(firstname,'',lastname)LIKE'%Jenkz%'。因此,大行數,但我認爲這是不可避免的,除非有一種方法可以將一個索引放在串聯字段中?

頂部的綠色位僅顯示從角色狀態報告中掃描的總行數。

然後,您可以看到每個單獨的UNION子句(#6 - #12),它們都顯示大量的行。一些指標是正常的,有些是獨特的。

似乎MySQL並未優化使用dt.user_id作爲UNION語句內部的比較。有什麼辦法來強制這種行爲?

請注意,我真正的設置不是出版物和作家,而是「網站管理員」,「玩家」,「團隊」等。

+0

它看起來像UNION中的select語句應該被限制爲「WHERE user_id = dt.user_id」。 – Martin 2010-03-17 13:26:28

+0

感謝您的評論馬丁。不幸的是,我無法訪問我的UNION子句中的「dt」數據。試着給出錯誤「未知列dt.user_id」。 – 2010-03-17 13:52:09

+0

我不認爲這樣的SQL有任何問題 - 我期望dt和dt2之間的user_id上的join操作將執行限制Jenkz談話。除非工會以某種方式打破了普通的優化器。你確定表格有正確的索引嗎?你可以發佈這個查詢的解釋輸出嗎? – AHM 2010-03-17 17:43:38

回答

0

已經簽出OMG小馬的回答爲SO - Use Of Correlated Subquery,我想出了這個:

SELECT * FROM (
    (SELECT user_id FROM users WHERE firstname LIKE '%Jenkz%') 
    UNION 
    (SELECT user_id FROM users WHERE lastname LIKE '%Jenkz%') 
) AS dt 
JOIN (SELECT 'writer' AS role, link_writers.user_id, link_writers.publication_id 
     FROM link_writers 
     UNION 
     SELECT 'editor' AS role, link_editors.user_id, link_editors.publication_id 
     FROM link_editors 
     UNION 
     SELECT 'publisher' AS role, lp.user_id, lpg.publication_id 
     FROM link_publishers lp 
     JOIN link_publisher_groups lpg ON lpg.publisher_group_id = lp.publisher_group_id 
    ) roles on roles.user_id = dt.user_id 

的解釋看起來合理的我的小數據集。它在真實的東西上看起來像什麼?

+0

您好馬丁,這似乎是有道理的,我假設「相關一級深」意味着它可以包裹在一個派生表?目前,性能沒有增加(行數相同,所以我正在試驗強制索引,因爲整個內部表仍然被掃描,如果我沒有,我會更新你的任何進度並標記你的答案) – 2010-03-19 12:39:42

+0

你原來的ROLES查詢有兩個級別,所以我希望刪除(顯然是整型的)外層可能允許MySQL將ON子句推入UNION的組件。無法判斷它是否適用於我的小數據集,而且我仍然不理解含有相關子查詢的手動子句可能適用於JOINS。 – Martin 2010-03-19 13:49:32

+0

查詢拒絕使用該列。 MySQL的網站:http://bugs.mysql.com/bug.php?id=9021顯然它在5.2 ..... 工作我不能從手冊的簡單例子工作:「SELECT * FROM t1 WHERE column1 = ANY (SELECT column1 FROM t2 WHERE t2.column2 = t1.column2); 「負載,但非常緩慢地忽略列。 – 2010-03-19 15:29:04

0

我最初的想法是創建一個臨時表來保存(和索引)與名稱匹配的user_id,並使用它來加入每個鏈接表。不幸的是,在MySQL中,臨時表只能在查詢中與ONCE連接。

令人討厭的解決方法是創建一個永久表,將connection_id添加到主鍵,以便單獨的會話不會感到困惑。

create table tt (connection_id int not null, 
        user_id int not null, 
        firstname varchar(10) not null, 
        lastname varchar(10) not null, 
        primary key(connection_id, user_id)); 

以下順序將每一次重複,你需要一個答案:

delete from tt where connection_id = connection_id(); 

insert into tt 
    SELECT connection_id(), user_id, firstname, lastname FROM users 
    WHERE firstname LIKE '%Jenkz%' 
    UNION 
    SELECT connection_id(), user_id, firstname, lastname FROM users 
    WHERE lastname LIKE '%Jenkz%'; 

其次,現有的UNION進行擴展,使得只有相關USER_ID被拉出:

SELECT 'writer' AS role, link_writers.user_id, link_writers.publication_id 
FROM link_writers 
JOIN tt ON tt.connection_id = connection_id() and tt.user_id = link_writers.user_id 

UNION 

SELECT 'editor' AS role, link_editors.user_id, link_editors.publication_id 
FROM link_editors 
JOIN tt ON tt.connection_id = connection_id() and tt.user_id = link_editors.user_id 

UNION 

SELECT 'publisher' AS role, link_publishers.user_id, link_publisher_groups.publication_id 
FROM link_publishers 
JOIN link_publisher_groups 
    ON link_publisher_groups.publisher_group_id = link_publishers.publisher_group_id 
JOIN tt ON tt.connection_id = connection_id() and tt.user_id = link_publishers.user_id 

也許這會是一個改進,因爲並不是所有鏈表的每一行都被拉入聯合。

EXPLAIN有點奇怪,因爲在tt上只有4個字節的索引正在被使用 - 我期望所有8個字節。也許這是因爲我的數據太少了。

*************************** 1. row *************************** 
      id: 1 
    select_type: PRIMARY 
     table: tt 
     type: ref 
possible_keys: PRIMARY 
      key: PRIMARY 
     key_len: 4 
      ref: const 
     rows: 1 
     Extra: Using index 
*************************** 2. row *************************** 
      id: 1 
    select_type: PRIMARY 
     table: link_writers 
     type: ref 
possible_keys: PRIMARY 
      key: PRIMARY 
     key_len: 4 
      ref: test.tt.user_id 
     rows: 1 
     Extra: Using index 
*************************** 3. row *************************** 
      id: 2 
    select_type: UNION 
     table: tt 
     type: ref 
possible_keys: PRIMARY 
      key: PRIMARY 
     key_len: 4 
      ref: const 
     rows: 1 
     Extra: Using index 
*************************** 4. row *************************** 
      id: 2 
    select_type: UNION 
     table: link_editors 
     type: ref 
possible_keys: PRIMARY 
      key: PRIMARY 
     key_len: 4 
      ref: test.tt.user_id 
     rows: 1 
     Extra: Using index 
*************************** 5. row *************************** 
      id: 3 
    select_type: UNION 
     table: tt 
     type: ref 
possible_keys: PRIMARY 
      key: PRIMARY 
     key_len: 4 
      ref: const 
     rows: 1 
     Extra: Using index 
*************************** 6. row *************************** 
      id: 3 
    select_type: UNION 
     table: link_publishers 
     type: ref 
possible_keys: PRIMARY 
      key: PRIMARY 
     key_len: 4 
      ref: test.tt.user_id 
     rows: 1 
     Extra: Using index 
*************************** 7. row *************************** 
      id: 3 
    select_type: UNION 
     table: link_publisher_groups 
     type: ref 
possible_keys: PRIMARY 
      key: PRIMARY 
     key_len: 4 
      ref: test.link_publishers.publisher_group_id 
     rows: 2 
     Extra: Using index 
*************************** 8. row *************************** 
      id: NULL 
    select_type: UNION RESULT 
     table: <union1,2,3> 
     type: ALL 
possible_keys: NULL 
      key: NULL 
     key_len: NULL 
      ref: NULL 
     rows: NULL 
     Extra: 
8 rows in set (0.00 sec) 
+0

這看起來像一個很好的「如果一切都失敗」的解決方案Martin,感謝您的工作。如果這提供了更多的見解,我現在已經從我的實際查詢中發佈了EXPLAIN? – 2010-03-18 10:57:48

0

另一種方法是稍微規範化設計以更好地支持您的查詢。

要做到這一點,創建一個新表「角色」:

create table role (
    user_id int not null, 
    role enum ('writer', 'editor', 'publisher') not null, 
    primary key (user_id, role) 
); 

這每當一個新行添加到您的鏈接表之一,包括爲user_id更新:

insert ignore into role values($user_id, $role); 

過了一段時間,角色條目很可能已經存在,因此是「忽略」修飾符。

該表可從現有的表自舉:

insert ignore into role select distinct user_id, 'writer' from link_writers; 
insert ignore into role select distinct user_id, 'editor' from link_editors; 
insert ignore into role select distinct user_id, 'publisher' from link_publishers; 

你的搜索查詢就變成了一套簡單連接哪個MySQL的應該沒有問題優化:

SELECT 
    r.user_id, 
    r.role, 
    case r.role 
     when 'writer' then w.publication_id 
     when 'editor' then e.publication_id 
     when 'publisher' then pg.publication_id 
     end as publication_id 
FROM (
    (SELECT user_id FROM users WHERE firstname LIKE '%Jenkz%') 
    UNION 
    (SELECT user_id FROM users WHERE lastname LIKE '%Jenkz%') 
) AS dt 
JOIN role r on r.user_id = dt.user_id 
LEFT JOIN link_writers w on r.user_id = w.user_id and r.role = 'writer' 
LEFT JOIN link_editors e on r.user_id = e.user_id and r.role = 'editor' 
LEFT JOIN link_publishers p on r.user_id = p.user_id and r.role = 'publisher' 
LEFT JOIN link_publisher_groups pg on p.publisher_group_id = pg.publisher_group_id; 

這將給出一個非常「廣泛」的答案。

相關問題