2012-06-27 2 views
1

У меня есть таблица с двумя столбцами (varchar from, varchar to). В этой таблице представлены соединения между узлами (от узла, узла). Я хочу, чтобы все узлы подключались от или к узлу, который я указываю, и узлам, связанным с или с этими узлами. В настоящее время я использую следующий запрос, который дает мне правильные результаты, но я ищу более быстрое решение.оптимизация запроса mysql

//currently used query specified node "node1" 
SELECT tonode as node 
FROM conn 
WHERE 
fromnode 
IN 
(SELECT tonode as node FROM conn WHERE fromnode="node1" 
UNION 
SELECT fromnode as node FROM conn WHERE tonode="node1") 
UNION 
SELECT fromnode as node 
FROM conn 
WHERE 
tonode 
IN 
(SELECT tonode as node FROM conn WHERE fromnode="node1" 
UNION 
SELECT fromnode as node FROM conn WHERE tonode="node1") 


//create table for conn table 
CREATE TABLE `conn` (
    `fromnode` varchar(70) NOT NULL, 
    `tonode` varchar(70) NOT NULL, 
    PRIMARY KEY (`fromnode`,`tonode`) USING BTREE 
) ENGINE=InnoDB DEFAULT CHARSET=utf8 ROW_FORMAT=DYNAMIC; 
INSERT INTO `conn` (`fromnode`,`tonode`) VALUES 
('node1','node2'), 
('node1','node3'), 
('node3','node2'), 
('node4','node1'), 
('node4','node2'), 
('node4','node5'), 
('node5','node6'), 
('node4','node3'); 
+0

Является ли ваше намерение идти только на два уровня в глубину? Поэтому, если node1 => node2 => node3 => node4. Для node1 вам нужны только node2 и node3, но не node4? – bobwienholt

+0

Да, я намерен идти ровно на 2 уровня глубиной – PainFly

+0

Являются ли эти соединения однонаправленными или двусторонними? –

ответ

3

Моя оптимизированная версия:

SET @origin = "node1"; 
SELECT DISTINCT 
IF(c1.fromnode = @origin, 
    IF(c1.tonode = c2.tonode, 
    IF(c2.fromnode = @origin, c2.tonode, c2.fromnode), 
    IF(c2.tonode = @origin, c2.fromnode, c2.tonode) 
    ), 
    IF(c1.fromnode = c2.tonode, 
    IF(c2.fromnode = @origin, c2.tonode, c2.fromnode), 
    IF(c2.tonode = @origin, c2.fromnode, c2.tonode) 
    ) 
) AS node 
FROM conn AS c1 
LEFT JOIN conn AS c2 ON (c1.fromnode = c2.fromnode OR c1.tonode = c2.fromnode OR c1.fromnode = c2.tonode OR c1.tonode = c2.tonode) 
WHERE c1.fromnode = @origin OR c1.tonode = @origin; 

описательной вывод вашего старого запроса:

+----+--------------------+------------+--------+---------------+---------+---------+------------+------+--------------------------+ 
| id | select_type  | table  | type | possible_keys | key  | key_len | ref  | rows | Extra     | 
+----+--------------------+------------+--------+---------------+---------+---------+------------+------+--------------------------+ 
| 1 | PRIMARY   | conn  | index | NULL   | PRIMARY | 424  | NULL  | 8 | Using where; Using index | 
| 2 | DEPENDENT SUBQUERY | conn  | eq_ref | PRIMARY  | PRIMARY | 424  | const,func | 1 | Using where; Using index | 
| 3 | DEPENDENT UNION | conn  | eq_ref | PRIMARY  | PRIMARY | 424  | func,const | 1 | Using where; Using index | 
| NULL | UNION RESULT  | <union2,3> | ALL | NULL   | NULL | NULL | NULL  | NULL |       | 
| 4 | UNION    | conn  | index | NULL   | PRIMARY | 424  | NULL  | 8 | Using where; Using index | 
| 5 | DEPENDENT SUBQUERY | conn  | eq_ref | PRIMARY  | PRIMARY | 424  | const,func | 1 | Using where; Using index | 
| 6 | DEPENDENT UNION | conn  | eq_ref | PRIMARY  | PRIMARY | 424  | func,const | 1 | Using where; Using index | 
| NULL | UNION RESULT  | <union5,6> | ALL | NULL   | NULL | NULL | NULL  | NULL |       | 
| NULL | UNION RESULT  | <union1,4> | ALL | NULL   | NULL | NULL | NULL  | NULL |       | 
+----+--------------------+------------+--------+---------------+---------+---------+------------+------+--------------------------+ 

описательной выход моего запроса:

+----+-------------+-------+-------+---------------+---------+---------+------+------+-------------------------------------------+ 
| id | select_type | table | type | possible_keys | key  | key_len | ref | rows | Extra          | 
+----+-------------+-------+-------+---------------+---------+---------+------+------+-------------------------------------------+ 
| 1 | SIMPLE  | c1 | index | PRIMARY  | PRIMARY | 424  | NULL | 8 | Using where; Using index; Using temporary | 
| 1 | SIMPLE  | c2 | index | PRIMARY  | PRIMARY | 424  | NULL | 8 | Using index        | 
+----+-------------+-------+-------+---------------+---------+---------+------+------+-------------------------------------------+ 
+0

Нужно посмотреть на план выполнения, чтобы убедиться, что оптимизатор создал работоспособный план. Я совершенно уверен, что этого не произойдет. – usr

+0

+1 Очень приятно. Это намного проще, чем мое решение. Хороший! Я начал спускаться по этому пути, но не мог понять, как отделить узлы первого уровня от узлов второго уровня и получить оба возврата в одном сканировании. – spencer7593

