Вопрос

У меня есть следующая структура данных и data:

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

Я хочу найти родительские записи с обоими другими 1 и 2.

Это то, что я выяснил, но мне интересно, есть ли способ получше:

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;

Результатом будет 1, "родительский 1", что является правильным.Проблема в том, что как только вы получаете список из более чем 5 объединений, он становится беспорядочным, и по мере роста таблицы связей он становится медленным.

Есть ли способ получше?

Я использую MySQL и PHP, но это, вероятно, довольно общий язык.

Это было полезно?

Решение

Хорошо, я проверил это.Запросы от лучшего к худшему были следующими:

Запрос 1:Соединения (0,016 с;в основном мгновенный)

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

Запрос 2:СУЩЕСТВУЕТ (0,625 с)

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

Запрос 3:Совокупный (1,016 с)

ВЫБЕРИТЕ p.id, p.name ИЗ родительского p ГДЕ (ВЫБЕРИТЕ COUNT(*) ИЗ отношения, ГДЕ parent_id = p.id И other_id В (100,101,102,103))

Запрос 4:Объединительный агрегат (2,39 с)

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

На самом деле вышеописанное приводило к получению неправильных данных, так что это либо неправильно, либо я сделал с этим что-то не так.Как бы то ни было, вышесказанное - просто плохая идея.

Если это не быстро, то вам нужно взглянуть на план объяснения запроса.Вероятно, вам просто не хватает соответствующих индексов.Попробуйте это с:

CREATE INDEX ON relationship (parent_id, other_id)

Прежде чем вы перейдете по пути агрегирования (ВЫБЕРИТЕ COUNT(*) ИЗ ...), вы должны прочитать Оператор SQL - ”Присоединиться“ Против “Сгруппировать по и имея”.

Примечание: Приведенные выше тайминги основаны на:

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

и почти 800 000 записей, созданных с помощью:

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

Итак, еще раз присоединяемся к carry the day.

Другие советы

Учитывая, что родительская таблица содержит уникальный ключ (parent_id,other_id), вы можете сделать это:

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

Немного упрощая, это должно работать и эффективно.

ВЫБЕРИТЕ ОТЛИЧНЫЕ p.id, p.name
ОТ родительского п
Отношение ВНУТРЕННЕЕ СОЕДИНЕНИЕ r1 ON p.id = r1.parent_id И r1.other_id = 1
Отношение ВНУТРЕННЕЕ СОЕДИНЕНИЕ r2 ON p.id = r2.parent_id И r2.other_id = 2

потребуется хотя бы одна объединенная запись для каждого «другого» значения.И оптимизатор должен знать, что ему нужно найти только одно совпадение, и ему нужно прочитать только индекс, а не какую-либо из вспомогательных таблиц, на одну из которых вообще нет ссылок.

На самом деле я это не проверял, но что-то вроде:

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

Идея в том, что вам не обязательно выполнять многосторонние соединения;просто объедините результаты обычных объединений, сгруппируйте их по идентификаторам и выберите строки, которые появились в каждом сегменте.

Это распространенная проблема при поиске нескольких партнеров через соединение «многие ко многим».Это часто встречается в сервисах, использующих концепцию «тега», например.Переполнение стека

См. другой мой пост о лучшей архитектуре для хранения тегов (в вашем случае «другое»).

Поиск состоит из двух этапов:

  1. Найдите все возможные кандидаты TagCollections, которые имеют любые/все необходимые вам теги (может быть проще использовать курсор или конструкцию цикла)
  2. Выберите данные на основе, соответствующие TagCollection

Производительность всегда выше, поскольку коллекций тегов значительно меньше, чем элементов данных для поиска.

Вы можете сделать это с помощью вложенного выбора, я тестировал его в MSSQL 2005, но, как вы сказали, он должен быть довольно общим.

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
)

и цифра 2 в COUNT(r.parent_Id)=2 в зависимости от количества соединений, которые вам нужны)

Если бы вы могли поместить список значенийother_id в таблицу, это было бы идеально.Код ниже ищет родителей, по крайней мере, с указанными идентификаторами.Если вы хотите, чтобы у него были ТОЧНО одинаковые идентификаторы (т.никаких дополнений) вам придется немного изменить запрос.

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)
Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top