MySQL- how to get data from 3 table? - mysql

I need a query which retrieve data from 1 table based on 3 table:
Table1
UID | GID
1 | 0
2 | 1
3 | 1
4 | 2
Table2
CID | UID
1 | 2
2 | 3
3 | 4
4 | 5
Table3
LID | CID
1 | 2
2 | 2
3 | 3
4 | 1
Now I need to retrieve data from table3 where table1.GID=1 and table2.UID=table1.UID and table3.CID=table2.CID

Unless I made a typo
SELECT
t3.*
FROM
t3
LEFT JOIN
t2 ON t3.cid = t2.cid
LEFT JOIN
t1 ON t2.uid = t1.uid
WHERE
t1.gid = 1;
With tables:
CREATE TABLE IF NOT EXISTS `t1` (
`uid` int(11) NOT NULL AUTO_INCREMENT,
`gid` int(11) NOT NULL,
PRIMARY KEY (`uid`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1 AUTO_INCREMENT=5 ;
INSERT INTO `t1` (`uid`, `gid`) VALUES
(1, 0),
(2, 1),
(3, 1),
(4, 2);
CREATE TABLE IF NOT EXISTS `t2` (
`cid` int(11) NOT NULL AUTO_INCREMENT,
`uid` int(11) NOT NULL,
PRIMARY KEY (`cid`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1 AUTO_INCREMENT=5 ;
INSERT INTO `t2` (`cid`, `uid`) VALUES
(1, 2),
(2, 3),
(3, 4),
(4, 5);
CREATE TABLE IF NOT EXISTS `t3` (
`lid` int(11) NOT NULL AUTO_INCREMENT,
`cid` int(11) NOT NULL,
PRIMARY KEY (`lid`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1 AUTO_INCREMENT=5 ;
INSERT INTO `t3` (`lid`, `cid`) VALUES
(1, 2),
(2, 2),
(3, 3),
(4, 1);

You can use this:
SELECT Table3.* FROM Table1 LEFT JOIN (Table2, Table3)
ON (table1.GID=1 and table2.UID=table1.UID and table3.CID=table2.CID)

SELECT table3.*
FROM table1
INNER JOIN table2 ON (table1.UID = table2.UID)
INNER JOIN table3 ON (table3.CID = table2.CID)
WHERE table1.GID = 1

You can use this query
SELECT table3.*
FROM table1
JOIN table2
JOIN table3
WHERE table1.GID=1
and table2.UID=table1.UID
and table3.CID=table2.CID

Related

mysql - find all students of a class if at least one student attended

I have four tables to map students and classes they attend, and to keep attendance info with start and end times.
Reproducible table schemas with records:
CREATE TABLE IF NOT EXISTS `student` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(255) DEFAULT NULL,
PRIMARY KEY (`id`)
)ENGINE=InnoDB;
INSERT INTO `student` (`id`, `name`) VALUES
(1, 'student 1'),
(2, 'student 2'),
(3, 'student 3'),
(4, 'student 4'),
(5, 'student 5');
CREATE TABLE IF NOT EXISTS `class` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(255) DEFAULT NULL,
PRIMARY KEY (`id`)
)ENGINE=InnoDB;
INSERT INTO `class` (`id`, `name`) VALUES
(1, 'class 1'),
(2, 'class 2');
CREATE TABLE IF NOT EXISTS `student_class` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`student_id` int(11) DEFAULT NULL,
`class_id` int(11) DEFAULT NULL,
PRIMARY KEY (`id`),
FOREIGN KEY (`student_id`) REFERENCES `student` (`id`),
FOREIGN KEY (`class_id`) REFERENCES `class` (`id`)
)ENGINE=InnoDB;
INSERT INTO `student_class` (`id`, `student_id`, `class_id`) VALUES
(1, 1, 1),
(2, 2, 1),
(3, 3, 1),
(4, 4, 2),
(5, 5, 2);
CREATE TABLE IF NOT EXISTS `attendance` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`student_class_id` int(11) DEFAULT NULL,
`start_time` time DEFAULT NULL,
`end_time` time DEFAULT NULL,
PRIMARY KEY (`id`),
FOREIGN KEY (`student_class_id`) REFERENCES `student_class` (`id`)
)ENGINE=InnoDB;
INSERT INTO `attendance` (`id`, `student_class_id`, `start_time`, `end_time`) VALUES
(1, 1, '09:00:00', '10:00:00');
Problem:
I need to list down rows which only shows all attendance of a class to which at least one student attended,
(even if the rest of students have null for start_time, end_time).
Here is my current sql:
SELECT c.id classId, sc.id mapperId, a.start_time startTime, a.end_time endTime FROM class c
JOIN student_class sc ON sc.class_id = c.id
LEFT JOIN attendance a ON a.student_class_id = sc.id;
The result should look like this.
classId
mapperId
startTime
endTime
1
1
09:00:00
10:00:00
1
2
NULL
NULL
1
3
NULL
NULL
#user you can get the result set you are looking for my using another instance of your query modified slightly as a sub query, like this:
SELECT c.id classId,
sc.id mapperId,
a.start_time startTime,
a.end_time endTime
FROM class c
JOIN student_class sc ON sc.class_id = c.id
LEFT JOIN attendance a ON a.student_class_id = sc.id
WHERE `c`.`id` IN (
SELECT DISTINCT c.id classId
FROM class c
JOIN student_class sc ON sc.class_id = c.id
JOIN attendance a ON a.student_class_id = sc.id
)
Here is a mock up of the answer on sqlfiddle.com
Just add a condition for the class id
SELECT c.id classId, sc.id mapperId, a.start_time startTime,
a.end_time endTime FROM class c
JOIN student_class sc ON sc.class_id = c.id
LEFT JOIN attendance a ON a.student_class_id = sc.id
where c.id in (select distinct sc.class_id
from attendance a
join student_class sc
on a.student_class_id = sc.id);
Based on your question answer will be like this
SELECT cls.id, stdclass.id AS mapperId , att.start_time ,att.end_time FROM attendance att INNER JOIN student_class stdclass ON att.student_class_id = stdclass.id INNER JOIN class cls ON stdclass.class_id = cls.id INNER JOIN student std ON stdclass.student_id = std.id

MySQL return rows from last day only for a specific ID

I have 2 tables: 'clients' and 'orders', joined on the field 'client_id'.
CREATE TABLE IF NOT EXISTS `clients` (
`client_id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(120) NOT NULL,
PRIMARY KEY (`client_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 AUTO_INCREMENT=3 ;
INSERT INTO `clients` (`client_id`, `name`) VALUES
(1, 'Ted Bundy'),
(2, 'Terry Towl');
CREATE TABLE IF NOT EXISTS `orders` (
`order_id` int(11) NOT NULL AUTO_INCREMENT,
`client_id` int(11) NOT NULL,
`description` varchar(70) NOT NULL,
`order_date` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`order_id`),
KEY `client_id` (`client_id`),
KEY `created` (`order_date`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1 AUTO_INCREMENT=11 ;
INSERT INTO `orders` (`order_id`, `client_id`, `description`, `order_date`) VALUES
(1, 1, 'Shirt', '2015-12-02 01:14:01'),
(2, 2, 'Trousers', '2015-12-02 03:31:53'),
(3, 2, 'Underware', '2015-12-04 11:07:46'),
(4, 2, 'Hat', '2015-12-06 11:27:16'),
(5, 2, 'Scarf', '2015-12-07 00:14:31'),
(6, 2, 'Shirt', '2015-12-07 07:17:03'),
(7, 1, 'Shoes', '2015-12-09 16:23:20'),
(8, 1, 'Socks', '2015-12-11 11:40:16'),
(9, 1, 'Sweater', '2015-12-13 05:20:11'),
(10, 1, 'Shorts', '2015-12-13 12:41:31');
ALTER TABLE `orders`
ADD CONSTRAINT `orders_ibfk_1`
FOREIGN KEY (`client_id`)
REFERENCES `clients` (`client_id`)
ON DELETE CASCADE
ON UPDATE CASCADE;
I need to find the orders for the most recent day for a specific client_id, and only 1 client at a time
Example output for client_id 2
client_id | name | description | order_date
-------------------------------------------------------------
2 | Terry Towl | Hat | 2015-12-07
2 | Terry Towl | Scarf | 2015-12-07
The issue is that we dont know the number of orders on that day, nor the date
The only way I can think to do this is to first query the date of the last order for a client, then to run another to find all records for that client on that date, however was hoping to be able to do this in one query.
Does anyone have an idea how to achieve this in one query?
Your idea is basically correct.
select *
from clients c join
orders o
on c.client_id = o.client_id
where c.client_id = $client_id and
o.order_date = (select max(o2.order_date)
from orders o2
where o2.client_id = c.client_id
);

Avoid duplicate entries when joining multiple tables (MySQL)

(please see the database structure I'm testing with at the bottom of this post.)
I execute this query:
SELECT m.title, GROUP_CONCAT(DISTINCT(d.name) SEPARATOR ',') d FROM movies m
INNER JOIN movies_seen s
ON s.object_id = m.id
LEFT JOIN movies_directors_connections dc
ON dc.movie_id = m.id
LEFT JOIN movies_directors d
ON d.id = dc.director_id
With this result:
title | d
Pulp Fiction | Quentin Tarantino,George Butler,Robert Fiore
But I'm trying to get this:
title | d
Pulp Fiction | Quentin Tarantino
Pumping Iron | George Butler,Robert Fiore
And suggestions? :)
CREATE TABLE `movies` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`title` varchar(90) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=MyISAM AUTO_INCREMENT=3 ;
CREATE TABLE `movies_seen` (
`object_id` int(10) NOT NULL DEFAULT '0',
`date` varchar(10) NOT NULL DEFAULT '0');
CREATE TABLE `movies_directors` (
`id` mediumint(8) unsigned NOT NULL AUTO_INCREMENT,
`name` varchar(255) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=MyISAM AUTO_INCREMENT=4 ;
CREATE TABLE IF NOT EXISTS `movies_directors_connections` (
`movie_id` mediumint(8) unsigned NOT NULL DEFAULT '0',
`director_id` mediumint(8) unsigned NOT NULL DEFAULT '0'
) ENGINE=MyISAM;
And then some test data:
INSERT INTO `movies` (`id`, `title`) VALUES
(1, 'Pulp Fiction'), (2, 'Pumping Iron');
INSERT INTO `movies_seen` (`object_id`, `date`) VALUES
(1, 1359511222), (2, 1359511223);
INSERT INTO `movies_directors` (`id`, `name`) VALUES
(1, 'Quentin Tarantino'),
(2, 'George Butler'),
(3, 'Robert Fiore');
INSERT INTO `movies_directors_connections` (`movie_id`, `director_id`) VALUES
(1, 1), (2, 2), (2, 3);
you just need to add GROUP BY clause
SELECT m.title,
GROUP_CONCAT(DISTINCT(d.name) SEPARATOR ',') d
FROM movies m
INNER JOIN movies_seen s
ON s.object_id = m.id
LEFT JOIN movies_directors_connections dc
ON dc.movie_id = m.id
LEFT JOIN movies_directors d
ON d.id = dc.director_id
GROUP BY m.title
SQLFiddle Demo
OTHER LINK
MySQL GROUP BY clause

MySQL count N consecutive days in normal form tables

I want to count N consecutive days that a specific user has meetings, on a given date and before it.
For example: count the consecutive meeting days that a user with id 1 has at 16 January 2013.
I found some good answers here and here but the tables are not in normal form like my sample above and i cannot figure out how to implement it for my occasion.
A sample table structure as follows:
CREATE TABLE IF NOT EXISTS `meetings` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`time` datetime NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
CREATE TABLE IF NOT EXISTS `meetings_users` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`user_id` int(10) unsigned NOT NULL,
`meeting_id` int(10) unsigned NOT NULL,
PRIMARY KEY (`id`),
KEY `user_id` (`user_id`),
KEY `meeting_id` (`meeting_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
CREATE TABLE IF NOT EXISTS `users` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
--
-- Constraints for table `meetings_users`
--
ALTER TABLE `meetings_users`
ADD CONSTRAINT `meetings_users_ibfk_2` FOREIGN KEY (`meeting_id`) REFERENCES `meetings` (`id`) ON DELETE CASCADE ON UPDATE CASCADE,
ADD CONSTRAINT `meetings_users_ibfk_1` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON DELETE CASCADE ON UPDATE CASCADE;
Sample inserts
INSERT INTO `users` ( `id` ) VALUES (1)
INSERT INTO `meetings` ( `id`, `time` ) VALUES
(1, '2013-01-14 10:00:00'),
(2, '2013-01-15 10:00:00'),
(3, '2013-01-16 10:00:00')
INSERT INTO `meetings_users` ( `id`, `meeting_id`, `user_id` ) VALUES
(1, 1, 1),
(2, 2, 1),
(3, 3, 1)
Desired output:
*+---------+-----------------+
| user_id | consecutive_days |
+---------+------------------+
| 1 | 3 |
+---------+------------------+
How about something like this. I expect it can be re-written without the subqueries but I must be having a bit of a brain freeze... (data set and query amended to suit shifting requirements)
DROP TABLE IF EXISTS meetings;
CREATE TABLE IF NOT EXISTS meetings
( meeting_id int(10) unsigned NOT NULL AUTO_INCREMENT
, meeting_time datetime NOT NULL
, PRIMARY KEY (meeting_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
DROP TABLE IF EXISTS meetings_users;
CREATE TABLE IF NOT EXISTS meetings_users
( user_id int(10) unsigned NOT NULL
, meeting_id int(10) unsigned NOT NULL
, PRIMARY KEY (meeting_id,user_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
DROP TABLE IF EXISTS users;
CREATE TABLE IF NOT EXISTS users
( user_id int(10) unsigned NOT NULL AUTO_INCREMENT
, PRIMARY KEY (user_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
INSERT INTO users ( user_id ) VALUES (1),(2),(3),(4);
INSERT INTO meetings ( meeting_id, meeting_time ) VALUES
(1, '2013-01-14 10:00:00'),
(2, '2013-01-15 10:00:00'),
(3, '2013-01-16 10:00:00'),
(4, '2013-01-17 10:00:00'),
(5, '2013-01-18 10:00:00'),
(6, '2013-01-19 10:00:00'),
(7, '2013-01-20 10:00:00'),
(8, '2013-01-14 12:00:00');
INSERT INTO meetings_users (meeting_id, user_id ) VALUES
(1, 1),
(2, 1),
(2, 3),
(3, 1),
(3, 3),
(4, 2),
(4, 3),
(5, 2),
(6, 1),
(1, 8);
SET #dt = '2013-01-15';
SELECT user_id
, start
, DATEDIFF(#dt,start)+1 cons
FROM
(
SELECT a.user_id
, a.meeting_date Start
, MIN(c.meeting_date) End
, DATEDIFF(MIN(c.meeting_date),a.meeting_date) + 1 diff
FROM (SELECT DISTINCT mu.user_id,DATE(m.meeting_time) meeting_date FROM meetings_users mu JOIN meetings m ON m.meeting_id = mu.meeting_id) a
LEFT
JOIN (SELECT DISTINCT mu.user_id,DATE(m.meeting_time) meeting_date FROM meetings_users mu JOIN meetings m ON m.meeting_id = mu.meeting_id) b
ON b.user_id = a.user_id
AND a.meeting_date = b.meeting_date + INTERVAL 1 DAY
LEFT
JOIN (SELECT DISTINCT mu.user_id,DATE(m.meeting_time) meeting_date FROM meetings_users mu JOIN meetings m ON m.meeting_id = mu.meeting_id) c
ON c.user_id = a.user_id
AND a.meeting_date <= c.meeting_date
LEFT
JOIN (SELECT DISTINCT mu.user_id,DATE(m.meeting_time) meeting_date FROM meetings_users mu JOIN meetings m ON m.meeting_id = mu.meeting_id) d
ON d.user_id = a.user_id
AND c.meeting_date = d.meeting_date - INTERVAL 1 DAY
WHERE b.meeting_date IS NULL
AND c.meeting_date IS NOT NULL
AND d.meeting_date IS NULL
GROUP
BY a.user_id
, a.meeting_date
) x
WHERE #dt BETWEEN start AND end;
+---------+------------+------+
| user_id | start | cons |
+---------+------------+------+
| 1 | 2013-01-14 | 2 |
| 3 | 2013-01-15 | 1 |
+---------+------------+------+

Optimizing MySQL query to avoid scanning a lot of rows

I am running an application that is using tables similar to the below tables. There are one tables for articles and there is another table for tags. I want to get the latest 30 articles for a specific tag order by article id. for example "acer", the below query will do the job but it is not indexed correctly because it will scan a lot of rows if there are a lot of articles related to a specific tag. How to run a query to get the same result without scanning a large number of rows?
EXPLAIN SELECT title
FROM tag, article
WHERE tag = 'acer'
AND tag.article_id = article.id
ORDER BY tag.article_id DESC
LIMIT 0 , 30
Output
id select_type table type possible_keys key key_len ref rows Extra
1 SIMPLE tag ref tag tag 92 const 220439 Using where; Using index
1 SIMPLE article eq_ref PRIMARY PRIMARY 4 testdb.tag.article_id 1
The flollowing is the tables and sample data:
CREATE TABLE `article` (
`id` int(11) NOT NULL auto_increment,
`title` varchar(60) NOT NULL,
`time_stamp` int(11) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8 AUTO_INCREMENT=1000001 ;
--
-- Dumping data for table `article`
--
INSERT INTO `article` VALUES (1, 'Saudi Apple type D', 1313390211);
INSERT INTO `article` VALUES (2, 'Japan Apple type A', 1313420771);
INSERT INTO `article` VALUES (3, 'UAE Samsung type B', 1313423082);
INSERT INTO `article` VALUES (4, 'UAE Apple type H', 1313417337);
INSERT INTO `article` VALUES (5, 'Japan Samsung type D', 1313398875);
INSERT INTO `article` VALUES (6, 'UK Acer type B', 1313387888);
INSERT INTO `article` VALUES (7, 'Saudi Sony type D', 1313429416);
INSERT INTO `article` VALUES (8, 'UK Apple type B', 1313394549);
INSERT INTO `article` VALUES (9, 'Japan HP type A', 1313427730);
INSERT INTO `article` VALUES (10, 'Japan Acer type C', 1313400046);
CREATE TABLE `tag` (
`tag` varchar(30) NOT NULL,
`article_id` int(11) NOT NULL,
UNIQUE KEY `tag` (`tag`,`article_id`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8;
--
-- Dumping data for table `tag`
--
INSERT INTO `tag` VALUES ('Samsung', 1);
INSERT INTO `tag` VALUES ('Acer', 2);
INSERT INTO `tag` VALUES ('Sony', 3);
INSERT INTO `tag` VALUES ('Apple', 4);
INSERT INTO `tag` VALUES ('Acer', 5);
INSERT INTO `tag` VALUES ('HP', 6);
INSERT INTO `tag` VALUES ('Acer', 7);
INSERT INTO `tag` VALUES ('Sony', 7);
INSERT INTO `tag` VALUES ('Acer', 7);
INSERT INTO `tag` VALUES ('Samsung', 9);
What makes you think the query will examine a large number of rows?
The query will scan exactly 30 records using the UNIQUE index on tag (tag, article_id), join the article to each record on PRIMARY KEY and stop.
This is exactly what your plan says.
I just made this test script:
CREATE TABLE `article` (
`id` int(11) NOT NULL auto_increment,
`title` varchar(60) NOT NULL,
`time_stamp` int(11) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8 AUTO_INCREMENT=1000001 ;
CREATE TABLE `tag` (
`tag` varchar(30) NOT NULL,
`article_id` int(11) NOT NULL,
UNIQUE KEY `tag` (`tag`,`article_id`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8;
INSERT
INTO article
SELECT id, CONCAT('Article ', id), UNIX_TIMESTAMP('2011-08-17' - INTERVAL id SECOND)
FROM t_source;
INSERT
INTO tag
SELECT CASE fld WHEN 1 THEN CONCAT('tag', (id - 1) div 10 + 1) ELSE tag END AS tag, id
FROM (
SELECT tag,
id,
FIELD(tag, 'Other', 'Acer', 'Sony', 'HP', 'Dell') AS fld,
RAND(20110817) AS rnd
FROM (
SELECT 'Other' AS tag
UNION ALL
SELECT 'Acer' AS tag
UNION ALL
SELECT 'Sony' AS tag
UNION ALL
SELECT 'HP' AS tag
UNION ALL
SELECT 'Dell' AS tag
) t
JOIN t_source
) q
WHERE POWER(3, -fld) > rnd;
, where t_source is a table with 1M records in it, and run your query:
SELECT *
FROM tag t
JOIN article a
ON a.id = t.article_id
WHERE t.tag = 'acer'
ORDER BY
t.article_id DESC
LIMIT 30;
It was instant.
try ANSI join syntax:
SELECT title
FROM tag t
INNER JOIN article a
ON t.article_id = a.id
WHERE
t.tag = 'acer'
ORDER BY
tag.article_id DESC
LIMIT 0 , 30
then put an index on tag.tag. Assuming you have enough selectivity on that table, and article.id is a primary key, that should be pretty zippy.
I would suggest modifying the storage engine and schema to utilize foreign keys.
CREATE TABLE `article` (
`id` int(11) NOT NULL auto_increment,
`title` varchar(60) NOT NULL,
`time_stamp` int(11) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 AUTO_INCREMENT=1000001 ;
CREATE TABLE `tag` (
`id` int(11) NOT NULL auto_increment,
`tag` varchar(30) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
CREATE TABLE `article_tag` (
`id` int(11) NOT NULL auto_increment,
`article_id` int(11) NOT NULL,
`tag_id` int(11) NOT NULL,
PRIMARY KEY (`id`),
FOREIGN KEY (`article_id`) REFERENCES article(id),
FOREIGN KEY (`tag_id`) REFERENCES tag(id)
) ENGINE=Innodb;
Which results in a query like so:
EXPLAIN
SELECT * FROM article
JOIN article_tag ON article.id = article_tag.id
JOIN tag ON article_tag.tag_id = tag.id
WHERE tag.tag="Acer";
+----+-------------+-------------+--------+----------------+---------+---------+-------------------------+------+-------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-------------+--------+----------------+---------+---------+-------------------------+------+-------------+
| 1 | SIMPLE | article_tag | ALL | PRIMARY,tag_id | NULL | NULL | NULL | 1 | |
| 1 | SIMPLE | tag | eq_ref | PRIMARY | PRIMARY | 4 | temp.article_tag.tag_id | 1 | Using where |
| 1 | SIMPLE | article | eq_ref | PRIMARY | PRIMARY | 4 | temp.article_tag.id | 1 | |
+----+-------------+-------------+--------+----------------+---------+---------+-------------------------+------+-------------+
3 rows in set (0.00 sec)
Edit: Add this index
UNIQUE KEY tag (article_id,tag)