2012-04-25 4 views
8

Für eine Dating-Anwendung, habe ich ein paar Tabellen, die ich für eine einzelne Ausgabe mit einem LIMIT 10 beider Abfragen kombiniert benötigen. Es scheint im Moment schwierig zu sein, obwohl es kein Problem ist, sie separat abzufragen, aber das LIMIT 10 funktioniert nicht, da die Zahlen nicht exakt sind (zB nicht LIMIT 5 und LIMIT 5, eine Abfrage kann 0 Zeilen zurückgeben , während die anderen 10, abhängig vom Szenario).MySQL - Kombinieren von zwei Select-Anweisungen in einem Ergebnis mit LIMIT effizient

members table 
member_id | member_name 
------------------------ 
    1   Herb 
    2   Karen 
    3   Megan 

dating_requests 
request_id | member1 | member2 | request_time 
---------------------------------------------------- 
    1   1   2  2012-12-21 12:51:45 

dating_alerts 
alert_id | alerter_id | alertee_id | type | alert_time 
------------------------------------------------------- 
    5   3   2  platonic 2012-12-21 10:25:32 

dating_alerts_status 
status_id | alert_id | alertee_id | viewed | viewed_time 
----------------------------------------------------------- 
    4   5   2   0  0000-00-00 00:00:00 

Stellen Sie Karen sind und gerade angemeldet, sollten Sie diese 2 Artikel sehen:

1. Herb requested a date with you. 
2. Megan wants a platonic relationship with you. 

In einer Abfrage mit einem Limit von 10 hier Stattdessen sind zwei Abfragen, die kombiniert werden müssen:

1. Herb requested a date with you. 
    -> query = "SELECT dr.request_id, dr.member1, dr.member2, m.member_name 
       FROM dating_requests dr 
       JOIN members m ON dr.member1=m.member_id 
       WHERE dr.member2=:loggedin_id 
       ORDER BY dr.request_time LIMIT 5"; 
2. Megan wants a platonic relationship with you. 
    -> query = "SELECT da.alert_id, da.alerter_id, da.alertee_id, da.type, 
         da.alert_time, m.member_name 
       FROM dating_alerts da 
       JOIN dating_alerts_status das ON da.alert_id=das.alert_id 
        AND da.alertee_id=das.alertee_id 
       JOIN members m ON da.alerter_id=m.member_id 
       WHERE da.alertee_id=:loggedin_id AND da.type='platonic' 
        AND das.viewed='0' AND das.viewed_time<da.alert_time 
       ORDER BY da.alert_time LIMIT 5"; 

auch hier manchmal beide Tabellen leer sein können, oder 1 Tabelle leer sein kann, oder beide voll ist (wo LIMIT 10 Tritte in) und durch die Zeit geordnet. Irgendwelche Ideen, wie man eine Abfrage erhält, um diese Aufgabe effizient auszuführen? Gedanken, Ratschläge, Glockenspiele, Optimierungen sind willkommen.

+1

