2016-04-09 8 views
2

Ich habe eine sehr komplexe Abfrage, die einige Unterabfragen innerhalb einer CASE-Anweisung verwendet.Verhindern abhängiger Unterabfragen in CASE WHEN x THE (Unterabfrage)

Für diese Frage ist die vollständige Abfrage nicht erforderlich und würde nur verhindern, dass Menschen schnell in das Problem eindringen.

Also dieser Beitrag verwendet Pseudocode mit zu arbeiten. Wenn gewünscht, könnte ich die Abfrage posten, aber es ist ein Monster und nutzlos für diese Frage.

Was ich will sind cachefähige Unterabfragen innerhalb der CASE-Anweisung.

SELECT * FROM posts posts 
INNER JOIN posts_shared_to shared_to 
     ON shared_to.post_id = posts.post_id 
INNER JOIN channels.channels 
     ON channels.channel_id = shared_to.channel_id 
WHERE posts.parent_id IS NULL 
AND MATCH (post.text) AGAINST (:keyword IN BOOLEAN MODE) 
AND CASE(
    WHEN channel.read_access IS NULL THEN 1 
    WHEN channel.read_access = 1 THEN 
    (
     SELECT count(*) FROM channel_users 
     WHERE user_id = XXX AND channel_id = channels.channel_id 
    ) 
    WHEN shared_to.read_type = 2 THEN 
    (
     /* another subquery with a join */ 
     /* check if user is in friendlist of post_author */ 
    ) 
    ELSE 0 
    END; 
) 
GROUP BY post.post_id 
ORDER BY post.post_id 
DESC LIMIT n,n 

Wie oben erwähnt, ist dies nur ein vereinfachter Pseudocode.

MySql EXPLAIN sagt, dass alle verwendeten Unterabfragen innerhalb des CASE ABHÄNGIG sind, was bedeutet (wenn ich richtig bin), dass sie jedes Mal ausgeführt werden müssen und nicht zwischengespeichert werden.

Jede Lösung, die zur Beschleunigung dieser Abfrage beiträgt, ist willkommen.

TEIL EDITED: Nun ist die wahre Abfrage wie folgt aussieht:

SELECT a.id, a.title, a.message AS post_text, a.type, a.date, a.author AS uid, 
b.a_name as name, b.avatar, 
shared_to.to_circle AS circle_id, shared_to.root_circle, 
c.circle_name, c.read_access, c.owner_uid, c.profile, 
MATCH(a.title,a.message) AGAINST (:keyword IN BOOLEAN MODE) AS score 

FROM posts a 

/** get userdetails for post_author **/ 
JOIN authors b ON b.id = a.author 

/** get circles posts was shared to **/ 
JOIN posts_shared_to shared_to ON shared_to.post_id = a.id AND shared_to.deleted IS NULL 

/** 
* get circle_details note: at the moment shared_to can contain NULL and 1 too and doesnt need to be a circle_id 
* if to_circle IS NULL post was shared public 
* if to_circle = 1 post was shared to private circles 
* since we use md5 keys as circle ids this can be a string insetad of (int) ... ugly.. 
* 
**/ 
LEFT JOIN circles c ON c.circle_id = shared_to.to_circle 
    /*AND c.circle_name IS NOT NULL */ 
    AND (c.profile IS NULL OR c.profile = 6 OR c.profile = 1) 
    AND c.deleted IS NULL 

LEFT JOIN (
    /** if post is within a channel that requires membership we use this to check if requesting user is member **/ 
    SELECT COUNT(*) users_count, user_id, circle_id FROM circles_users 
    GROUP BY user_id, circle_id 
    ) counts ON counts.circle_id = shared_to.to_circle 
      AND counts.user_id = :me 

LEFT JOIN (
    /** if post is shared private we check if requesting users exists within post authors private circles **/ 
    SELECT count(*) in_circles_count, ci.owner_uid AS circle_owner, cu1.user_id AS user_me 
    FROM circles ci 
    INNER JOIN circles_users cu1 ON cu1.circle_id = ci.circle_id 
           AND cu1.deleted IS NULL 
    WHERE ci.profile IS NULL AND ci.deleted IS NULL 
    GROUP BY user_me, circle_owner 
) users_in_circles ON users_in_circles.user_me = :me 
        AND users_in_circles.circle_owner = a.id 

/** make sure post is a topic **/ 
WHERE a.parent_id IS NULL AND a.deleted IS NULL 

/** search title and post body **/ 
AND MATCH (a.title,a.message) AGAINST (:keyword IN BOOLEAN MODE) 

AND (
    /** own circle **/ 
    c.owner_uid = :me 
    /** site member read_access (this query is for members, for guests we use a different query) **/ 
    OR (c.read_access = 1 OR c.read_access = "1") 
    /** public read_access **/ 
    OR (shared_to.to_circle IS NULL OR (c.read_access IS NULL AND c.owner_uid IS NOT NULL)) 
    /** channel/circle member read_access**/ 
    OR (c.read_access = 3 OR c.read_access = "3" AND counts.users_count > 0) 
    /** for users within post creators private circles **/ 
    OR ( 
    ( 
    /** use shared_to to determine if post is private **/ 
    shared_to.to_circle = "1" OR shared_to.to_circle = 1 
    /** use circle settings to determine global privacy **/ 
    OR (c.owner_uid IS NOT NULL AND c.read_access = 2 OR c.read_access = "2") 
    ) AND users_in_circles.circle_owner = a.author AND users_in_circles.user_me = :me 
    ) 
) 

