SQL to Select only full groups of data - sql

Let's say I have three sample tables for groups of people as shown below.
Table users:
id
name
available
1
John
true
2
Nick
true
3
Sam
false
Table groups:
id
name
1
study
2
games
Table group_users:
group_id
user_id
role
1
1
teach
1
2
stdnt
1
3
stdnt
2
1
tank
2
2
heal
And I need to show to a user all groups that he participates in and also available right now, which means all users in that group have users.available = true.
I tried something like:
SELECT `groups`.*, `users`.* , `group_users`.*
FROM `groups`
LEFT JOIN `group_users` ON `groups`.`id` = `group_users`.`group_id`
LEFT JOIN `users` ON `users`.`id` = `group_users`.`user_id`
WHERE `users`.`available` = true AND `users`.`id` = 1
But it just shows groups and part of their users, that are available. And I need to have ONLY the groups that have all their users available.
If I were to find all available groups as User 1 - I should get only group 2 and it's users. How to do this the right way?
Tables DDL:
CREATE TABLE users (
id int PRIMARY KEY,
name varchar(256) NOT NULL,
available bool
);
CREATE TABLE teams (
id int PRIMARY KEY,
name varchar(256) NOT NULL
);
CREATE TABLE team_users (
team_id int NOT NULL,
user_id int NOT NULL,
role varchar(64)
);
INSERT INTO users VALUES
(1, 'John', true ),
(2, 'Nick', true ),
(3, 'Sam' , false);
INSERT INTO teams VALUES
(1, 'study'),
(2, 'games');
INSERT INTO team_users VALUES
(1, 1, 'teach'),
(1, 2, 'stdnt'),
(1, 3, 'stdnt'),
(2, 1, 'tank' ),
(2, 2, 'heal' );
mySQL select version() output:
10.8.3-MariaDB-1:10.8.3+maria~jammy

