Multiple tags search query

2020-02-28 07:04发布

I working on a tag based search. I have three tables tag(id,name), tagXmedia(id,tag_id,media_id), and media(id,...). tagXmedia is the mapping table between the tag and media tables. This is a one to many relationship.

I could really use a little direction on how to create an "AND" type of search. For instance I need to be able to search for an entry in the media table that is associated with both the "home" and "hawaii" tags.

I have experimented with MySQL exists such as

SELECT
    tam.media_id
FROM
    tagXmedia tam
    LEFT JOIN tag ON tag.id = tam.tag_id
WHERE
    EXISTS (SELECT * FROM tag  WHERE tag.name = "home")
AND EXISTS (SELECT * FROM tag WHERE tag.name = "hawaii")

Any help on this would really be appreciated.

标签: mysql
4条回答
啃猪蹄的小仙女
2楼-- · 2020-02-28 07:25

Try this query:

SELECT
    T1.media_id
FROM
    tagXmedia as T1
INNER JOIN media as T2 
ON T1.media_id =T2.id
INNER JOIN tag as T3 
ON T1.id = T3.tag_id AND T3.name IN ('home','hawaii')
GROUP BY T1.media_id
查看更多
家丑人穷心不美
3楼-- · 2020-02-28 07:34

@kba's answer is correct but you can also do this with a JOIN which is probably more efficient.

SELECT media_id
  FROM tagXmedia
  LEFT JOIN tag ON tag_id = tag.id
  WHERE tag.name IN ('home', 'hawaii')
  GROUP BY media_id
  HAVING COUNT(tag_id) = 2;

I had a similar problem where I wanted to get not just the media_id but the actual object and wanted to pass in arbitrary comma separated lists of tags. Here's my complete solution using a stored procedure:

CREATE PROCEDURE FindByTag(IN _tags VARCHAR(256))
BEGIN
  DECLARE _length INT;

  -- Get the length of the list
  SET _tags =  TRIM(BOTH ',' FROM _tags);
  SET _length = LENGTH(_tags) - LENGTH(REPLACE(_tags, ',', '')) + 1;

  -- Find media
  SELECT * FROM media
    WHERE id IN (
      SELECT media_id FROM tagXmedia
        LEFT JOIN tag ON tag_id = tag.id
        WHERE FIND_IN_SET(tag.name, _tags)
        GROUP BY media_id
        HAVING COUNT(tag_id) = _length
    )
END
查看更多
Evening l夕情丶
4楼-- · 2020-02-28 07:41
SELECT
    media1.media_id
FROM
    (
        SELECT media_id FROM tagXmedia A INNER JOIN
        (SELECT id tag_id FROM tag WHERE name='home') B
        USING (tag_id)
    ) media1
    INNER JOIN
    (
        SELECT media_id FROM tagXmedia C INNER JOIN
        (SELECT id tag_id FROM tag WHERE name='hawaii') D
        USING (tag_id)
    ) media2
    USING (media_id)
;

Make sure you have this index in tagXmedia:

ALTER TABLE tagXmedia ADD UNIQUE INDEX (tag_id,media_id);

Here is a test case:

drop database if exists tagmediatest;
create database tagmediatest;
use tagmediatest
CREATE TABLE media
(
   id int not null auto_increment,
   stuff varchar(20),
   PRIMARY KEY (id)
);
INSERT INTO media (stuff) VALUES
('magazine'),('television'),('iphone'),
('ipad'),('IE9 Browser'),('radio');
CREATE TABLE tag
(
   id int not null auto_increment,
   name varchar(20),
   PRIMARY KEY (id),
   UNIQUE KEY (name)
);
INSERT INTO tag (name) VALUES
('away'),('home'),('jersery city'),('hawaii'),('nyc');
CREATE TABLE tagXmedia
(
   id int not null auto_increment,
   tag_id INT NOT NULL,
   media_id INT NOT NULL,
   PRIMARY KEY (id),
   UNIQUE KEY (tag_id,media_id)
);
INSERT INTO tagXmedia (tag_id,media_id) VALUES
(1,1),(1,2),(1,3),(1,6),
(2,1),(2,2),(2,4),(2,5),
(3,5),(3,4),(3,3),(3,1),
(4,2),(4,3),(4,5),(4,6),
(5,2),(5,3),(5,5),(5,4);
SELECT 
    media1.media_id 