Wenn die von den zwei Abfragen zurückgegebenen Spalten identisch sind, verbinden Sie sie mit ['UNION'] (http://dev.mysql.com/doc/refman/5.6/en/union.html) und machen Sie das Ganze eine Unterabfrage an eine äußere Abfrage, die das 'LIMIT' ausführt. Andernfalls können Sie die erforderliche 'LIMIT' für die zweite Abfrage ermitteln (10 minus der Anzahl der von der ersten Abfrage zurückgegebenen Datensätze). Dies ist wahrscheinlich am einfachsten in der Sprache, die Sie zum Aufrufen von Abfragen verwenden. – eggyal

+0

Erstellen Sie eine Tabelle mit Ihren erwarteten Ergebnissen. Sie werden das Problem dort sehen. –

+0

Es ist nicht möglich, 2 Abfragen mit verschiedenen Auswahllisten zu kombinieren. – vyegorov

Antwort

17

Sie können mehrere Abfragen mit UNION kombinieren, aber nur, wenn die Abfragen die gleiche Anzahl von Spalten haben. Idealerweise sind die Spalten nicht nur im Datentyp identisch, sondern auch in ihrer semantischen Bedeutung. Allerdings kümmert sich MySQL nicht um die Semantik und wird mit unterschiedlichen Datentypen umgehen, indem es zu etwas Generischerem übergeht - also könnten Sie die Spalten überladen, um unterschiedliche Bedeutungen von jeder Tabelle zu haben, und dann bestimmen, welche Bedeutung in Ihrem höheren Sinn zutrifft Level-Code (obwohl ich es nicht so empfehle).

Wenn die Anzahl der Spalten unterschiedlich ist oder wenn Sie eine bessere/weniger überladene Ausrichtung von Daten aus zwei Abfragen erreichen möchten, können Sie Dummy-Literalspalten in Ihre SELECT-Anweisungen einfügen. Zum Beispiel:

SELECT t.cola, t.colb, NULL, t.colc, NULL FROM t; 

Sie sogar einige Spalten für die erste Tabelle und andere für den zweiten Tisch reserviert haben könnten, so dass sie NULL anderswo sind (aber nicht vergessen, dass die Spaltennamen aus der ersten Abfrage kommen, so können Sie wollen, um sicherzustellen, sie sind alle dort genannt):

SELECT a, b, c, d, NULL AS e, NULL AS f, NULL AS g FROM t1 
UNION ALL -- specify ALL because default is DISTINCT, which is wasted here 
    SELECT NULL, NULL, NULL, NULL, a, b, c FROM t2; 

Sie könnten versuchen, Ihre zwei Abfragen auf diese Weise auszurichten und sie dann mit einem UNION Operator kombiniert; von LIMIT zum UNION Anwendung, sind Sie in der Nähe Ihres Ziel zu erreichen:

(SELECT ...) 
UNION 
    (SELECT ...) 
LIMIT 10; 

Die einzige Frage, die bleibt, ist, dass, wie oben dargestellt, 10 oder mehrere Datensätze aus der ersten Tabelle werden „Push-out“ alle Datensätze von der Sekunde. Wir können jedoch einen ORDER BY in der äußeren Abfrage verwenden, um dies zu lösen.

es Putting alles zusammen:

(
    SELECT 
    dr.request_time AS event_time, m.member_name,  -- shared columns 
    dr.request_id, dr.member1, dr.member2,    -- request-only columns 
    NULL AS alert_id, NULL AS alerter_id,    -- alert-only columns 
     NULL AS alertee_id, NULL AS type 
    FROM dating_requests dr JOIN members m ON dr.member1=m.member_id 
    WHERE dr.member2=:loggedin_id 
    ORDER BY event_time LIMIT 10 -- save ourselves performing excessive UNION 
) UNION ALL (
    SELECT 
    da.alert_time AS event_time, m.member_name,  -- shared columns 
    NULL, NULL, NULL,         -- request-only columns 
    da.alert_id, da.alerter_id, da.alertee_id, da.type -- alert-only columns 
    FROM 
    dating_alerts da 
    JOIN dating_alerts_status das USING (alert_id, alertee_id) 
    JOIN members m ON da.alerter_id=m.member_id 
    WHERE 
    da.alertee_id=:loggedin_id 
    AND da.type='platonic' 
    AND das.viewed='0' 
    AND das.viewed_time<da.alert_time 
    ORDER BY event_time LIMIT 10 -- save ourselves performing excessive UNION 
) 
ORDER BY event_time 
LIMIT 10; 

Natürlich, jetzt ist es an Ihnen zu bestimmen, welche Art von Zeile mit Ihnen zu tun hat, wie Sie jeden Datensatz in der Ergebnismenge gelesen (schlage vor, Sie testen request_id und/oder alert_id für NULL Werte; alternativ könnte man eine zusätzliche Spalte zu den Ergebnissen hinzufügen, die explizit angibt, aus welcher Tabelle jeder Datensatz stammte, aber es sollte äquivalent sein, vorausgesetzt, dass id Spalten sind NOT NULL).

+0

Danke für das Beispiel und Erklärung eggyal. Die Logik hinter dem Abrufen von Zeilen steht in der ORDER BY-Klausel, der Anforderungszeit und der Alarmzeit in der Reihenfolge, in der sie in die verschiedenen Tabellen eingefügt wurden. Technisch gesehen ist es sehr wahrscheinlich, dass 3 Datensätze aus der ersten Tabelle, dann 2 Datensätze aus der zweiten Tabelle und dann 1 Datensatz zwischen den Tabellen gespeichert werden, bis LIMIT 10 erreicht ist. – Wonka

+0

@Wonka: Es klingt, als ob Sie das mit einem 'ORDER BY' in der äußeren Abfrage erreichen könnten - lassen Sie mich wissen, wenn Sie es nicht herausfinden können. – eggyal

+0

Sie meinen ORDER BY [time_here] LIMIT 10? Was ist mit den Abfragen innerhalb, nur die ORDER BY dr.request_time LIMIT 5 und ORDER BY da.alert_time LIMIT 5? Können Sie mir zeigen, wie die letzte Abfrage mit meinen Abfragen aussehen würde, also bin ich zuversichtlich? – Wonka