Выберите все проекты, которые имеют соответствующие теги - PullRequest
5 голосов
/ 27 августа 2010

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

Как выбрать все проекты, теги которых похожи на нужный проект?

Возьмите эту таблицу, например:
(sql код для воссоздания таблиц ниже)

project 1 -> tagA | tagB | tagC
project 2 -> tagA | tagB
project 3 -> tagA
project 4 -> tagC

При выборе проекта 1 должны быть возвращены все проекты.
Выбор проекта 4 должен возвращать только проект проекта 1

Мой запрос до сих пор довольно сильно зависит от левых соединений, и наверняка есть лучший способ сделать это:

SELECT all_tags.project_id, all_tags.tag_id, final.title, tag.tag
FROM projects AS p
LEFT JOIN projects_to_tags AS pt ON p.num = pt.project_id
LEFT JOIN projects_to_tags AS all_tags ON pt.tag_id = all_tags.tag_id
LEFT JOIN projects AS final ON all_tags.project_id = final.num
LEFT JOIN tags AS tag ON all_tags.tag_id = tag.tag_id
WHERE p.num = 4
GROUP BY final.num

Спасибо всем за вклад. Хотя я хотел бы поделиться с вами, ребята, усредненными результатами всех запросов к базе данных по 100 тыс. Проектов, базе данных по 100 тыс. Тегов с отношением 100 тыс. Проектов. Все запросы были изменены для запроса project_1.

Сладкое и короткое:

0.0160 sec - OMG Ponies - Using JOINS  
0.0208 sec - jdelard  
0.2581 sec - OMG Ponies - Using EXISTS  
0.2777 sec - OMG Ponies - Using IN  
0.5295 sec - Emtucifor - updated query  
0.5088 sec - Emtucifor - first query  

Спасибо всем большое за это. Собираюсь обновить ВСЕ мои запросы соответственно.

Здесь идут все запросы и соответствующие ОБЪЯСНЕНИЯ MySQL вместе с временем

===============================================================================================================================================
Emtucifor - updated query
===============================================================================================================================================
Showing rows 0 - 1 (2 total, Query took 0.5295 sec)
SELECT * 
FROM projects AS L
WHERE L.num !=1-- instead of <> PT2.project_id inside

AND EXISTS (

SELECT 1 
FROM projects_to_tags PT
INNER JOIN projects_to_tags PT2 ON PT.tag_id = PT2.tag_id
WHERE L.num = PT.project_id
AND PT2.project_id =1
)
LIMIT 0 , 30

id  select_type table   type    possible_keys   key key_len ref rows    Extra
1   PRIMARY L   ALL PRIMARY NULL    NULL    NULL    100000  Using where
2   DEPENDENT SUBQUERY  PT2 ref project_id  project_id  4   const   1   Using index
2   DEPENDENT SUBQUERY  PT  ref project_id  project_id  8   test.L.num,test.PT2.tag_id  12000   Using index




===============================================================================================================================================
Emtucifor - first query
===============================================================================================================================================
Showing rows 0 - 1 (2 total, Query took 0.5088 sec)
SELECT * 
FROM projects AS L
WHERE 
EXISTS (

SELECT 1 
FROM projects_to_tags PT
INNER JOIN projects_to_tags PT2 ON PT.tag_id = PT2.tag_id
WHERE L.num = PT.project_id
AND PT2.project_id =1
AND PT2.project_id <> L.num
)
LIMIT 0 , 30

id  select_type table   type    possible_keys   key key_len ref rows    Extra
1   PRIMARY L   ALL NULL    NULL    NULL    NULL    100000  Using where
2   DEPENDENT SUBQUERY  PT2 ref project_id  project_id  4   const   1   Using index
2   DEPENDENT SUBQUERY  PT  ref project_id  project_id  8   test.L.num,test.PT2.tag_id  12000   Using where; Using index




