Selecting rows which do not exist for a specific user - sql

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

Related

SQL Query multi reference to a table

I have two tables Task and User
Task PRIMARY KEY
IdTask
TaskName
IdHandler FOREIGN KEY REFERENCES IdUser
IdCreator FOREIGN KEY REFERENCES IdUser
User
IdUser PRIMARY KEY
Name
How to query with IdHandler and IdCreator reference to IdUser
Expected Result: TaskName, Name(Handler), Name(Creator)
PS: I'm also do not know if I can put Foreign Keys like that, at least SQL Server let me do it.
SELECT TaskName, h.Name [HandlerName], c.Name [CreatorName]
FROM Task t
INNER JOIN User h ON t.IdHandler = h.IdUser
INNER JOIN User c ON t.IdCreator = c.IdUser
Please see below query.
Select
tt.TaskName,
userHandler.Name as 'UserHandler',
userCreator.Name as 'UserCreator'
from
TasKTable tt
left join
User userHandler on userHandler.IdUser = tt.IdHandler
left join
User userCreator on userCreator.IdCreator = tt.IdHandler
select all field from all table
Select
tt.*,
userHandler.*,
userCreator.*
from
TasKTable tt left join
User userHandler on userHandler.IdUser = tt.IdHandler left join
User userCreator on userCreator .IdCreator = tt.IdHandler
You may also check this SQL Tutorial for reference.
Here is an alternative query not using JOINs
select T.NAME_TASK
,(select U1.NAME from USERS U1 where U1.ID_USER = T.ID_HANDLER) as HANDLER
,(select U2.NAME from USERS U2 where U2.ID_USER = T.ID_CREATOR) as CREATOR
from TASKS T
Refer to this db<>fiddle

How to select from multiple tables in a group by query?

I have some database tables containing some documents that people need to sign. The tables are defined (somewhat simplified) as follows.
create table agreement (
id integer NOT NULL,
name character varying(50) NOT NULL,
org_id integer NOT NULL,
CONSTRAINT agreement_pkey PRIMARY KEY (id)
CONSTRAINT org FOREIGN KEY (org_id) REFERENCES org (id) MATCH SIMPLE
)
create table version (
id integer NOT NULL,
content text NOT NULL,
publish_date timestamp NOT NULL,
agreement_id integer NOT NULL,
CONSTRAINT version_pkey PRIMARY KEY (id)
CONSTRAINT agr FOREIGN KEY (agreement_id) REFERENCES agreement (id) MATCH SIMPLE
)
I skipped the org table, to reduce clutter. I have been trying to write a query that would give me all the right agreement information for a given org. So far, I can do
SELECT a.id, a.name FROM agreement AS a
JOIN version as v ON (a.id = v.agreement_id)
JOIN org as o ON (o.id = a.org_id)
WHERE o.name = $1
GROUP BY a.id
This seems to give me a single record for each agreement that belongs to the org I want and has at least one version. But I need to also include content and date published of the latest version available. How do I do that?
Also, I have a separate table called signatures that links to a user and a version. If possible, I would like to extend this query to only include agreements where a given user didn't yet sign the latest version.
Edit: reflected the need for the org join, since I select orgs by name rather than by id
You can use a correlated subquery:
SELECT a.id, a.name, v.*
FROM agreement a JOIN
version v
ON a.id = v.agreement_id
WHERE a.org_id = $1 AND
v.publish_date = (SELECT MAX(v2.publish_date) FROM version v2 WHERE v2.agreement_id = v.agreement_id);
Notes:
The org table is not needed because agreement has an org_id.
No aggregation is needed for this query. You are filtering for the most recent record.
The correlated subquery is one method that retrieves the most recent version.
Postgresql has Window Functions.
Window functions allow you to operate a sort over a specific column or set of columns. the rank function returns the row's place in the results for the sort. If you filter to just where the rank is 1 then you will always get just one row and it will be the highest sorted for the partition.
select u.id, u.name, u.content, u.publish_date from (
SELECT a.id, a.name, v.content, v.publish_date, rank() over (partition by a.id order by v.id desc) as pos
FROM agreement AS a
JOIN version as v ON (a.id = v.agreement_id)
JOIN org as o ON (o.id = a.org_id)
WHERE o.id = $1
) as u
where pos = 1
SELECT a.id, a.name, max(v.publish_date) publish_date FROM agreement AS a
JOIN version as v ON (a.id = v.agreement_id)
JOIN org as o ON (o.id = a.org_id)
WHERE o.id = $1
GROUP BY a.id, a.name

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

How to work in case in join condition