Check do you need in this:
WITH cte AS (
SELECT users.name username,
teams.id teamid,
teams.name teamname,
SUM(NOT users.available) OVER (PARTITION BY teams.id) non_availabe_present,
SUM(users.name = #user_name) OVER (PARTITION BY teams.id) needed_user_present
FROM team_users
JOIN users ON team_users.user_id = users.id
JOIN teams ON team_users.team_id = teams.id
)
SELECT username, teamid, teamname
FROM cte
WHERE needed_user_present
AND NOT non_availabe_present;
https://dbfiddle.uk/?rdbms=mysql_8.0&fiddle=605cf10d147fd904fb2d4a6cd5968302
PS. I use user name as a criteria, you may edit and use user's identifier, of course.

Join the tables and aggregate with the conditions in the HAVING clause:
SELECT t.id, t.name
FROM teams t
INNER JOIN team_users tu ON t.id = tu.team_id
INNER JOIN users u ON u.id = tu.user_id
GROUP BY t.id
HAVING MIN(u.available) AND SUM(u.id = 1);
The HAVING clause is a simplification of:
HAVING MIN(u.available) = true AND SUM(u.id = 1) > 0
See the demo.

first you need to find those group which users is available. then find the all the group details of those group which is not related to those group which user is available.
SELECT * FROM team_users a
JOIN teams b ON a.team_id=b.id
JOIN users c ON a.user_id=c.id
WHERE NOT EXISTS
(
SELECT 1 FROM team_users tu
JOIN users u ON tu.user_id=u.id AND u.available =1
WHERE tu.team_id=a.Team_id
)

Related

Join a table with another table with columns containing null

I want to join users table with both Groupid and superadmingroupid from group table.
Superadmingroupid may be null
I tried below query but not working
SELECT U.Name, G.Name
FROM Groups G
INNER JOIN USERS U ON G.Groupid = U.Gid
LEFT JOIN USERs U2 On G.superadmingroupid= U.Gid
where U.Name='Mishrsa'
Group table
Groupid Gname SuperAdminGroupId
----- ------ --------
17 A 3
2 B null
3 C null
Users
------
id Name Gid
-- ------- ----
1 mishra 2
2 mishrsa 3
I want to diplay the user with groups that are referenced as groupid or superadmingroupid
Ex: User does not have groupid 17 but superadmingroupid 3 is there in users table so group 17 should come in the output
Output
Name GName
Mishra B
Mishra C
Mishra A
Solution for your problem is:
SELECT U.Name, G.GName
FROM Groups G
INNER JOIN USERS U
ON G.Groupid = U.Gid
OR G.superadmingroupid= U.Gid;
Working example: dbfiddle Link
I believe you should use UNION for that. (Maybe this is not the most elegant way).
The first part will give you the match between Groupid to Gid.
The second part will give you the match between SuperAdminGroupId to Gid.
The order is different then what you mentioned, and I do not know if it is important for you, but please try the below example:
SELECT U.Name, G.Name
FROM Groups G
JOIN Users U ON G.Groupid = U.Gid
UNION
SELECT U.Name, G.Name
FROM Groups G
JOIN Users U ON G.SuperAdminGroupId = U.Gid
Posting this answer just because I'd already written it before Bogner Boy posted their answer.
I changed the table names a touch because GROUP is a reserved word in SQL Server.
Bonger Boy's UNION might be more efficient for larger tables, but for smaller tables you'll be fine to use an OR or an IN:
CREATE TABLE AdminGroup
(
GroupId INTEGER,
Gname CHAR(1),
SuperAdminGroupId INTEGER
);
CREATE TABLE Users
(
Id INTEGER,
Name NVARCHAR(64),
Gid INTEGER
);
INSERT INTO Users (Id, Name, Gid) VALUES (1, 'mishra', 2);
INSERT INTO Users (Id, Name, Gid) VALUES (2, 'mishra',3);
INSERT INTO AdminGroup (GroupId, Gname, SuperAdminGroupId) VALUES (17, 'A', 3);
INSERT INTO AdminGroup (GroupId, Gname, SuperAdminGroupId) VALUES (2, 'B', null);
INSERT INTO AdminGroup (GroupId, Gname, SuperAdminGroupId) VALUES (3, 'C', null);
SELECT U.Name, G.GName
FROM Users U
INNER JOIN AdminGroup G ON U.Gid = G.SuperAdminGroupId OR U.Gid = G.GroupId;
--INNER JOIN AdminGroup G ON U.Gid IN (G.SuperAdminGroupId, G.GroupId);
Here's a DBFiddle:
https://www.db-fiddle.com/f/p1RA4z67SH1DijFMZyKRuA/0

Marking users as not active if they don't have required records

I have a simple table model. There are some required "Actions" that every user has to have completed.
User
-Id
-Name
-IsActive
Actions
-Id
-Name
UserActions
-UserID
-ActionID
I want to find all Users who don't have all the Action records in the UserAcitons table. If they don't have all actions records in UserActions, I want to mark IsActive as false.
There could be 20K users so this should be effecient to process.
Looking for the best way to do this without having a cursor.
Assuming rows in UserActions are unique, you can count the actions in the two tables and compare them:
update u
set isactive = (case when num_actions > total_actions then 1 else 0 end)
from users u left join
(select ua.userid, count(*) as num_actions
from useractions ua
group by ua.userid
) ua
on ua.userid = u.id cross join
(select count(*) as total_actions
from actions a
) a;
SQL Server doesn't support booleans, so this uses 0 for false and 1 for true.
You forgot to tell us some details, for example if there are duplicated actions, if ids are UNIQUE, etc.
for very simple scenario I managed to create the below example:
create table [User]
(
Id int not null primary key,
Name varchar(50) not null,
IsActive bit not null
)
create table [Actions]
(
Id int not null primary key,
Name varchar(50) not null
)
GO
create table [UserActions]
(
UserId int not null,
ActionId int not null,
foreign key (UserId) REFERENCES [User](Id),
foreign key (ActionId) REFERENCES [Actions](Id)
)
GO
insert into [User] values
(1, 'Alice', 1),(2, 'Bob', 1),(3, 'Caroline', 1)
insert into [Actions] values
(1, 'eat'),(2,'drink'),(3,'sleep')
insert into [UserActions] values
(1,1),(1,2),(1,3),
(2,1),(2,2),
(3,1),(3,2),(3,1)
GO
update us
set us.IsActive = 0
from [User] us
join
(
select ua.UserId, COUNT(distinct ua.ActionId) as ActionCount
from [UserActions] ua
group by ua.UserId
) as uac on uac.UserId = us.Id
where uac.ActionCount < (select count(*) from [Actions])
select * from [User] us
Provides de results below
Id Name IsActive
----------- ---------- --------
1 Alice 1
2 Bob 0
3 Caroline 0

Get room members, room's owner and admin at the same time in one query with grouped by id (unique) on PostgreSQL 12

I want to get room's member list, room's owner member in case of he doesn't exists in other table and admin member at the same time. Currently i fetch them individually.
CREATE TABLE public.room_members (
id bigint NOT NULL,
member_id bigint,
room_id bigint,
group_id bigint
);
CREATE TABLE public.rooms (
id bigint NOT NULL,
member_id bigint,
group_id bigint,
name varchar(128)
);
CREATE TABLE public.members (
id bigint NOT NULL,
group_id bigint,
username varchar(128),
is_admin bool default false
);
CREATE TABLE public.groups (
id bigint NOT NULL,
name varchar(128)
);
-- My Group created
INSERT INTO "groups" (id, name) VALUES (1, 'My Group');
-- Create users for this group. We have 4 users/members
INSERT INTO "members" (id, group_id, username, is_admin) VALUES (1, 1, 'Pratha', true);
INSERT INTO "members" (id, group_id, username) VALUES (2, 1, 'John');
INSERT INTO "members" (id, group_id, username) VALUES (3, 1, 'Mike');
INSERT INTO "members" (id, group_id, username) VALUES (4, 1, 'April');
-- April creates a room and he is owner of this room
INSERT INTO "rooms" (id, group_id, member_id, name) VALUES (1, 1, 4, 'My Room'); -- 4 is April
-- April also adds Mike to the room members. But she does not add herself. As she is owner.
INSERT INTO "room_members" (id, group_id, room_id, member_id) VALUES (1, 1, 1, 3); -- 3 is Mike
What I want is:
room_members list of 'My Room' (Which is only Mike at the moment)
My Room's owner in case of he didn't add himself to room_members table. Because he is the owner of that room (Which is April)
Plus, admin member (Which is Pratha)
And this should be unique. For example, if user add himself to room_members and also owner then it should fetch member one time only.
What I tried so far?
select * from members
left outer join room_members cm on cm.member_id = members.id
left outer join rooms c on c.id = cm.room_id
where c.name = 'My Room' or members.id = 1
I couldn't use group by here either. Also i don't need the all fields. Just room_members table fields only.
See here: https://rextester.com/XWDS42043
Expected output for room_members:
+-------------+------------+------------+
| member_id | group_id | username |
+-------------+------------+------------+
| 1 | 1 | Pratha |
+-------------+------------+------------+
| 3 | 1 | Mike |
+-------------+------------+------------+
| 4 | 1 | April |
+-------------+------------+------------+
Pratha: Because he is ADMIN
Mike: Because he is member of My Room. MEMBER
April: Because she created that room. OWNER
room_members can be many. I just added only Mike but it can have multiple members including admins and owners.
You can address this with UNION:
-- list the admin(s) of the room group
select m.id, m.group_id, m.username
from rooms r
inner join members m on m.group_id = r.group_id and m.is_admin = true
where r.name = 'My Room'
union
-- list the members of the room
select m.id, m.group_id, m.username
from rooms r
inner join room_members rm on r.id = rm.room_id
inner join members m on rm.member_id = m.id
where r.name = 'My Room'
union
-- recover the room owner
select m.id, m.group_id, m.username
from rooms r
inner join members m on r.member_id = m.id
where r.name = 'My Room'
UNION eliminates duplicates accross queries, so if a user is both member and/or group admin and/or owner of the room, they will only appear once.
In your fiddle, this query returns:
id group_id username
1 4 1 April
2 3 1 Mike
3 1 1 Pratha

SQL query to get at least one common item

I have 3 tables which are,
room (room_no)
room_no_access (room_no, user_id)
user (user_id)
If a user is recorded in room_no_access table with a room_no and user_id then that user cannot access the mentioned room.
Let's assume that I am a user in user table. Now what I need is to get the users who shares at least one common room access with me.
How to write a SQL to get this done? It's better if there are no loops in it.
Example:
rooms
room_1,
room_2,
room_3,
users
1,
2,
3,
4,
rooms_no_access
[room_1, 4],
[room_2, 3].
[room_3, 3],
[room_1, 3],
If I'm user 4 -> I have access to room_2, room_3
So who are the other users who has access to room_2 or room_3
They are ->1,2
This is kind of back to front, as you hold which rooms people DON'T have access to, and we really want rooms people DO have access to.
I created some test data:
--Test data
DECLARE #room TABLE (room_no INT);
INSERT INTO #room SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3;
DECLARE #user TABLE ([user_id] INT);
INSERT INTO #user SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3;
DECLARE #room_no_access TABLE (room_no INT, [user_id] INT);
INSERT INTO #room_no_access SELECT 1, 1;
INSERT INTO #room_no_access SELECT 3, 2;
INSERT INTO #room_no_access SELECT 1, 3;
INSERT INTO #room_no_access SELECT 2, 3;
INSERT INTO #room_no_access SELECT 3, 3;
...and then came up with this query:
--For each user find users with common access
--First find which rooms each user can access
WITH room_access AS (
SELECT
r.room_no,
u.[user_id]
FROM
#room r
CROSS JOIN #user u
LEFT JOIN #room_no_access rn ON rn.room_no = r.room_no AND rn.[user_id] = u.[user_id]
WHERE
rn.[user_id] IS NULL)
SELECT DISTINCT
u.[user_id],
sa.[user_id] AS shared_access_user
FROM
#user u
LEFT JOIN room_access ra ON ra.[user_id] = u.[user_id]
LEFT JOIN room_access sa ON sa.room_no = ra.room_no
WHERE
sa.[user_id] != u.[user_id]; --Ignore self match
That's a little long-winded for the job, but it's easy to follow.
Results are:
user_id shared_access_user
1 2
2 1
Because in my data user_id #1 and #2 share access to room #2, but user_id #3 has no access to any rooms.
I am returning the results for each user in the database, but you could filter this to only return results for a specified user?
Try this:
select distinct [user_id] from (
--all rooms that can be accessed by at least one user
select room_no, [USER_ID] from room cross join [user]
except
select room_no, [USER_ID] from room_no_access
) a where room_no in (
-- rooms that I have access to
select room_no from room
except
select room_no from room_no_access where [USER_ID] = /*my_user_id*/
)
This select users that share access with you and the rooms they shares.
set #you = 1;
select
others_access.user_id,
others_access.room_no
from
(select room_no, #you as user_id
from room r
/* rooms that you have access */
where not exists (
select * from room_no_access rna
where rna.room_no = r.room_no
and rna.user_id = #you)) you_access
join
/* rooms that others have access */
(select room_no, u.user_id
from room r cross join user u
where u.user_id <> #you
and not exists (
select * from room_no_access rna
where rna.room_no = r.room_no
and rna.user_id = u.user_id)) others_access
on you_access.room_no = others_access.room_no;
Here a DB Fiddle to play with

How to replace LEFT outer join with INNER join in SQL?

I have a view on which I need to provide cluster Indexing the problem is in order to provide cluster indexing the it should not have any of the left or right outer joins , and I want to replace the LEFT outer join with INNER join , one of the ways which I can think of is to insert a dummy value with lets say -1 in the right table and by doing this even if all the Ids from the left table wont match Ids from the right table in INNER JOIN but since we have inserted -1 in the right table and we are using IsNULL(u.UserId,-1) it should return all the values from the left table but somehow this approach is not working.
create table Users(
UserId int,
UserName nvarchar(255)
)
insert into Users values(1,'sid429')
insert into Users values(2,'ru654')
insert into Users values(3,'dick231')
create table managers
(
caseId int,
CaseName nvarchar(255),
UserId int
)
insert into managers values (100,'Case1',1)
insert into managers values (101,'Case2',2)
insert into managers values (-1,NULL,-1)
select username from users u inner join managers m on m.UserId=IsNULL(u.UserId,-1)
Don't talk about indexes, but I think you could replace LEFT JOIN by INNER JOIN + UNION
select username from users u inner join managers m on m.UserId= u.UserId
UNION ALL
select username from users u WHERE NOT EXISTS (SELECT 1 FROM managers m WHERE m.UserId = u.UserId)
IsNull(u.UserId,-1) doesn't seem right - u.UserId is never null, since the absence of data is in the managers table - in this case, u.UserId will always have a value, but m.UserId might not, so IsNull(u.UserId, -1) won't work.
I'm intrigued to see a better answer, but I don't think you can do that - I think you eventually need to substitute the value conditionally to -1 if it doesn't exist in the other table, like this:
select username from users u
inner join managers m on m.UserId =
case when not exists(select * from managers where UserId = u.UserId)
then -1 else u.UserId end
This has the desired effect, but looking at the execution plan, won't help your performance issue.
You can replace a LEFT OUTER JOIN with an INNER JOIN if you add the missing values in the related table.
It has not worked for you because you have added a -1 value. But the not matching value on your INNER JOIN is a 3, not a null or a -1.
You can do so at runtime with an UNION, no need to permanently create those values as you have tried to do (inserting that -1 value) :
with expanded_managers as (
select CaseId, CaseName, UserId
from managers
union
select null, null, UserId
from users
where not exists (select * from managers where managers.UserId = users.UserId)
)
select UserName, CaseName
from users
inner join expanded_managers on expanded_managers.UserId = users.UserId
if you require only username it should be simple:
select distinct username from users u inner join managers m on m.UserId=u.UserId OR ( m.UserId=-1 AND u.userId = u.userId)
I have cleaned-up this part a bit. I had to guess the logical model, given that you did not specify any constraints.
create table Users (
UserId int not null
, UserName nvarchar(255) not null
, constraint pk_users primary key (UserId)
, constraint ak_users unique (UserName)
);
create table Cases (
CaseId int not null
, CaseName nvarchar(255) not null
, UserId int not null
, constraint pk_cases primary key (CaseId)
, constraint ak_cases unique (CaseName)
, constraint fk_cases foreign key (UserId)
references Users (UserId)
);
insert into Users values(1,'sid429') ;
insert into Users values(2,'ru654') ;
insert into Users values(3,'dick231');
insert into Cases values (100,'Case1',1);
insert into Cases values (101,'Case2',2);
This is mostly self-explanatory, but you have to understand that candidate keys (unique) for the result are: {UserID, CaseId}, {UserName, CaseName}, {UserID, CaseName}, {UserName, CaseId}. Not sure if you were expecting that.
with
R_00 as (
select UserId from Users
except
select UserId from Cases
)
select u.UserId
, u.UserName
, c.CaseId
, c.CaseName
from Users as u
join Cases as c on u.UserId = c.UserId
union
select u.UserId
, u.UserName
, (-1) as CaseId
, 'n/a'as CaseName
from Users as u
join R_00 as r on r.UserId = u.UserID
;
Another version of this, similar to other examples in the post.
select u.UserId
, u.UserName
, c.CaseId
, c.CaseName
from Users as u
join Cases as c on u.UserId = c.UserId
union
select u.UserId
, u.UserName
, (-1) as CaseId
, 'n/a' as CaseName
from Users as u
where not exists (select 1 from Cases as c where c.UserId = u.userId)
;