Find rows that has ALL the linked rows - mysql

I've got two tables:
User (id, name, etc)
UserRight (user_id, right_id)
I want to find the users who have rights 1, 2 and 3, but no users who only have one or two of these. Also, the number of rights will vary, so searches for (1,2,3) and (1,2,3,4,5,6,7) should work with much the same query.
Essentially:
SELECT *
FROM User
WHERE (
SELECT right_id
FROM tblUserRight
WHERE user_id = id
ORDER BY user_id ASC
) = (1,2,3)
Is this possible in MySQL?

SELECT u.id, u.name ...
FROM User u
JOIN UserRight r on u.id = r.user_id
WHERE right_id IN (1,2,3)
GROUP BY u.id, u.name ...
HAVING COUNT DISTINCT(right_id) = 3

You can also do this using PIVOT, especially if you want a visual representation. I did this on SQL Server - you may be able to translate it.
Declare #User Table (id Int, name Varchar (10))
Declare #UserRight Table (user_id Int, right_id Int)
Insert Into #User Values (1, 'Adam')
Insert Into #User Values (2, 'Bono')
Insert Into #User Values (3, 'Cher')
Insert Into #UserRight Values (1, 1)
Insert Into #UserRight Values (1, 2)
Insert Into #UserRight Values (1, 3)
--Insert Into #UserRight Values (2, 1)
Insert Into #UserRight Values (2, 2)
Insert Into #UserRight Values (2, 3)
Insert Into #UserRight Values (3, 1)
Insert Into #UserRight Values (3, 2)
--Insert Into #UserRight Values (3, 3)
SELECT *
FROM #User U
INNER JOIN #UserRight UR
ON U.id = UR.User_Id
PIVOT
(
SUM (User_Id)
FOR Right_Id IN ([1], [2], [3])
) as xx
WHERE 1=1
SELECT *
FROM #User U
INNER JOIN #UserRight UR
ON U.id = UR.User_Id
PIVOT
(
SUM (User_Id)
FOR Right_Id IN ([1], [2], [3])
) as xx
WHERE 1=1
AND [1] IS NOT NULL
AND [2] IS NOT NULL
AND [3] IS NOT NULL

In correspondance with the errors in my answer pointed out, here a solution with count and a subquery:
SELECT *
FROM User
WHERE 3 = (
SELECT Count(user_id)
FROM tblUserRight
WHERE right_id IN (1,2,3)
AND user_id = User.id
)
An optimizer may of course change this to Martin Smith's solution (i.e. by using a group by).

Related

SQL query to return users with no active group memberships

I've got this schema, which has the ability to soft-delete/archive a group:
create table users (id int, name varchar(255));
create table groups (id int, name varchar(255), archived_at datetime);
create table memberships (group_id int, user_id int);
insert into users (id, name) values
(1, 'no groups'),
(2, 'only active groups'),
(3, 'only archived groups'),
(4, 'active and archived groups');
insert into groups (id, name, archived_at) values
(1, 'active group', null),
(2, 'archived group', '2021-04-13');
insert into memberships (user_id, group_id) values
(2, 1),
(3, 2),
(4, 1),
(4, 2);
And I want to query users that have "no groups": exactly zero Active (non-archived) groups, plus zero or more Archived groups.
-- How do I get this to return only users 1 and 3?
select *
from users u
left join memberships m on m.user_id = u.id
left join groups g on g.id = m.group_id
where
m.group_id is null
or
g.archived_at is not null
Is this possible with the current schema?
https://www.db-fiddle.com/f/h1szvQmQHp1g84Ex9WvRHy/1
This sounds like not exists:
select u.*
from users u
where not exists (select 1
from memberships m join
groups g
on m.group_id = g.id
where m.user_id = u.id and g.archived_at is null
);
Presumably, an active group is one that has not been archived.

both as a target for 'DELETE' and as a separate source for data