===============================================================================================================================================
jdelard
===============================================================================================================================================
Showing rows 0 - 1 (2 total, Query took 0.0208 sec)
SELECT p.num, p.title
FROM projects_to_tags pt1, projects_to_tags pt2, projects p
WHERE pt1.project_id =1
AND pt2.project_id !=1
AND pt1.tag_id = pt2.tag_id
AND p.num = pt2.project_id
GROUP BY pt2.project_id
LIMIT 0 , 30

id  select_type table   type    possible_keys   key key_len ref rows    Extra
1   SIMPLE  pt1 ref project_id  project_id  4   const   1   Using index; Using temporary; Using filesort
1   SIMPLE  pt2 index   project_id  project_id  8   NULL    75001   Using where; Using index
1   SIMPLE  p   eq_ref  PRIMARY PRIMARY 4   test.pt2.project_id 1    




===============================================================================================================================================
OMG Ponies - Using IN
===============================================================================================================================================
Showing rows 0 - 2 (3 total, Query took 0.2777 sec)
SELECT p . * 
FROM projects p
JOIN projects_to_tags pt ON pt.project_id = p.num
WHERE pt.tag_id
IN (

SELECT x.tag_id
FROM projects_to_tags x
WHERE x.project_id =1
)
LIMIT 0 , 30

id  select_type table   type    possible_keys   key key_len ref rows    Extra
1   PRIMARY pt  index   project_id  project_id  8   NULL    100001  Using where; Using index
1   PRIMARY p   eq_ref  PRIMARY PRIMARY 4   test.pt.project_id  1    
2   DEPENDENT SUBQUERY  x   ref project_id  project_id  8   const,func  12000   Using where; Using index




===============================================================================================================================================
OMG Ponies - Using EXISTS
===============================================================================================================================================
Showing rows 0 - 2 (3 total, Query took 0.2581 sec)
SELECT p . * 
FROM projects p
JOIN projects_to_tags pt ON pt.project_id = p.num
WHERE EXISTS (

SELECT NULL 
FROM projects_to_tags x
WHERE x.project_id = 1
AND x.tag_id = pt.tag_id
)
LIMIT 0 , 30




===============================================================================================================================================
OMG Ponies - Using JOINS
===============================================================================================================================================
Showing rows 0 - 2 (3 total, Query took 0.0160 sec)
SELECT DISTINCT p . * 
FROM projects p
JOIN projects_to_tags pt ON pt.project_id = p.num
JOIN projects_to_tags x ON x.tag_id = pt.tag_id
AND x.project_id = 1
LIMIT 0 , 30

id  select_type table   type    possible_keys   key key_len ref rows    Extra
1   SIMPLE  x   ref project_id  project_id  4   const   1   Using index; Using temporary
1   SIMPLE  pt  index   project_id  project_id  8   NULL    75001   Using where; Using index
1   SIMPLE  p   eq_ref  PRIMARY PRIMARY 4   test.pt.project_id  1   

SQL-код для копирования / вставки и возни.

CREATE TABLE IF NOT EXISTS `projects` (
  `num` int(2) NOT NULL auto_increment,
  `title` varchar(30) NOT NULL,
  PRIMARY KEY  (`num`)
) ENGINE=MyISAM  DEFAULT CHARSET=latin1 AUTO_INCREMENT=5 ;


INSERT INTO `projects` (`num`, `title`) VALUES(1, 'project 1'),(2, 'project 2'),(3, 'project 3'),(4, 'project 4');


CREATE TABLE IF NOT EXISTS `projects_to_tags` (
  `project_id` int(2) NOT NULL,
  `tag_id` int(2) NOT NULL,
  KEY `project_id` (`project_id`,`tag_id`)
) ENGINE=MyISAM DEFAULT CHARSET=latin1;


INSERT INTO `projects_to_tags` (`project_id`, `tag_id`) VALUES(1, 1),(1, 2),(1, 3),(2, 1),(2, 2),(3, 1),(4, 3);