GROUP BY a.id ORDER BY a.id DESC LIMIT n,n 

Frage: Ist das wirklich der bessere Weg? Wenn ich mir anschaue, wie viele Zeilen die abgeleiteten Tabellen enthalten können, bin ich mir nicht sicher.

Und vielleicht kann mir jemand helfen, durch @ Ollie-Jones die Abfrage wie erwähnt zu ändern:

SELECT stuff, stuff, stuff 
    FROM (
     SELECT post.post_id 
      FROM your whole query 
      ORDER BY post_id DESC 
      LIMIT n,n 
     ) ids 
    JOIN whatever ON whatever.post_id = ids.post_id 
    JOIN whatelse ON whatelse 

Sry wenn dieser Ton slazy aber ich nicht wirklich ein mysqlguy bin und ich bekam Kopfschmerzen seit Jahren nur vom Bau diese Abfrage. : D

Antwort

2

Der beste Weg, um Ihre abhängige Unterabfrage zu beseitigen, ist es, es so zu transformieren, dass es eine virtuelle Tabelle (eine unabhängige Unterabfrage) ist, dann JOIN oder LEFT JOIN mit dem Rest Ihrer Tabellen.

In Ihrem Fall haben Sie

 SELECT count(*) FROM channel_users 
     WHERE user_id = XXX AND channel_id = channels.channel_id 

die unabhängige-Unterabfrage Gießen von dieser So ist

    SELECT COUNT(*) users_count, 
          user_id, channel_id 
        FROM channel_users 
        GROUP BY user_id, channel_id 

Haben Sie sehen, wie das virtuelle Tabelle eine Zeile für jede einzelne Kombination von user_id enthält und channel_id Werte? Jede Zeile hat den Wert users_count, den Sie benötigen. Sie können das dann in den Rest Ihrer Anfrage mitmachen, so. (Beachten Sie, dass INNER JOIN === in MySQL JOIN, so habe ich JOIN es ein wenig zu verkürzen.)

SELECT * FROM posts posts 
    JOIN posts_shared_to shared_to ON shared_to.post_id = posts.post_id 
    JOIN channels.channels ON channels.channel_id = shared_to.channel_id 
    LEFT JOIN (
        SELECT COUNT(*) users_count, 
          user_id, channel_id 
        FROM channel_users 
        GROUP BY user_id, channel_id 
     ) counts ON counts.channel_id = shared_to.channel_id 
       AND counts.user_id = channels.user_id 
    LEFT JOIN ( /* your other refactored subquery */ 
      ) friendcounts ON whatever 
WHERE posts.parent_id IS NULL 
    AND channels.user_id = XXX 
    AND MATCH (post.text) AGAINST (:keyword IN BOOLEAN MODE) 
    AND (   channel.read_access IS NULL 
       OR (channel.read_access = 1 AND counts.users_count > 0) 
       OR (shared_to.read_type = AND friendcount.users_count > 0) 
     ) 
GROUP BY post.post_id 
ORDER BY post.post_id DESC 
LIMIT n,n 

Die MySQL query plan Allgemeines intelligent genug, um eine geeignete Untergruppe von jeder unabhängigen Unterabfrage zu erzeugen.

Pro-Tipp:SELECT lots of columns ... ORDER BY something LIMIT n wird allgemein als verschwenderische Antipattern betrachtet. Es kostet Leistung, weil es eine ganze Reihe von Datenspalten sortiert und den größten Teil des Ergebnisses verwirft.

Pro-Tipp:SELECT * in JOIN Abfrage ist auch verschwenderisch.Sie sind viel besser dran, wenn Sie eine Liste der Spalten angeben, die Sie tatsächlich in Ihrer Ergebnismenge benötigen.

So können Sie Ihre Anfrage Refactoring wieder

SELECT stuff, stuff, stuff 
     FROM (
      SELECT post.post_id 
       FROM your whole query 
       ORDER BY post_id DESC 
       LIMIT n,n 
      ) ids 
     JOIN whatever ON whatever.post_id = ids.post_id 
     JOIN whatelse ON whatelse. 

Die Idee, nur ist die post_id Werte Art zu tun, verwenden Sie dann die begrenzte Teilmenge den Rest der Daten zu ziehen, die Sie benötigen.

+1

Ich wünschte, ich könnte behaupten, dass die Abfragen in meiner Antwort debuggte und einsatzbereit waren. Aber ich bin kein großer Lügner. –

+1

Lächeln. Sieht genial aus. Vielen Dank. Gibt mir genug neue Ideen, mit denen ich arbeiten kann. – user2429266

+0

Ok. Ich nahm einen tiefen Atemzug und arbeitete durch deine gegebene Eingabe. (Es ist schwierig, die ursprüngliche Abfrage auf diese Weise zu erstellen. Es war schwierig, sie überhaupt zu erstellen.) Ist die unabhängige Unterabfrage wirklich effektiver, wenn sie auf großen Tabellen ausgeführt wird? – user2429266

Verwandte Themen