Reputation: 13
CREATE TABLE `files`
(
`file_id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
`filename` text COLLATE utf8mb4_unicode_ci NOT NULL,
PRIMARY KEY (`file_id`)
) ENGINE = InnoDB
DEFAULT CHARSET = utf8mb4
COLLATE = utf8mb4_unicode_ci;
INSERT INTO `files` (`file_id`, `filename`)
VALUES (100, 'file_100.ext'),
(101, 'file_101.ext');
CREATE TABLE `files_tags`
(
`file_id` bigint(20) unsigned NOT NULL,
`tag_id` int(10) unsigned NOT NULL,
PRIMARY KEY (`file_id`, `tag_id`),
CONSTRAINT `FK__files` FOREIGN KEY (`file_id`) REFERENCES `files` (`file_id`) ON DELETE CASCADE ON UPDATE CASCADE
) ENGINE = InnoDB
DEFAULT CHARSET = utf8mb4
COLLATE = utf8mb4_unicode_ci;
INSERT INTO `files_tags` (`file_id`, `tag_id`)
VALUES (100, 1),
(100, 2),
(100, 3),
(101, 1),
(101, 2);
SQL fiddle: http://www.sqlfiddle.com/#!9/b776ab/3
I have a join table (files_tags) which lists tags for another table (files). I have the following query, which works great to select all files tagged with tags 1 and 2:
SELECT f.filename
FROM files_tags ft0
INNER JOIN files f ON ft0.file_id = f.file_id
INNER JOIN files_tags ft1 ON f.file_id = ft1.file_id AND ft1.tag_id = 2
WHERE ft0.tag_id = 1;
I'm looking for a query that selects files tagged 1 AND 2 but NOT 3. I tried the above query with this additional join:
INNER JOIN files_tags ft2 ON f.file_id = ft2.file_id AND ft1.tag_id = 3
but that just ends up giving me 5 rows back (identical to the files_tags table) which is not what I want. My expected output for this is a single row of file_101.ext
. What would be the proper query to get files that are tagged 1 AND 2 but NOT 3?
Upvotes: 1
Views: 61
Reputation: 36
Not sure if this is elegant enough, but should be quit performant.
SELECT f.filename
FROM files_tags ft0
INNER JOIN files f ON ft0.file_id = f.file_id
INNER JOIN files_tags ft1 ON f.file_id = ft1.file_id AND ft1.tag_id = 2
LEFT JOIN files_tags ft2 on f.file_id = ft2.file_id and ft2.tag_id = 3
WHERE ft0.tag_id = 1
and ft2.file_id is null;
Upvotes: 2
Reputation: 64476
You can use exists/ not exists
to check the existence and absence of related records as
select f.filename
from files f
where exists (
select 1
from files_tags
where tag_id = 1
and file_id = f.file_id
) and exists (
select 1
from files_tags
where tag_id = 2
and file_id = f.file_id
) and not exists (
select 1
from files_tags
where tag_id = 3
and file_id = f.file_id
)
Upvotes: 1