How to replace LEFT outer join with INNER join in SQL? - 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)
;

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
)

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.

Selecting rows which do not exist for a specific user

I have 4 tables (Users,Config,Fields,AvailableFields)
User's relevant schema is
UserID uniqueidentifier NOT NULL PRIMARY_KEY
Config's relevant schema is
ConfigID uniqueidentifier NOT NULL PRIMARY KEY
UserID uniqueidentifier NOT NULL FOREIGN KEY (User.UserID)
Field's relevant schema is
FieldID uniqueidentifier NOT NULL PRIMARY KEY
AvailableFieldID uniqueidentifier NOT NULL FOREIGN KEY (AvailableFields.AvailableFieldsID)
ConfigID uniqueidentifier NOT NULL FOREIGN KEY (Config.ConfigID)
AvailableFields relevant schema is
AvailableFieldID uniqueidentifier NOT NULL PRIMARY KEY
I am trying to return all of the Available fields for a specific user which they do not have in their Fields table.
Is there a way to do this with a join?
I cannot see a way as the only link between the template AvailableFields table is AvailableFieldID which is a foreign key inside Fields table.
Yes, use the INNER JOIN function with the Config as the first table and Field as the second table to get all users with a Config entry. Then use RIGHT JOIN to select all the available field entries and leave out users with no available field entry.
Then all of the select with WHERE AvailableFields.AvailableFieldID IS NULL. This joins the table with all results from Field/Config table. Then it selects the users who do not have an AvailableFields entry.
Code:
SELECT *
FROM Config
INNER JOIN Field ON Config.ConfigID = Field.ConfigID
RIGHT JOIN AvailableFields ON AvailableFields.AvailableFieldID = Field.AvailableFieldID
WHERE Field.UserID IS NULL
This will return all entries in AvailableFields that don't have a corresponding Field entry (i.e. No users) .
You need to use combination of INNER JOIN and RIGHT JOIN
SELECT af.AvailableFieldID
from Config C
inner join Field F on F.configid = c.configid
right join AvailableFields af on F.AvailableFieldID = af.AvailableFieldID
where f.AvailableFieldID is null
Use
SELECT AvailableFieldID FROM AvailableFields
WHERE AvailableFieldID NOT IN (
SELECT AvailableFieldID from Field f INNER JOIN Config c ON f.ConfigID = c.ConfigID INNER JOIN UserID u on u.UserID = c.UserID
WHERE UserID = "Your condition")
I hope resolve your problem.
I think the most intuitive way would be to use NOT IN with a subquery:
SELECT * from AvailableField
WHERE AvailableField.AvailableFieldId NOT IN
(SELECT AvailableFieldId FROM Field
INNER JOIN Config ON Config.ConfigId = Field.ConfigId
WHERE Config.UserId = ?)
Where ? is a placeholder for the ID of the user you are interested in.
If you are performance-sensitive and are using large tables, multiple joins would be better than this subquery approach.
The simplified version answering your question's requirements:
select af.AvailableFieldID
from AvailableFields af -- all available fields
left join (select distinct f.AvailableFieldID
from Config c
join Fields f
on c.ConfigID = f.ConfigID
where c.UserID = #UserID) AS UserHas -- the fields the user has
on af.AvailableFieldID = UserHas.AvailableFieldID
where UserHas.AvailableFieldID is null -- a field match for the user isn't there
This will also return AvailableFieldID that are not in a Config.
The version you probably want b/c it can be multi-users:
select u.UserID, af.AvailableFieldID
from Users u -- all users
cross join AvailableFields af -- all fields
left join (select distinct f.AvailableFieldID, c.UserID
from Config c
join Fields f
on c.ConfigID = f.ConfigID) AS UserHas -- the fields each user has
on u.UserID = UserHas.UserID
and af.AvailableFieldID = UserHas.AvailableFieldID
where UserHas.AvailableFieldID is null -- a field match for the user isn't there
and (#UserID is null or u.UserID = #UserID) -- option to limit it to one user

How to let COUNT show zeros in tables with many to many relationship?

the following query does not show the Groups where no users belong to.
I would like to have the shown with a count of 0 too. How do I do this?
Like this should it be
Group A 8
Group B 0
Group C 2
This is it now
Group A 8
Group C 2
SELECT UsersToGroups.GroupID,
groups.Group,
COUNT(UsersToGroups.UserID) AS countUsersPerGroup
FROM users_Groups AS groups
LEFT JOIN AssociationUsersToGroups AS UsersToGroups ON
UsersToGroups.GroupID =
groups.ID
LEFT JOIN users_Users AS users ON
UsersToGroups.UserID =
users.ID
GROUP BY GroupID,
groups.Group
ORDER BY groups.Group ASC
Query will select all groups
SELECT groups.ID,
groups.Group,
FROM users_Groups AS groups
If you add LEFT JOIN AssociationUsersToGroups you should receive groups with number of participants:
SELECT groups.ID,
groups.Group,
COUNT(UsersToGroups.UserID) AS countUsersPerGroup
FROM users_Groups AS groups
LEFT JOIN AssociationUsersToGroups AS UsersToGroups ON
UsersToGroups.GroupID =
groups.ID
GROUP BY groups.ID, groups.Group
First of all i don't see why you need to join the user table?
There's no need to assuming that you have a foreign key relationship between "users" and "users-to-group" association
table with ON DELETE CASCADE
This works for me:
-- setting up test-tables and test-data
create table #Groups
(
ID int,
GroupName varchar(100)
)
create table #UsersToGroup
(
GroupID int,
UserID int
)
insert into #Groups
values (1,'Group A'),(2,'Group B'),(3,'Group C')
insert into #UsersToGroup
values (1,1),(1,2),(1,3),(1,4),(1,5),(1,6),(1,7),(1,8),(3,1),(3,2)
-- the query you want:
select g.ID as GroupID,
g.GroupName,
count(utg.UserID) as countUsersPerGroup
from #Groups g
left join #UsersToGroup utg on g.ID = utg.GroupID
group by g.ID, g.GroupName
order by g.GroupName asc
-- cleanup
drop table #Groups
drop table #UsersToGroup
output:

Join a table based on condition

I have a situation where I need to join a table only on a particular condition.
Lets take the underwritten scenario
create table [premiumuser] (user_id int, name nvarchar(50));
create table [liteuser] (user_id int, name nvarchar(50));
create table [feature] (id nvarchar(50), user_id int, userkey int);
insert into [premiumuser] select 1, 'stephen';
insert into [premiumuser] select 2, 'roger';
insert into [liteuser] select 1, 'apollo';
insert into [liteuser] select 2, 'venus';
insert into feature select 'Upload content', 1, 1;
insert into feature select 'Create account', 1, 0;
insert into feature select 'View content', 2, 0;
Now I want to join only 1 table at a time from premiumUser or Lite user.
i.e.
select F.*, U.Name from feature
Inner Join
if condition 1
then
LiteUser U on U.User_Id = F.User_ID
else
PremiumUser U on U.User_Id = F.User_ID
end
Is there anyway to achieve this???
I know something like this can be done
select
f.id,
case when userkey=0 then l.name else p.name end as username
from [feature] f
left join [liteuser] l on l.user_id = f.user_id
left join [premium user] p on p.user_id = f.user_id
but since I have huge tables I don't want to join both the tables.
I want to be selective in joining the tables
No, you can't selective join in standard TSQL (definitely true for MS SQL and I'm pretty sure it's true for Oracle and MySQL).
You can however put the condition in the join. This will allow SQL to very quickly ignore the join because it will evaluate the conditions in order:
select
f.id,
case when userkey=0 then l.name else p.name end as username
from [feature] f
left join [liteuser] l
on condition1 = 1
AND l.user_id = f.user_id
left join [premium user] p
on condition1 = 2
and p.user_id = f.user_id