问题
Sample database design
USERS WORKDAYS DAYS
[id] [name] [user_id] [day_id] [id] [name]
1 john 1 2 1 sunday
2 fred 1 3 2 monday
3 bert 1 4 3 tuesday
4 harry 1 5 4 wednesday
1 6 5 thursday
2 2 6 friday
4 1 7 saturday
4 2
4 3
4 4
4 5
4 6
4 7
How would I query
- Someone like John, who works exactly all workdays of the week: monday, tuesday, wednesday, thursday, friday
- Someone like Harry and John who works at least all those days?
I work with mysql some time but I cannot find a solution at the moment. Normally I would use a bitflag for this but I try to grasp a more normalized solution in SQL.
CREATE TABLE `users` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(100) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8;
CREATE TABLE `days` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(100) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=8 DEFAULT CHARSET=utf8;
CREATE TABLE `workdays` (
`user_id` int(11) NOT NULL,
`day_id` int(11) NOT NULL,
KEY `workdays_users_FK` (`user_id`),
KEY `workdays_days_FK` (`day_id`),
CONSTRAINT `workdays_days_FK` FOREIGN KEY (`day_id`) REFERENCES `days` (`id`),
CONSTRAINT `workdays_users_FK` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
INSERT INTO play.users
(id, name)
VALUES(1, 'john');
INSERT INTO play.users
(id, name)
VALUES(2, 'fred');
INSERT INTO play.users
(id, name)
VALUES(3, 'bert');
INSERT INTO play.users
(id, name)
VALUES(4, 'harry');
INSERT INTO play.days
(id, name)
VALUES(1, 'sunday');
INSERT INTO play.days
(id, name)
VALUES(2, 'monday');
INSERT INTO play.days
(id, name)
VALUES(3, 'tuesday');
INSERT INTO play.days
(id, name)
VALUES(4, 'wednesday');
INSERT INTO play.days
(id, name)
VALUES(5, 'thursday');
INSERT INTO play.days
(id, name)
VALUES(6, 'friday');
INSERT INTO play.days
(id, name)
VALUES(7, 'saturday');
INSERT INTO play.workdays
(user_id, day_id)
VALUES(1, 2);
INSERT INTO play.workdays
(user_id, day_id)
VALUES(1, 3);
INSERT INTO play.workdays
(user_id, day_id)
VALUES(1, 4);
INSERT INTO play.workdays
(user_id, day_id)
VALUES(1, 5);
INSERT INTO play.workdays
(user_id, day_id)
VALUES(1, 6);
INSERT INTO play.workdays
(user_id, day_id)
VALUES(2, 2);
INSERT INTO play.workdays
(user_id, day_id)
VALUES(4, 1);
INSERT INTO play.workdays
(user_id, day_id)
VALUES(4, 2);
INSERT INTO play.workdays
(user_id, day_id)
VALUES(4, 3);
INSERT INTO play.workdays
(user_id, day_id)
VALUES(4, 4);
INSERT INTO play.workdays
(user_id, day_id)
VALUES(4, 5);
INSERT INTO play.workdays
(user_id, day_id)
VALUES(4, 6);
INSERT INTO play.workdays
(user_id, day_id)
VALUES(4, 7);
回答1:
- You can
GROUP BYon user id and name. - Use
HAVINGclause withSUM()aggregation to filter out cases. - If a user does not work on a particular day,
SUM()for that day will be zero, post joins.
For first case (works exactly all workdays of the week), Try:
SELECT u.id, u.name
FROM USERS AS u
JOIN WORKDAYS AS wd ON wd.user_id = u.id
JOIN DAYS AS d ON d.id = wd.day_id
GROUP BY u.id, u.name
HAVING SUM(d.name = 'monday')
AND SUM(d.name = 'tuesday')
AND SUM(d.name = 'wednesday')
AND SUM(d.name = 'thursday')
AND SUM(d.name = 'friday')
AND SUM(d.name = 'sunday') = 0
AND SUM(d.name = 'saturday') = 0
For second case, just remove the conditions on sunday and saturday. Try:
SELECT u.id, u.name
FROM USERS AS u
JOIN WORKDAYS AS wd ON wd.user_id = u.id
JOIN DAYS AS d ON d.id = wd.day_id
GROUP BY u.id, u.name
HAVING SUM(d.name = 'monday')
AND SUM(d.name = 'tuesday')
AND SUM(d.name = 'wednesday')
AND SUM(d.name = 'thursday')
AND SUM(d.name = 'friday')
回答2:
For all the days and then some, I would do:
SELECT u.id, u.name
FROM USERS u JOIN
WORKDAYS wd
ON wd.user_id = u.id JOIN
DAYS d
ON d.id = wd.day_id
GROUP BY u.id, u.name
HAVING SUM(d.name = 'monday') > 0 AND
SUM(d.name = 'tuesday') > 0 AND
SUM(d.name = 'wednesday') > 0 AND
SUM(d.name = 'thursday') > 0 AND
SUM(d.name = 'friday') > 0;
Or:
SELECT u.id, u.name
FROM USERS u JOIN
WORKDAYS wd
ON wd.user_id = u.id JOIN
DAYS d
ON d.id = wd.day_id
WHERE d.name IN ('monday', 'tuesday', 'wednesday', 'thursday', 'friday')
GROUP BY u.id, u.name
HAVING COUNT(DISTINCT d.name) = 5
For exactly those days and no others:
SELECT u.id, u.name
FROM USERS u JOIN
WORKDAYS wd
ON wd.user_id = u.id JOIN
DAYS d
ON d.id = wd.day_id
GROUP BY u.id, u.name
HAVING SUM(d.name = 'monday') > 0 AND
SUM(d.name = 'tuesday') > 0 AND
SUM(d.name = 'wednesday') > 0 AND
SUM(d.name = 'thursday') > 0 AND
SUM(d.name = 'friday') > 0 AND
SUM(d.name NOT IN ('monday', 'tuesday', 'wednesday', 'thursday', 'friday')) = 0
回答3:
If you want it in 1 query you could
select
id,
name,
group_concat(distinct w.day_id order by w.day_id) worked
from
users u
join workdays w on
u.id = w.user_id
group by
id,
name
having
worked like '%2,3,4,5,6%' // or worked ='2,3,4,5,6'
id |name |worked |
---|------|--------------|
1 |john |2,3,4,5,6 |
4 |harry |1,2,3,4,5,6,7 |
note I used my own user table for this
来源:https://stackoverflow.com/questions/52837058/how-to-select-rows-that-have-certain-relationships-through-a-pivot-tabel