FROM 
    ( 
        SELECT media_id FROM tagXmedia A INNER JOIN 
        (SELECT id tag_id FROM tag WHERE name='home') B 
        USING (tag_id) 
    ) media1 
    INNER JOIN 
    ( 
        SELECT media_id FROM tagXmedia C INNER JOIN 
        (SELECT id tag_id FROM tag WHERE name='hawaii') D 
        USING (tag_id) 
    ) media2
USING (media_id)
; 

Here is the result:

mysql> drop database if exists tagmediatest;
Query OK, 3 rows affected (0.09 sec)

mysql> create database tagmediatest;
Query OK, 1 row affected (0.00 sec)

mysql> use tagmediatest
Database changed
mysql> CREATE TABLE media
    -> (
    ->    id int not null auto_increment,
    ->    stuff varchar(20),
    ->    PRIMARY KEY (id)
    -> );
Query OK, 0 rows affected (0.05 sec)

mysql> INSERT INTO media (stuff) VALUES
    -> ('magazine'),('television'),('iphone'),
    -> ('ipad'),('IE9 Browser'),('radio');
Query OK, 6 rows affected (0.05 sec)
Records: 6  Duplicates: 0  Warnings: 0

mysql> CREATE TABLE tag
    -> (
    ->    id int not null auto_increment,
    ->    name varchar(20),
    ->    PRIMARY KEY (id),
    ->    UNIQUE KEY (name)
    -> );
Query OK, 0 rows affected (0.08 sec)

mysql> INSERT INTO tag (name) VALUES
    -> ('away'),('home'),('jersery city'),('hawaii'),('nyc');
Query OK, 5 rows affected (0.06 sec)
Records: 5  Duplicates: 0  Warnings: 0

mysql> CREATE TABLE tagXmedia
    -> (
    ->    id int not null auto_increment,
    ->    tag_id INT NOT NULL,
    ->    media_id INT NOT NULL,
    ->    PRIMARY KEY (id),
    ->    UNIQUE KEY (tag_id,media_id)
    -> );
Query OK, 0 rows affected (0.06 sec)

mysql> INSERT INTO tagXmedia (tag_id,media_id) VALUES
    -> (1,1),(1,2),(1,3),(1,6),
    -> (2,1),(2,2),(2,4),(2,5),
    -> (3,5),(3,4),(3,3),(3,1),
    -> (4,2),(4,3),(4,5),(4,6),
    -> (5,2),(5,3),(5,5),(5,4);
Query OK, 20 rows affected (0.05 sec)
Records: 20  Duplicates: 0  Warnings: 0

mysql> SELECT
    ->     media1.media_id
    -> FROM
    ->     (
    ->         SELECT media_id FROM tagXmedia A INNER JOIN
    ->         (SELECT id tag_id FROM tag WHERE name='home') B
    ->         USING (tag_id)
    ->     ) media1
    ->     INNER JOIN
    ->     (
    ->         SELECT media_id FROM tagXmedia C INNER JOIN
    ->         (SELECT id tag_id FROM tag WHERE name='hawaii') D
    ->         USING (tag_id)
    ->     ) media2
    -> USING (media_id)
    -> ;
+----------+
| media_id |
+----------+
|        2 |
|        5 |
+----------+
2 rows in set (0.00 sec)

mysql>

Note that tag_id 2 and 4 reside in media_id 2 and 5. This is why the query works.

查看更多
The star\"
5楼-- · 2020-02-28 07:43

The following should work.

SELECT media_id
FROM tagXmedia
WHERE tag_id IN (SELECT id FROM tag WHERE name IN ('home','hawaii'))
GROUP BY media_id
HAVING COUNT(tag_id) = 2;

If you wish to have it match more than just two tags, you can easily add them. Just remember to change the 2 in the HAVING clause.

I assumed all the rows in tagXmedia are unique. In case they aren't, you will have to add DISTINCT to the COUNT part.

查看更多
登录 后发表回答