2009-03-01 7 views
1

Ich habe die folgende Datenstruktur und Daten:Wie führst du ein UND mit einem Join aus?

CREATE TABLE `parent` (
    `id` int(11) NOT NULL auto_increment, 
    `name` varchar(10) NOT NULL, 
    PRIMARY KEY (`id`) 
) ENGINE=MyISAM DEFAULT CHARSET=latin1; 

INSERT INTO `parent` VALUES(1, 'parent 1'); 
INSERT INTO `parent` VALUES(2, 'parent 2'); 

CREATE TABLE `other` (
    `id` int(11) NOT NULL auto_increment, 
    `name` varchar(10) NOT NULL, 
    PRIMARY KEY (`id`) 
) ENGINE=MyISAM DEFAULT CHARSET=latin1; 

INSERT INTO `other` VALUES(1, 'other 1'); 
INSERT INTO `other` VALUES(2, 'other 2'); 

CREATE TABLE `relationship` (
    `id` int(11) NOT NULL auto_increment, 
    `parent_id` int(11) NOT NULL, 
    `other_id` int(11) NOT NULL, 
    PRIMARY KEY (`id`) 
) ENGINE=MyISAM DEFAULT CHARSET=latin1; 

INSERT INTO `relationship` VALUES(1, 1, 1); 
INSERT INTO `relationship` VALUES(2, 1, 2); 
INSERT INTO `relationship` VALUES(3, 2, 1); 

ich die die übergeordneten Datensätze mit den beiden anderen 1 & 2.

Dies ist, was ich habe herausgefunden finden wollen, aber ich bin frage mich, ob es einen besseren Weg gibt:

SELECT p.id, p.name 
FROM parent AS p 
    LEFT JOIN relationship AS r1 ON (r1.parent_id = p.id) 
    LEFT JOIN relationship AS r2 ON (r2.parent_id = p.id) 
WHERE r1.other_id = 1 AND r2.other_id = 2; 

Das Ergebnis ist 1, "Elternteil 1", die korrekt ist. Das Problem ist, dass sobald Sie eine Liste von 5+ Joins erhalten, es unordentlich wird und wenn die Beziehungstabelle wächst, wird es langsam.

Gibt es einen besseren Weg?

Ich benutze MySQL und PHP, aber das ist wahrscheinlich ziemlich generisch.

Antwort

4

Ok, ich habe das getestet. Die Abfragen vom besten zum schlechtesten waren:

Abfrage 1: Verknüpft (0.016s, im Grunde Instant)

SELECT p.id, name 
FROM parent p 
JOIN relationship r1 ON p.id = r1.parent_id AND r1.other_id = 100 
JOIN relationship r2 ON p.id = r2.parent_id AND r2.other_id = 101 
JOIN relationship r3 ON p.id = r3.parent_id AND r3.other_id = 102 
JOIN relationship r4 ON p.id = r4.parent_id AND r4.other_id = 103 

Abfrage 2: EXISTS (0.625s)