0

, если я понял, вы правильно (о переходе всего 2 лева ELS глубоко), вы можете сделать что-то like this:

SELECT level,fromnode , tonode 
FROM conn1 
WHERE level < 3 
CONNECT BY PRIOR tonode = fromnode 
START WITH fromnode like '%'; 
+0

будет работать в mysql? –

+0

Вы правы, в MySql нет эквивалента, но вы можете его реализовать: http://explainextended.com/2009/03/17/hierarchical-queries-in-mysql/ – alfasin

+0

Здесь есть еще одна реализация: http: //dba.stackexchange.com/questions/7147/find-highest-level-of-a-hierarchical-field-with-vs-without-ctes/7161#7161 – alfasin

0

С этими «от» и «до» отношения быть двунаправленным (вы нуждаясь пройти в обоих направлениях), там просто нет просто заявление, чтобы сделать это в MySQL. Для того, чтобы получить все значения узлов в одном наборе результатов возвращенного в одном столбце, ближайший я могу прийти к избежанию операции UNION является:

SELECT CASE 
     WHEN t.i = 1 THEN t.dnode 
     WHEN t.i = 2 AND t.dnode = c.fromnode THEN c.tonode 
     WHEN t.i = 2 AND t.dnode = c.tonode THEN c.fromnode 
     ELSE NULL 
     END AS node 
    FROM (SELECT d.i 
       , m.root 
       , CASE WHEN m.root = n.fromnode THEN n.tonode ELSE n.fromnode END AS dnode 
      FROM (SELECT 'node1' AS root) m 
      CROSS 
      JOIN (SELECT 1 AS i UNION ALL SELECT 2) d 
      LEFT 
      JOIN conn n ON m.root IN (n.fromnode,n.tonode) 
     ) t 
    LEFT 
    JOIN conn c 
    ON t.i = 2 AND t.dnode IN (c.fromnode,c.tonode) 
GROUP BY node 
ORDER BY node 

Я не знаю, если я даже буду способный распаковать его, но я попробую. Чтобы избежать необходимости указывать корневой узел «node1» несколько раз, я использую подзапрос, чтобы вернуть его.

   (SELECT 'node1' AS root) m 

Потому что мы будем «два уровня глубины», мне нужно два набора узлов, поэтому я создать декартово произведение, чтобы удвоить количество строк У меня есть, и я собираюсь маркировать их 1 для первого уровня и 2 для второго уровня.

  CROSS 
      JOIN (SELECT 1 AS i UNION ALL SELECT 2) d 

С этим, я готов присоединиться к conn столу, и я хочу все строки, которые имеют либо Fromnode или значение tonode, соответствующий корневой узел.

  LEFT 
      JOIN conn n ON m.root IN (n.fromnode,n.tonode) 

С этой результирующем я хочу «флип» на Fromnode и tonode на некоторых из этих строк так, что мы в основном всегда есть «корень» узел на одной стороне. Я делаю это с помощью выражения CASE, что тесты, которые сторона соответствует корню:

CASE WHEN m.root = n.fromnode THEN n.tonode ELSE n.fromnode END AS dnode 

Так что теперь я обернуть эту ResultSet как вид рядные псевдонимами t. Этот подзапрос может работать отдельно, чтобы увидеть, что мы возвращаемся, что мы ожидаем:

SELECT d.i 
    , m.root 
    , CASE WHEN m.root = n.fromnode THEN n.tonode ELSE n.fromnode END AS dnode 
    FROM (SELECT 'node1' AS root) m 
CROSS 
    JOIN (SELECT 1 AS i UNION ALL SELECT 2) d 
    LEFT 
    JOIN conn n ON m.root IN (n.fromnode,n.tonode) 

Нам нужно вернуть, что значение «уровень» (d.i мы получили ранее, нам нужно это на следующем этапе, когда мы снова присоединиться к столу Конн, чтобы пройти на следующий уровень, и мне нужно только присоединиться те строки, где мы будем смотреть на втором уровне.

LEFT 
    JOIN conn c 
    ON t.i = 2 AND t.dnode IN (c.fromnode,c.tonode) 

и опять же, я не забочусь, какая сторона узел включен, на данный момент мне просто нужно выполнить матч.

На этом этапе вы можете запустить весь запрос и вытащить t.*, c.*, чтобы увидеть, что у нас есть, но я собираюсь пропустить эту часть и перейти прямо к «магии».

На этом этапе мы можем использовать выражение CASE для «выделения» нужного значения узла из этого беспорядка.

Если значение уровня (к сожалению, обозначено i), равно 1, то мы смотрим на первый уровень, поэтому нам просто нужно получить значение «nodeX», которое было на «другой» стороне «root» ». Это доступно из источника t как выражение с псевдонимом как dnode.

В противном случае мы рассмотрим строки для «второго» уровня, i = 2. (В данном конкретном случае тест на i = 2 можно опустить, но он включен в систему для полноты, и на всякий случай мы собираемся расширить этот подход, чтобы получить три уровня (вздох) или больше (о мой!).

Здесь мы должны знать, какую сторону (от или до) соответствует первому уровню, и мы просто тянуть другую сторону. Если t.dnode матчи на стороне, мы тянем другую сторону.

Наконец, мы используем GROUP BY, чтобы свернуть дубликаты.

Поскольку нам все равно, на каком уровне они были, опускаем возвращаемый t.i, что даст нам уровень.


РЕЗЮМЕ

Я не думаю, что это больше, чем просто ваш запрос. И я понятия не имею, как производительность будет сравнивать. Но приятно иметь другие утверждения для сравнения производительности.

Смежные вопросы