CREATE TABLE IF NOT EXISTS `tags` (
  `tag_id` int(2) NOT NULL auto_increment,
  `tag` varchar(30) NOT NULL,
  PRIMARY KEY  (`tag_id`),
  UNIQUE KEY `tag` (`tag`)
) ENGINE=MyISAM  DEFAULT CHARSET=latin1 AUTO_INCREMENT=4 ;


INSERT INTO `tags` (`tag_id`, `tag`) VALUES(1, 'tag a'),(2, 'tag b'),(3, 'tag c');

Ответы [ 3 ]

6 голосов
/ 27 августа 2010

В любом из следующих случаев, если вы не знаете PROJECT.num / PROJECT_TO_TAGS.project_id, вам придется присоединиться к таблице PROJECTS, чтобы получить значение идентификатора, чтобы узнать, какие теги связаны .

Использование IN

SELECT p.*
  FROM PROJECTS p
  JOIN PROJECTS_TO_TAGS pt ON pt.project_id = p.num
 WHERE pt.tag_id IN (SELECT x.tag_id
                       FROM PROJECTS_TO_TAGS x
                      WHERE x.project_id = 4)

Использование EXISTS

SELECT p.*
  FROM PROJECTS p
  JOIN PROJECTS_TO_TAGS pt ON pt.project_id = p.num
 WHERE EXISTS (SELECT NULL
                 FROM PROJECTS_TO_TAGS x
                WHERE x.project_id = 4
                  AND x.tag_id = pt.tag_id)

Использование JOINS (это самый эффективный!)

DISTINCT необходим, потому что JOIN'ы рискуют дублировать данные в наборе результатов ...

SELECT DISTINCT p.*
  FROM PROJECTS p
  JOIN PROJECTS_TO_TAGS pt ON pt.project_id = p.num
  JOIN PROJECTS_TO_TAGS x ON x.tag_id = pt.tag_id
                         AND x.project_id = 4
4 голосов
/ 27 августа 2010

Как насчет ... (пример для проекта 1)

SELECT p.num, p.title
FROM projects_to_tags pt1, projects_to_tags pt2, projects p
where pt1.project_id = 1 and 
      pt2.project_id != 1 and 
      pt1.tag_id = pt2.tag_id and 
      p.num = pt2.project_id 
group by pt2.project_id

И, возможно, добавить отдельный индекс для tag_id в projects_to_tags, чтобы вы могли использовать его отдельно, вместо составного.Нет больше типа ВСЕ.(Таблица сканирования) Замена обоих 1 на 4 также дает желаемые результаты.

2 голосов
/ 27 августа 2010

Как то так ...?

SELECT *
FROM projects AS L
WHERE
   EXISTS (
      SELECT 1
      FROM
         projects_to_tags PT
         INNER JOIN projects_to_tags PT2 ON PT.tag_id = PT2.tag_id
      WHERE
         L.num = PT.project_id
         AND PT2.project_id = 4
         AND PT2.project_id <> L.num
   )

Это 2 попытки и сканирование.

UPDATE

Взяв страницу из книги Джделарда, одна крошечная модификация переключает мой запрос, чтобы превзойти его (конечно, я делаю это на SQL Server, то есть я взял его GROUP BY и вставил DISTINCT, поэтому YMMV на MySQL):

SELECT *
FROM projects AS L
WHERE
   L.num != 4 -- instead of <> PT2.project_id inside
   AND EXISTS (
      SELECT 1
      FROM
         projects_to_tags PT
         INNER JOIN projects_to_tags PT2 ON PT.tag_id = PT2.tag_id
      WHERE
         L.num = PT.project_id
         AND PT2.project_id = 4
   )

Улучшение по сравнению с его запросом связано с отсутствием DISTINCT или агрегата, а также использованием полусоединения вместо полного объединения, поэтому не каждая строка должна быть объединена. В остальном семантически они во многом совпадают.

Мне нужно будет запомнить трюк Джделарда, так как это очень полезный инструмент. По какой-то причине механизм запросов не был достаточно умен, чтобы вычислить то, что задано {a = 4, a! = B}, то {b! = 4}.

...