How to select rows that have certain relationships through a pivot tabel?

放肆的年华 提交于 2021-02-10 14:50:39

问题


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

  1. Someone like John, who works exactly all workdays of the week: monday, tuesday, wednesday, thursday, friday
  2. 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 BY on user id and name.
  • Use HAVING clause with SUM() 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

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!