SELECT id, name 
FROM parent p 
WHERE EXISTS (SELECT 1 FROM relationship WHERE parent_id = p.id AND other_id = 100) 
AND EXISTS (SELECT 1 FROM relationship WHERE parent_id = p.id AND other_id = 101) 
AND EXISTS (SELECT 1 FROM relationship WHERE parent_id = p.id AND other_id = 102) 
AND EXISTS (SELECT 1 FROM relationship WHERE parent_id = p.id AND oth 

Abfrage 3: Aggregat (1.016s)

SELECT p.id, p.name von übergeordneten p WHERE (SELECT COUNT (*) FROM Beziehung WHERE parent_id = p.id other_id UND IN (100.101.102.103))

Abfrage 4: UNION Aggregat (2.39s)

SELECT id, name FROM (
    SELECT p1.id, p1.name 
    FROM parent AS p1 LEFT JOIN relationship as r1 ON(r1.parent_id=p1.id) 
    WHERE r1.other_id = 100 
    UNION ALL 
    SELECT p2.id, p2.name 
    FROM parent AS p2 LEFT JOIN relationship as r2 ON(r2.parent_id=p2.id) 
    WHERE r2.other_id = 101 
    UNION ALL 
    SELECT p3.id, p3.name 
    FROM parent AS p3 LEFT JOIN relationship as r3 ON(r3.parent_id=p3.id) 
    WHERE r3.other_id = 102 
    UNION ALL 
    SELECT p4.id, p4.name 
    FROM parent AS p4 LEFT JOIN relationship as r4 ON(r4.parent_id=p4.id) 
    WHERE r4.other_id = 103 
) a 
GROUP BY id, name 
HAVING count(*) = 4 

Tatsächlich ist die oben produzierte die falschen Daten, so ist es entweder falsch oder ich habe etwas falsch mit ihm. Was auch immer der Fall ist, das obige ist nur eine schlechte Idee.

Wenn das nicht schnell ist, dann müssen Sie den EXPLAIN-Plan für die Abfrage betrachten. Ihnen fehlen wahrscheinlich nur die passenden Indizes. Versuchen Sie es mit:

CREATE INDEX ON relationship (parent_id, other_id) 

Bevor Sie die Route der Aggregation nach unten gehen (SELECT COUNT (*) FROM ...) sollten Sie SQL Statement - “Join” Vs “Group By and Having” lesen.

Hinweis: Die oben genannten Zeitpunkte basieren auf:

CREATE TABLE parent (
    id INT PRIMARY KEY, 
    name VARCHAR(50) 
); 

CREATE TABLE other (
    id INT PRIMARY KEY, 
    name VARCHAR(50) 
); 

CREATE TABLE relationship (
    id INT PRIMARY KEY, 
    parent_id INT, 
    other_id INT 
); 

CREATE INDEX idx1 ON relationship (parent_id, other_id); 
CREATE INDEX idx2 ON relationship (other_id, parent_id); 

und fast 800.000 Datensätze mit erstellt:

<?php 
ini_set('max_execution_time', 600); 

$start = microtime(true); 

echo "<pre>\n"; 
mysql_connect('localhost', 'scratch', 'scratch'); 
if (mysql_error()) { 
    echo "Connect error: " . mysql_error() . "\n"; 
} 
mysql_select_db('scratch'); 
if (mysql_error()) { 
    echo "Selct DB error: " . mysql_error() . "\n"; 
} 

define('PARENTS', 100000); 
define('CHILDREN', 100000); 
define('MAX_CHILDREN', 10); 
define('SCATTER', 10); 
$rel = 0; 
for ($i=1; $i<=PARENTS; $i++) { 
    query("INSERT INTO parent VALUES ($i, 'Parent $i')"); 
    $potential = range(max(1, $i - SCATTER), min(CHILDREN, $i + SCATTER)); 
    $elements = sizeof($potential); 
    $other = rand(1, min(MAX_CHILDREN, $elements - 4)); 
    $j = 0; 
    while ($j < $other) { 
     $index = rand(0, $elements - 1); 
     if (isset($potential[$index])) { 
      $c = $potential[$index]; 
      $rel++; 
      query("INSERT INTO relationship VALUES ($rel, $i, $c)"); 
      unset($potential[$index]); 
      $j++; 
     } 
    } 
} 
for ($i=1; $i<=CHILDREN; $i++) { 
    query("INSERT INTO other VALUES ($i, 'Other $i')"); 
} 

$count = PARENTS + CHILDREN + $rel; 
$stop = microtime(true); 
$duration = $stop - $start; 
$insert = $duration/$count; 

echo "$count records added.\n"; 
echo "Program ran for $duration seconds.\n"; 
echo "Insert time $insert seconds.\n"; 
echo "</pre>\n"; 

function query($str) { 
    mysql_query($str); 
    if (mysql_error()) { 
     echo "$str: " . mysql_error() . "\n"; 
    } 
} 
?> 

Also noch einmal schließt sich der Tag tragen.

+0

Dies ist genau das, was ich habe, nur anders geschrieben. –

+0

Sie haben Ihre Antwort vollständig geändert ... –

+0

Ja. Weil ich die Frage missverstanden habe. – cletus

0

Ich habe nicht wirklich getestet, aber etwas entlang der Linien von:

SELECT id, name FROM (
    SELECT p1.id, p1.name 
    FROM parent AS p1 LEFT JOIN relationship as r1 ON(r1.parent_id=p1.id) 
    WHERE r1.other_id = 1 
    UNION ALL 
    SELECT p2.id, p2.name 
    FROM parent AS p2 LEFT JOIN relationship as r2 ON(r2.parent_id=p2.id) 
    WHERE r2.other_id = 2 
    -- etc 
) GROUP BY id, name 
HAVING count(*) = 2 

Die Idee ist, dass Sie nicht Mehrweg-Verknüpfungen zu tun haben; Verknüpfen Sie einfach die Ergebnisse von regulären Joins, gruppieren Sie nach Ihren IDs und wählen Sie die Zeilen aus, die in jedem Segment angezeigt werden.

+0

Hmmm, das könnte funktionieren. Ich denke, es ist noch unordentlicher als das, was ich habe, aber vielleicht offensichtlicher. –

+0

UNION in Unterabfrage = WIRKLICH WIRKLICH schlecht. Tu das nicht. – cletus

+0

es ist nicht schön, aber der Code, um es zu generieren, ist einfach, und ich vermute, dass Sie eine große Leistungsverbesserung sehen werden, wenn Sie viele Eltern haben. Wenn Sie es versuchen, kommentieren Sie Ihre Ergebnisse - ich bin neugierig. – SquareCog

2

Da übergeordneten Tabelle auf eindeutigen Schlüssel enthält (parent_id, other_id) Sie können dies tun:

select p.id, p.name 
    from parent as p 
where (select count(*) 
     from relationship as r 
     where r.parent_id = p.id 
     and r.other_id in (1,2) 
     ) >= 2 
+0

Sehr gute Idee ... jetzt zu versuchen, mit dem Rest der SQL-Anweisung zu integrieren ... hmmm –

+0

Warnung: Bevor Sie diese Route hinunter gehen, lesen Sie http://stackoverflow.com/questions/477006/sql-statement-join- vs-group-by-and-have/477013 # 477013 – cletus

+0

Diese Lösung (mit Aggregation) ist effizienter als mehrere Auswahlvorgänge, wenn Sie eine unterschiedliche Anzahl von untergeordneten Elementen erwarten. Das Hinzufügen von IDs zur IN-Liste ist lesbarer und möglicherweise effizienter als das Hinzufügen von Bedingungen mit SELECT pro ID. Überprüfen Sie in jedem Fall den Abfrageplan, um Abfragen zu vergleichen ... – topchef

0

Dies ist ein häufiges Problem, wenn über eine viele mehrere Mitarbeiter der Suche zu viele mitmachen. Dies tritt häufig bei Diensten auf, die das "Tag" -Konzept verwenden, z. Stackoverflow

See my other post on a better architecture for tag (in your case 'other') storage

Die Suche ist ein zweistufiger Prozess:

  1. Suche alle möglichen candiates von TagCollections, die jeder haben/alle Tags, die Sie benötigen (kann einen Cursor von Schleifenkonstrukt einfacher verwenden)
  2. Select Daten basieren, die TagCollection Spiele

Leistung ist nur aufgrund schneller dort signif sein icantly weniger TagCollections als Datenelemente suchen

0

Sie können es mit einer verschachtelten select tun, ich habe es in MSSQL 2005 getestet, aber wie Sie gesagt haben, es ist ziemlich allgemein sein sollte

SELECT * FROM parent p 
WHERE p.id in(
    SELECT r.parent_Id 
    FROM relationship r 
    WHERE r.parent_id in(1,2) 
    GROUP BY r.parent_id 
    HAVING COUNT(r.parent_Id)=2 
) 

und die Nummer 2 in COUNT(r.parent_Id)=2 ist entsprechend der Anzahl der Verbindungen, die Sie benötigen)

