Frage

Ich habe die folgende Datenstruktur und Daten:

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 möchte die die übergeordneten Datensätze mit den beiden anderen 1 & 2.

finden

Dies ist, was ich herausgefunden habe, aber ich frage mich, ob es eine bessere Art und Weise ist:

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 ein „Elternteil 1“, was richtig ist. Das Problem ist, dass, sobald Sie eine Liste bekommen von 5+ beitritt, wird es chaotisch und wie die Beziehungstabelle wächst, wird es langsam.

Gibt es einen besseren Weg?

Ich bin mit MySQL und PHP, aber das ist wahrscheinlich ziemlich allgemein gehalten.

War es hilfreich?

Lösung

Ok, Getestet habe ich diese. 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: Aggregate (1.016s)

SELECT p.id, p.name Von den Eltern p WHERE (SELECT COUNT (*) FROM Beziehung WHERE parent_id = p.id UND other_id IN (100.101.102.103))

Abfrage 4: UNION Aggregate (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

Eigentlich wurde die obige die falschen Daten erzeugt, so dass es entweder falsch oder ich habe etwas falsch mit ihm. Was auch immer der Fall ist, die oben ist nur eine schlechte Idee.

Wenn das nicht schnell ist, dann müssen Sie auf dem Plan erklärt für die Abfrage suchen. Sie fehlen wahrscheinlich nur entsprechenden 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 eine href lesen <= "https://stackoverflow.com/questions/477006/sql-statement-join-vs- group-by-und-mit / 477013 # 477013" > SQL-Anweisung - ‚Join‘ Vs ‚Group By und‘ mit

.

Hinweis: Die oben genannten Zeitpunkt 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 erstellt mit:

<?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 sie den Tag trägt.

Andere Tipps

Da die übergeordnete Tabelle enthält eindeutige Schlüssel auf (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

ein wenig Vereinfachen, sollte dies funktionieren und effizient.

  

DISTINCT p.id SELECT, p.name
  Von den Eltern p
  INNER JOIN Beziehung r1 ON p.id = r1.parent_id UND r1.other_id = 1 |   INNER JOIN Beziehung r2 ON p.id = r2.parent_id UND r2.other_id = 2

erfordert mindestens einen Datensatz für jeden „anderen“ Wert verbunden. Und das Optimierungsprogramm sollte wissen, dass es nur ein Spiel zu finden, hat jeder, und es braucht nur den Index zu lesen, nicht eine der beiden Tochter-Tabellen, von denen nicht einmal überhaupt verwiesen wird.

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, Sie müssen Mehrweg-nicht beitritt; verketten nur die Ergebnisse der regelmäßigen beitritt, Gruppe durch Ihre IDs und die Zeilen auszuwählen, die in jedem Segment auftauchten.

Dies ist ein häufiges Problem, wenn mehrere Mitarbeiter über einen viele Benutzer zu viele mitmachen. Dies wird oft in Diensten mit dem ‚Tag‘ Konzept zum Beispiel angetroffen Stackoverflow

Sehen Sie meine anderen Post auf eine bessere Architektur für -Tag (in Ihrem Fall 'andere') Speicher

Die Suche ist ein zweistufiger Prozess:

  1. Alle möglichen candiates von TagCollections, die jeder haben / alle Tags, die Sie benötigen (kann einen Cursor von Schleifenkonstrukt einfacher verwenden)
  2. Wählen Sie Daten basieren, die übereinstimmt TagCollection

Die Leistung ist immer schneller durch dort deutlich weniger TagCollections als Datenelemente sind zur Suche

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

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 wird entsprechend der Anzahl der Joins Sie benötigen)

Wenn Sie Ihre Liste der other_id Werte in eine Tabelle setzen können, die ideal wäre. Der folgende Code sieht für Eltern mit MINDESTENS dem angegebenen IDs. Wenn Sie es wollen genau haben die gleichen IDs (das heißt keine Extras) Sie müssten die Abfrage ändern sich geringfügig.

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)
Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top