SQL query to get at least one common item - sql

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

Related

SQL to Select only full groups of data

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
)

How to use array in a sql join and check if all of the array elements satisfy a condition?

I have two tables, activity and contacts in postgresql.
An activity can have multiple contacts in array form, like this
contact Id = {23,54,34}.
I want to delete a activity only if all the contact Ids of that activity are deleted in contacts table and keep the activity if at least one one contact id is still not deleted.
Deleted At is column in contacts table to check for deleted contacts. I don't want to use NOT IN.
Activity table
id contact Id
-------------------
16 {60,61,23}
15 {}
5 {59}
6 {}
You can use simple EXISTS predicate testing contacts table with activity.contacts array:
create table activity (
id int primary key,
contacts int[]
)
create table contacts (
id int primary key,
name varchar(10),
deleted boolean
)
insert into activity
select 16 as id, '{1,2,3}'::int[] as contacts union all
select 15, null union all
select 5, '{4}' union all
select 6, '{6, 5}'
insert into contacts
select 1 as id, 'q' as name, false as deleted union all
select 2, 'w', false union all
select 3, 'e', true union all
select 4, 'r', false union all
select 5, 't', true union all
select 6, 'y', true
delete
from activity a
where not exists (
select null
from contacts c
where not(c.deleted)
and c.id = any(a.contacts)
)
2 rows affected
db<>fiddle here
Use UNNEST to convert the nested-array to rows, then do an anti-join to look for broken references:
(An anti-join is when you perform a x LEFT OUTER JOIN y with a WHERE y.KeyColumn IS NULL - this gives you the opposite of an INNER JOIN with the same join criteria).
WITH unnested AS (
SELECT
Id AS ActivityId,
UNNEST( ContactId ) AS ContactIdFromArray
FROM
crmActivity
)
SELECT
u.ActivityId,
u.ContactIdFromArray AS Missing_ContactId_in_Activity
FROM
unnested AS u
LEFT OUTER JOIN contacts AS c ON
c.ContactId = u.ContactIdFromArray
WHERE
c.ContactId IS NULL
ORDER BY
u.ActivityId
I want to delete a activity only if all the contact Ids of that activity are deleted in contacts table and keep the activity if at least one one contact id is still not deleted.
This can be done with a DELETE FROM using WHERE crmActivity.Id IN with a CTE that generates the correct set of bad crmActivity.Id values, via a GROUP BY with the above query:
WITH unnested AS (
SELECT
Id AS ActivityId,
UNNEST( ContactId ) AS ContactIdFK
FROM
crmActivity
)
WITH brokenContacts AS (
SELECT
u.ActivityId,
u.ContactIdFK,
c.ContactId AS ContactIdPK
FROM
unnested AS u
LEFT OUTER JOIN contacts AS c ON
c.ContactId = u.ContactIdFromArray
)
WITH counts AS (
SELECT
ActivityId,
COUNT(*) AS ContactIdCount,
COUNT( CASE WHEN ContactIdPK IS NULL THEN 1 END ) AS BadContactIdCount
FROM
brokenContacts
GROUP BY
ActivityId
)
WITH badActivityIds AS (
SELECT
ActivityId
FROM
counts
WHERE
BadContactIdCount = ContactIdCount
AND
ContactIdCount > 0
)
DELETE FROM
crmActivity
WHERE
ActivityId IN ( SELECT ActivityId FROM badActivityIds );

select all from table1 with zero entries in table2 in sqlite?

Consider 2 tables, user and contact:
CREATE TABLE user (
id INT,
name VARCHAR(36)
);
CREATE TABLE contact (
id INT,
phone INT,
userID INT
);
INSERT INTO user (id,name) VALUES
(1,'Frank'),
(2,'Henry'),
(3,'John')
INSERT INTO contact (id,phone,userID) VALUES
(1,911,1),
(2,922,2),
(3,933,2)
I am interested in all user entries that do not have any contact.
The outer join of these tables returns 4 results:
SELECT contact.*, user.*
FROM contact
LEFT JOIN user
ON contact.userID = user.id
UNION ALL
SELECT contact.*, user.*
FROM user
LEFT JOIN contact
ON contact.userID = user.id
WHERE contact.userID IS NULL
How do I select all user where contact.userID is null (1 result, in this example)?
This is your second subquery:
SELECT c.*, u.*
FROM user u LEFT JOIN
contact c
ON c.userID = u.id
WHERE c.userID IS NULL;
It does exactly what you want, although I would remove the c.* from the SELECT.

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)
;

SQL - How to optimize the query

I have wrote the following query which extracts all users which are in child departments of current user's department.
Current user cames from client app, but I Decalred it here in SQL for tests reason.
DECLARE #UserID INT = 72
SELECT *
FROM users
WHERE Department_Id IN (
SELECT DISTINCT Id /*, idp*/
FROM departments
WHERE idp IN (
SELECT Department_Id
FROM users
WHERE Id = #UserID
)
)
OR Department_Id IN (
SELECT DISTINCT idp
FROM departments
WHERE idp IN (
SELECT Department_Id
FROM users
WHERE Id = #UserID
)
)
I wanted to select the Id and the idp from departments to have a short query, but when i use this way it returns me an SQL error :
Only one expression can be specified in the select list when the subquery is not introduced with EXISTS.
This is because my list should contain only one column, but not 2.
Please advice me any way to reduce this query, especially the second part (after OR) which is a copy-paste of first (before OR)
Thank you.
Try using EXISTS like this
SELECT *
FROM users u
WHERE EXISTS( SELECT *
FROM departments
WHERE idp IN (SELECT Department_Id FROM users WHERE Id = #UserID)
AND (id = u.Department_Id
OR idp = u.Department_Id) )
A few thoughts...
Nested IN subqueries are unlikely to be friendly.
You don't need DISTINCT when using IN
I'd use GROUP BY to deal with the 1:many relationships, but as your answer is using an alternative structure, I'll try to keep close to what you have...
DECLARE #UserID INT = 72
SELECT
*
FROM
users AS associates
WHERE
EXISTS (
SELECT
*
FROM
users
INNER JOIN
departments
ON departments.idp = users.Department_Id
WHERE
users.id = #user_id
AND ( departments.id = associates.department_id
OR departments.idp = associates.department_id)
)
If you did use the GROUP BY approach, you avoid sub-queries and correlated-sub-queries all together...
DECLARE #UserID INT = 72
SELECT
associates.id
FROM
users
INNER JOIN
departments
ON departments.idp = users.Department_Id
INNER JOIN
users AS associates
ON associates.department_id = departments.id
OR associates.department_id = departments.idp
WHERE
users.id = #user_id
GROUP BY
associates.id
If there any other fields in associates that you need, just add them to the SELECT and the GROUP BY.
SELECT *
FROM users
WHERE Department_Id IN (
SELECT myId FROM
( SELECT Id AS myId
FROM departments
UNION ALL
SELECT idp AS myId
FROM departments
) A
WHERE A.myId IN (
SELECT Department_Id
FROM users
WHERE Id = #UserID
)
)