How to find city when ContactID is provided and condition is if ContactID is coming as 123 then it will look whether it is P or C, If P then it will go to Person table and returns City(USA) as output and If C then it will go to Company table and gives City(AUS) as output.
NB: all tables contain thousands of record and City value comes from run time.
Unless you're dynamically generating the query (i.e. using some language other than SQL to execute it) then you need to join on both tables anyway. If you're joining on both tables then there's no need for a CASE statement:
select *
from contacts co
left outer join person p
on co.contactid = p.contactid
and co.person_company = 'P'
left outer join company c
on co.contactid = c.contactid
and co.person_company = 'C'
You'll start noting an issue here, for every column from PERSON and COMPANY you're going to have to add some business logic to work out which table you want the information from. This can get very tiresome
select co.contactid
, case when p.id is not null then p.name else c.name end as name
from contacts co
left outer join person p
on co.contactid = p.contactid
and co.person_company = 'P'
left outer join company c
on co.contactid = c.contactid
and co.person_company = 'C'
Your PERSON and COMPANY tables seem to have exactly the same information in them. If this is true in your actual data model then there's no need to split them up. You make the determination as to whether each entity is a person or a company in your CONTACTS table.
Creating additional tables to store data in this manner is only really helpful if you need to store additional data. Even then, I'd still put the data that means the same thing for a person or a companny (i.e. name or address) in a single table.
If there's a 1-2-1 relationship between CONTACTID and PID and CONTACTID and CID, which is what your sample data implies, then you have a number of additional IDs, which have no value.
Lastly, if you're not restricting that only companies can go in the COMPANY table and individuals in the PERSON table. You need the PERSON_COMPANY column to exist in both PERSON and COMPANY, though as a fixed string. It would be more normal to set up this data model as something like the following:
create table contacts (
id integer not null
, contact_type char(1) not null
, name varchar2(4000) not null
, city varchar2(3)
, constraint pk_contacts primary key (id)
, constraints uk_contacts unique (id, contact_type)
);
create table people (
id integer not null
, contact_type char(1) not null
, some_extra_info varchar2(4000)
, constraint pk_people primary key (id)
, constraint fk_people_contacts
foreign key (id, contact_type)
references contacts (id, contact_type)
, constraint chk_people_type check (contact_type = 'P')
);
etc.
you can LEFT JOIN all 3 tables and the using a CASE statement select the one that you need based on the P or C value
SELECT
CASE c.[Person/Company]
WHEN 'P' THEN p.NAME
WHEN 'C' THEN a.Name
END AS Name
FROM Contact c
LEFT JOIN Person p on p.ContactId = c.ContactId
LEFT JOIN Company a on a.ContachId = c.ContactId
Ben's answer is almost right. You might want to check that the first join has no match before doing the second one:
select c.*, coalesce(p.name, c.name) as p.name
from contacts c left outer join
person p
on c.contactid = p.contactid and
c.person_company = 'P' left join
company co
on c.contactid = co.contactid and
c.person_company = 'C' and
p.contactid is null;
This may not be important in your case. But in the event that the second join matches multiple rows and the first matches a single row, you might not want the additional rows in the output.

SQL Server Select from table where primary key does not appear as foreign key in another table

I have 3 tables -
User UserID PK
SecurityGroup SecurityGroupID PK, SecurityGroupName
UserSecurityGroup UserSecurityGroupID, UserID FK, SecurityGroupID FK
I am trying to select the names of the security groups that a user is NOT a part of.
A user can be a member of more than one group.
i.e. I have three security groups: Admin, Moderator, Member
I pass through a UserID. Said user is assigned to the Admin and Moderator groups but is NOT part of the Member group. I'm trying to display "Member".
These are my attempts so far:
Attempt 1 -
select tblSecurityGroup.SecurityGroupName
from tblSecurityGroup
inner join tblUserSecurityGroup
on tblSecurityGroup.SecurityGroupID = tblUserSecurityGroup.SecurityGroupID
inner join tblUser
on tblUserSecurityGroup.UserID = tblUser.UserID
where tblUser.UserID = 1
and tblSecurityGroup.SecurityGroupID not in (tblUserSecurityGroup.SecurityGroupID);
Attempt 2 -
select tblSecurityGroup.SecurityGroupName
from tblSecurityGroup
inner join tblUserSecurityGroup
on tblSecurityGroup.SecurityGroupID = tblUserSecurityGroup.SecurityGroupID
inner join tblUser
on tblUserSecurityGroup.UserID = tblUser.UserID
where tblUser.UserID = 1
and not exists (select tblSecurityGroup.SecurityGroupID
from tblSecurityGroup
where tblUserSecurityGroup.SecurityGroupID = tblSecurityGroup.SecurityGroupID);
Guidance for a nooby student would be most appreciated.
Your question can be answered by a not exists query. Here is one method:
select sg.SecurityGroupName
from tblSecurityGroup sg
where not exists (select 1
from tblUserSecurityGroup usg
where sg.SecurityGroupID = usg.SecurityGroupID and
usg.UserID = 1
);
Note that tblUser is not needed because UserID is in tblUserSecurityGroup.