Domanda

Ho la seguente struttura dei dati e dei dati:

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);

Voglio trovare i record padre sia con altri di 1 & 2.

Questo è quello che ho capito, ma mi chiedo se c'è un modo migliore:

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;

Il risultato è 1, "genitore 1" che è corretto. Il problema è che una volta che si ottiene un elenco di 5+ si unisce, diventa disordinato e come la tabella rapporto cresce, diventa lento.

C'è un modo migliore?

Sto usando MySQL e PHP, ma questo è probabilmente abbastanza generico.

È stato utile?

Soluzione

Ok, ho provato questo. Le query dal migliore al peggiore erano:

Query 1: entra a far parte (0.016s; fondamentalmente istantanea )

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

Query 2: ESISTE (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

Query 3: Aggregate (1.016s)

Selezionare p.id, p.name DA padre p DOVE (SELECT COUNT (*) FROM rapporto in cui parent_id = p.id E other_id IN (100.101.102.103))

Query 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

In realtà quanto sopra stava producendo i dati sbagliati quindi è sia sbagliato o ho fatto qualcosa di sbagliato con esso. Qualunque sia il caso, quanto sopra è solo una cattiva idea.

Se questo non è veloce, allora avete bisogno di guardare il piano di spiegare per la query. Probabilmente stai manca solo indici appropriati. Da provare con:

CREATE INDEX ON relationship (parent_id, other_id)

Prima di andare verso il basso il percorso di aggregazione (SELECT COUNT (*) FROM ...) si dovrebbe leggere SQL Statement - ‘Join’ Vs ‘GROUP bY e avere’

.

Nota: I tempi di cui sopra si basano su:

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);

e quasi 800.000 record creati con:

<?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";
    }
}
?>

Quindi, si unisce ancora una volta portare il giorno.

Altri suggerimenti

Dato che tabella principale contiene la chiave unica (parent_id, other_id) si può fare questo:

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

Semplificando un po ', questo dovrebbe funzionare, e in modo efficiente.

  

SELEZIONARE p.id DISTINTO, p.name
  DA padre p
  INNER JOIN rapporto r1 ON p.id = r1.parent_id E r1.other_id = 1
  INNER JOIN rapporto R2 su p.id = r2.parent_id E r2.other_id = 2

richiederà almeno un uniti record per ogni "altro" valore. E l'ottimizzatore dovrebbe sapere che ha solo per trovare un match ciascuno, e ha solo bisogno di leggere l'indice, non è uno dei tavoli controllate, uno dei quali non è nemmeno fatto riferimento a tutti.

Non ho effettivamente provato, ma qualcosa sulla falsariga di:

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

L'idea è che non dovete fare si unisce a più vie; basta concatenare i risultati di regolari si unisce, gruppo per gli ID, e scegliere le righe che si sono presentati in ogni segmento.

Questo è un problema comune quando si cerca più soci tramite una relazione molti a molti join. Questo si incontra spesso in servizi che utilizzano il 'tag' concept per esempio StackOverflow

Vedere il mio altro post su un'architettura migliore per la tag (nel tuo caso 'altro') di memorizzazione

La ricerca è un processo in due fasi:

  1. Trova tutte le possibili candiates di TagCollections che hanno qualsiasi / tutti i tag richiesti (può essere più facile utilizzando un cursore di ciclo costrutto)
  2. basata
  3. Seleziona dati che corrispondono TagCollection

La prestazione è sempre più veloce a causa vi sia significativamente inferiore rispetto TagCollections elementi di dati per la ricerca

È possibile farlo con una selezionata nidificato, ho provato in MSSQL 2005, ma come ha detto lei dovrebbe essere abbastanza generico

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
)

e il numero 2 in COUNT(r.parent_Id)=2 è secondo il numero di join è necessario)

Se si può mettere la vostra lista di valori other_id in una tabella che sarebbe l'ideale. Il codice qui sotto cerca i genitori con almeno gli ID dato. Se si desidera avere esattamente gli stessi ID (cioè nessun extra) che avrebbe dovuto cambiare leggermente la query.

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)
Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top