select distinct from join - sql

My Tables look like this
Table 1 Table 2
Users Options
id name id user_id option
------- --- -------- --------
1 Donald 1 1 access1
2 John 2 1 access2
3 Bruce 3 1 access3
4 Paul 4 2 access1
5 Ronald 5 2 access3
6 Steve 6 3 access1
Now, i want to select join these to find a user which has only access1
If i do something like
select t1.id,t1.name,t2.id,t2.user_id,t2.option
from table1 t1, table2 t2
where t1.id=t2.user_id
and option='access1';
This does not give me unique results, as in the example i need only user_id=3 my data has has these in hundreds
I also tried something like
select user_id from table2 where option='access1'
and user_id not in (select user_id from table2 where option<>'access1')
There have been other unsuccessful attempts too but i am stuck here

You can do this using a EXISTS subquery (technically, a left semijoin):
SELECT id, name
FROM table1
WHERE EXISTS(
SELECT * FROM table2
WHERE table1.id = table2.user_id
AND table2.option = 'access1'
)
If you want only users that have access1 and not any other access, add NOT EXISTS (a left anti-semi-join; there's a term to impress your colleagues!):
AND NOT EXISTS (
SELECT * FROM table2
WHERE table1.id = table2.user_id
AND table2.option <> 'access1'
)

bool_and makes it simple
with users (id,name) as ( values
(1,'donald'),
(2,'john'),
(3,'bruce'),
(4,'paul'),
(5,'ronald'),
(6,'steve')
), options (id,user_id,option) as ( values
(1,1,'access1'),
(2,1,'access2'),
(3,1,'access3'),
(4,2,'access1'),
(5,2,'access3'),
(6,3,'access1')
)
select u.id, u.name
from
users u
inner join
options o on o.user_id = u.id
group by 1, 2
having bool_and(o.option = 'access1')
;
id | name
----+-------
3 | bruce

If you want the user that has only access1, I would use aggregation:
select user_id
from table2
group by user_id
having min(option) = max(option) and min(option) = 'access1';

WITH users(id,name) AS ( VALUES
(1,'Donald'),
(2,'John'),
(3,'Bruce'),
(4,'Paul'),
(5,'Ronald'),
(6,'Steve')
), options(id,user_id,option) AS ( VALUES
(1,1,'access1'),
(2,1,'access2'),
(3,1,'access3'),
(4,2,'access1'),
(5,2,'access3'),
(6,3,'access1')
), user_access_count AS (
SELECT op.user_id,count(op.option) AS access_count
FROM options op
WHERE EXISTS(
SELECT 1 FROM options
WHERE option = 'access1'
)
GROUP BY op.user_id
)
SELECT u.id,u.name
FROM users u
INNER JOIN user_access_count uac ON uac.user_id = u.id
WHERE uac.access_count = 1;

Related

Subquery SQL for link name and firstname

Hello I would like to retrieve the name and the first name in the user table thanks to the id contained in the message table (id_receive and id_send) in sql via a subquery
SELECT user.nom FROM user
WHERE user.id IN (
SELECT message.id_send, message.id_receive FROM message WHERE message.id=1
)
```
I would recommend using EXISTS, twice:
SELECT u.nom
FROM user u
WHERE EXISTS (SELECT 1 FROM message m WHERE m.id = 1 AND u.id = id_send) OR
EXISTS (SELECT 1 FROM message m WHERE m.id = 1 AND u.id = id_receive) ;
However, a JOIN might also be appropriate:
SELECT u.nom
FROM user u JOIN
message m
ON u.id IN (m.id_send, id_receive)
WHERE m.id = 1;
I suspect it isn't actually what you want but it looks like this is what you're trying to do:
SELECT user.nom FROM user
WHERE user.id IN (
SELECT message.id_send FROM message WHERE message.id=1
UNION ALL
SELECT message.id_receive FROM message WHERE message.id=1
)
The query that drives the IN should return a single column of values
Try and conceive that in works like this:
SELECT * FROM t WHERE c IN(
1
2
3
)
Not like this:
SELECT * FROM t WHERE c IN(
1 2 3
)
Nor like this:
SELECT * FROM t WHERE c IN(
1 2 3
4 5 6
)
It might help you reember that the query inside it must return a single column, but multiple rows, all of qhich are searched for a matching value c by IN
Small addition to your original query to make it working:
SELECT user.nom FROM user
WHERE user.id IN (
SELECT unnest(array[message.id_send, message.id_receive])
FROM message
WHERE message.id=1
)

SQL Query to fetch a user without an associated service

I have the below data in a table. I am trying to fetch all the "Modem" users, who do not have an associated telephone service.
UserID DeviceNumber DeviceType DeviceRole
1 A Telephone SingleUser
1 A Modem MultiUser
1 B Modem MultiUser
2 C Telephone SingleUser
2 C Modem MultiUser
2 D Modem MultiUser
select distinct t.* from table t
join table t1 on t1.UserID= v.UserID
and t1.DeviceNumber <> t.DeviceNumber
and t.DeviceType = 'Modem';
I want to see DeviceNumber B and D in my output. But above query is not returning expected results.
Hmmm . . . One method would be:
select t.*
from t
where t.devicetype = 'Modem' and
not exists (select 1
from t t2
where t2.userid = t.userid and t2.devicenumber = t.devicenumber and
t2.devicetype = 'Telephone'
);
You can do by count. Here is the demo.
select
UserID,
DeviceNumber,
DeviceType,
DeviceRole
from
(
select
yt.*,
count(*) over (partition by DeviceNumber) as cnt
from yourTable yt
) val
where cnt = 1
and DeviceType = 'Modem'
Output:

SQL query to check if user assigned with a specific value and does not a different value assigned to the same user?

We can have users with multiple valueIDs. In SQL Server I am trying to pull all users that have valueID of 3 but do not have valueID of 1 and 2. So Table A would have a user column and a valueID column.
How do I write a SQL query to do this?
Also, any good resources to get my SQL query skills up to par?
Much appreciated.
Try:
SELECT user
FROM A outer
WHERE valueID = 3
AND NOT EXISTS
(
SELECT 1
FROM A inner
WHERE
(
valueID = 1
OR valueID = 2
)
AND inner.user = outer.user
)
Here's one more.... (all users with valueid = 3 and nothing else)
SELECT [USER]
FROM A
GROUP BY [USER]
HAVING MAX(CASE WHEN valueid = 3 THEN 0 when valueid IN (1,2) THEN 1 END) = 0;
This query will return all users that have values (3 and 1) or (3 and 2) or just 3
select user
from mytable t1
where valueId = 3
and (select count(distinct valueId)
from mytable t2 where t2.user = t1.user
and valueId IN (1,2)) < 2
A simple way to get what you asked if you meant users with a value of 3 except those which also have values of 1 and 2
Select s.UserID From SomeTable s
Where s.UserID NOT In
(select s1s.UserID From SomeTable s1s
inner join SomeTable s2s On s1s.UserID = s2s.UserID
Where s1s.ValueId = 1 And s2s.ValueId = 2)
Or
Select s.UserID From SomeTable s
Left Join
(select s1s.UserID From SomeTable s1s
inner join SomeTable s2s On s1s.UserID = s2s.UserID
Where s1s.ValueId = 1 And s2s.ValueId = 2) both
Where both.userID is null
I'm not really sure what your table structure is, but it's likely the below query should work depending on column names...
SELECT user, valueID
FROM users
WHERE valueID = 3

How to exclude records with certain values in sql select

How do I only select the stores that don't have client 5?
StoreId ClientId
------- ---------
1 4
1 5
2 5
2 6
2 7
3 8
I'm trying something like this:
SELECT SC.StoreId FROM StoreClients
INNER JOIN StoreClients SC
ON StoreClients.StoreId = SC.StoreId
WHERE SC.ClientId = 5
GROUP BY StoreClients.StoreId
That seems to get me all the stores that have that client but I can't do the opposite because if I do <> 5 ill still get Store 1 and 2 which I don't want.
I'm basically trying to use this result in another query's EXISTS IN clause
One way:
SELECT DISTINCT sc.StoreId
FROM StoreClients sc
WHERE NOT EXISTS(
SELECT * FROM StoreClients sc2
WHERE sc2.StoreId = sc.StoreId AND sc2.ClientId = 5)
SELECT SC.StoreId
FROM StoreClients SC
WHERE SC.StoreId NOT IN (SELECT StoreId FROM StoreClients WHERE ClientId = 5)
In this way neither JOIN nor GROUP BY is necessary.
SELECT DISTINCT a.StoreID
FROM tableName a
LEFT JOIN tableName b
ON a.StoreID = b.StoreID AND b.ClientID = 5
WHERE b.StoreID IS NULL
SQLFiddle Demo
OUTPUT
╔═════════╗
║ STOREID ║
╠═════════╣
║ 3 ║
╚═════════╝
SELECT StoreId
FROM StoreClients
WHERE StoreId NOT IN (
SELECT StoreId
FROM StoreClients
Where ClientId=5
)
SQL Fiddle
You can use EXCEPT syntax, for example:
SELECT var FROM table1
EXCEPT
SELECT var FROM table2
<> will surely give you all values not equal to 5.
If you have more than one record in table it will give you all except 5.
If on the other hand you have only one, you will get surely one.
Give the table schema so that one can help you properly

How to optimize m:n relation query on 3 tables

this is my sql problem - there are 3 tables:
Names Lists ListHasNames
Id Name Id Desc ListsId NamesId
=-------- ------------ ----------------
1 Paul 1 Football 1 1
2 Joe 2 Basketball 1 2
3 Jenny 3 Ping Pong 2 1
4 Tina 4 Breakfast Club 2 3
5 Midnight Club 3 2
3 3
4 1
4 2
4 3
5 1
5 2
5 3
5 4
Which means that Paul (Id=1) and Joe (Id=2) are in the Football team (Lists.Id=1), Paul and Jenny in the Basketball team, etc...
Now I need a SQL statement which returns the Lists.Id of a specific Name combination:
In which lists are Paul, Joe and Jenny the only members of that list ? Answer only Lists.Id=4 (Breakfast Club) - but not 5 (Midnight Club) because Tina is in that list, too.
I've tried it with INNER JOINS and SUB QUERIES:
SELECT Q1.Lists_id FROM
(
SELECT Lists_Id FROM
names as T1,
listhasnames as T2
WHERE
(T1.Name='Paul') and
(T1.Id=T2.Names_ID) and
( (
SELECT count(*) FROM
listhasnames as Z1
where (Z1.lists_id = T2.lists_Id)
) = 3)
) AS Q1
INNER JOIN (
SELECT Lists_Id FROM
names as T1,
listhasnames as T2
WHERE
(T1.Name='Joe') and
(T1.Id=T2.Names_ID) and
(
(SELECT count(*) FROM
listhasnames as Z1
WHERE (Z1.Lists_id = T2.lists_id)
) = 3)
) AS Q2
ON (Q1.Lists_id=Q2.Lists_id)
INNER JOIN (
SELECT Lists_Id FROM
names as T1,
listhasnames as T2
WHERE
(T1.Name='Jenny') and
(T1.Id=T2.Names_ID) and
(
(SELECT count(*) FROM
listhasnames as Z1
WHERE (Z1.Lists_id = T2.lists_id)
) = 3)
) AS Q3
ON (Q1.Lists_id=Q3.Lists_id)
Looks a little bit complicated, uh? How to optimize that?
I need only that Lists.Id in which specific names are in (and only these names and nobody else). Maybe with SELECT IN?
Regards,
Dennis
SELECT ListsId
FROM ListHasNames a
WHERE NamesId in (1, 2, 3)
AND NOT EXISTS
(SELECT * from ListHasNames b
WHERE b.ListsId = a.ListsId
AND b.NamesId not in (1, 2, 3))
GROUP BY ListsId
HAVING COUNT(*) = 3;
Edit: Corrected thanks to Chris Gow's comment; the subselect is necessary to exclude lists that have other people on them.
Edit 2 Corrected the table name thanks to Dennis' comment
Using Carl Manaster's solution as a starting point I came up with:
SELECT listsid
FROM listhasnames
GROUP BY listsid HAVING COUNT(*) = 3
INTERSECT
SELECT x.listsid
FROM listhasnames x, names n
WHERE n.name IN('Paul', 'Joe', 'Jenny')
AND n.id = x.namesid
Updated:
select a.ListsId from
(
--lists with three names only
select lhn.ListsId, count(*) as count
from ListHasNames lhn
inner join Names n on lhn.NamesId = n.Id
group by lhn.ListsId
having count(*) = 3
) a
where a.ListsId in (select ListsId from ListHasNames lhn where NamesId = (select NamesId from names where Name = 'Paul'))
and a.ListsId in (select ListsId from ListHasNames lhn where NamesId = (select NamesId from names where Name = 'Joe'))
and a.ListsId in (select ListsId from ListHasNames lhn where NamesId = (select NamesId from names where Name = 'Jenny'))
I was just solving a problem recently that may work well for your case as well. It may be overkill.
I took the approach of creating a list of candidate associations that may be the correct solution, and then using a cursor or queue table to go through the likely correct solutions to do full validation.
In my case this was implemented by doing like
select
ParentId
count(*) as ChildCount
checksum_agg(checksum(child.*) as ChildAggCrc
from parent join child on parent.parentId = child.parentId
Then you can compare the count and aggregate checksum against your lookup data (i.e. your 3 names to check for). If no rows match, you are guaranteed to have no matches. If any row matches you can then go through and do a join of that specific ParentId to validate if there are any discrepancies between the row sets.
Clear as mud? :)