+0

Warum habe ich einen Downvote für eine getestete Arbeitsabfrage erhalten? (zumindest kannst du erklären, was damit nicht stimmt) –

+0

War nicht ich, aber vielleicht, weil es fast genau dasselbe ist wie: http://stackoverflow.com/questions/599461/how-do-you-perform-an- und-mit-einem-join/599485 # 599485 –

+0

gut, während ich es in MSSQL Studio baute war mir nicht bewusst, dass eine ähnliche Antwort gepostet wurde, immer noch dies ist kein Grund für einen Downvote. –

1

Vereinfacht ein wenig, sollte dies funktionieren, und effizient.

DISTINCT p.id SELECT, p.name
von übergeordneten p
INNER JOIN r1 Beziehung ON p.id = r1.parent_id UND r1.other_id = 1
innere Beziehung r2 ON p.id JOIN = r2.parent_id UND r2.other_id = 2

erfordert mindestens einen verbundenen Datensatz für jeden "anderen" Wert. Und der Optimierer sollte wissen, dass er nur eine Übereinstimmung finden muss und nur den Index lesen muss, nicht eine der Hilfstabellen, von denen eine überhaupt nicht referenziert wird.

0

Wenn Sie Ihre Liste der anderen ID-Werte in eine Tabelle einfügen können, die ideal wäre. Der folgende Code sucht nach Eltern mit MINDESTENS den angegebenen IDs. Wenn Sie genau die gleichen IDs (d. H. Keine Extras) haben möchten, müssten Sie die Abfrage leicht ändern.

SELECT 
    p.id, 
    p.name 
FROM 
    My_Other_IDs MOI 
INNER JOIN Relationships R ON 
    R.other_id = MOI.other_id 
INNER JOIN Parents P ON 
    P.parent_id = R.parent_id 
GROUP BY 
    p.parent_id, 
    p.name 
HAVING 
    COUNT(*) = (SELECT COUNT(*) FROM My_Other_IDs)