my SQL is:
delete from user
where cid in (select cid from user group by cid having count(cid) > 1)
and id not in (select min(id) from user group by cid having count(cid )>1)
Prompt error messageļ¼š
Table 'user' is specified twice, both as a target for 'DELETE' and as
a separate source for data
How to fix this
did you try:
CREATE TABLE user
(`cid` int, `username` varchar(50))
;
INSERT INTO user
(`cid`, `username`)
VALUES
(1, 'John'),
(1, 'John'),
(2, 'Paul'),
(2, 'Paul'),
(3, 'Ryan')
;
DELETE FROM user
WHERE cid IN
( SELECT todel.cid
FROM (SELECT * FROM user todel
WHERE todel.cid
IN (
SELECT t1.cid
FROM user t1
GROUP BY t1.cid
HAVING COUNT(t1.cid) > 1
)
AND todel.cid
NOT IN (
SELECT MIN(t2.cid)
FROM user t2
GROUP BY t2.cid
HAVING COUNT(t2.cid) > 1
)
) as todel
)
Due to some research on stackoverflow i found this:
Delete - I can't specify target table?
sounds like it is the same problem you have.

MySQL find ids for which there is no existing row

I have three tables :
1. Person (person_id, name) : (1, "Test1"), (2, "Test2"), (3, "Test3")
2. Role (role_id, description) : (1, "Admin"), (2, "Designer"), (3, "Developer") ..
3. PersonRoles (person_id, role_id) : (1 , 1), (1, 2), (2, 3), (2, 1), (3, 1)
Is it possible in MySQL with a query to get the ids of the people for which there`s no row with exact role in the PersonRoles table. For Example if I want to check for "Designer" role the query should return ids: 2 and 3
Here is your solution:
select person_id from Person
where person_id not in
(select person_id from Role r
inner join PersonRoles pr on pr.role_id=r.role_id
where r.description='Designer')
You could use the not exists operator:
SELECT *
FROM Person p
WHERE NOT EXISTS (SELECT *
FROM Role r
JOIN PersonRoles pr ON r.role_id = pr.role_id
WHERE r.description = 'Designer' AND
pr.person_id = p.person_id)

PL/SQL SELECT on two tables

I've got two tables. One of these contains userData and the other one contains userGroups linked to users which are in a Group.
Means like a user can be in 0 or more Groups.
I'm trying to get this columns with a query used like this:
SELECT distinct(a.userID), a.userName, Count(b.userID_FID)
FORM userData a,
userGroup b
WHERE a.userID = b.userID_FID
But somehow the Count part returning me a wrong number.
SELECT max(a.userName), Count(distinct b.userID_FID)
FROM userData a
JOIN userGroup b
ON a.userID = b.userID_FID
GROUP BY a.userID
My suggestion is do not use many tables with from
This will get you the number of groups each user is in.
To build the test scenario, use this:
create table #groups
(userID_FID int
,groups int
);
create table #user
(userID int);
insert into #groups values (1, 5);
insert into #groups values (1, 2);
insert into #groups values (1, 3);
insert into #groups values (1, 4);
insert into #groups values (2, 3);
insert into #groups values (2, 4);
insert into #groups values (3, 1);
insert into #groups values (3, 5);
insert into #user values (1);
insert into #user values (2);
insert into #user values (3);
select * from #groups;
select * from #user;
This piece is the query you want, according to the test scenario above:
select userid, count(*) as NumberOfGroups
from #groups g
, #user u
where g.userID_FID = u.userID
group by userID;

How to avoid grabbing a row from a one-to-many relationship?

I have three tables, with structures like this:
Trips: id
Users: id
users_has_trips: user_id, trips_id
My current query:
SELECT trips.id
FROM trips
LEFT JOIN users_has_trips ON users_has_trips.trips_id = trips.id
WHERE users_has_trips.users_id != '1'
I would like not to select a trip.id if the value in users_has_trips.users_id is set to a certain value (such as 1). users_has_trips has multiple rows with the same values for users_has_trips.trips_id so when I eliminate the row that has the undesired users_id, I will still have rows showing up.
For example, there are trip.id values of 1 and 2. users_has_trips columns (trips_id, users_id) have values of (1, 1), (1, 2), and (1, 3), respectively.
When I run the query above, WHERE will eliminate the the row for (1, 1), but will still grab the trips.id of 1 in rows (1, 2), (1, 3), and also the trips.id of 2.
The desired outcome is to not select trips.id value of 1 (because my users_id was associated with it) and only grab trips.id value of 2.
SELECT *
FROM trips AS t
WHERE t.id NOT IN
(
SELECT trip_id
FROM users_has_trips
WHERE user_id = '1'
)
Or rephrasing your question:
Only show those trips where no row exists for a given user:
SELECT *
FROM trips AS t
WHERE NOT EXISTS
(
SELECT *
FROM users_has_trips AS ut
WHERE ut.user_id = '1'
AND ut.trip_id = t.id
)