I have two tables,
PERSON
and FRIENDS.
FRIENDS has the fields NAME and SURNAME.
A person has N friends.
I want to retrieve all the PERSONs that have atleast two FRIENDs, one with name ="mark", and the other with name="rocco" and surname ="siffredi".
Example: if I have a person that has 5 friends, one of them is called mark and no one is called rocco siffredi, no tables are returned.
I was thinking about:
SELECT * FROM person p
JOIN friends AS f ON p.ID=f.personID
WHERE f.name ="mark" AND f IN
( SELECT * from FRIENDS WHERE name="rocco" and surname="siffredi")
or
SELECT * FROM person p
JOIN friends AS f1 ON p.ID=f1.personID
JOIN friends AS f2 ON p.ID=f2.personID
WHERE f1.name="mark" AND f2.name="rocco" AND f2.surname="siffredi"
What is the best way? I mean the fastest way to execute it.
I don't care about readability.
Is there any other way to execute this query?
Ty.
EDIT: added the join on the ID...
I had to guess your column names and make up a table:
Use EXISTS:
CREATE table FRIENDS(person_id INT, friend_id INT)
go
SELECT *
FROM person
WHERE
EXISTS
(SELECT *
FROM friends f
JOIN person per
ON f.friend_id = per.id
WHERE
per.name ='mark' AND
person.id = f.person_id) AND
EXISTS
(SELECT *
FROM friends f
JOIN person per
ON f.friend_id = per.id
WHERE
per.name = 'rocco' AND
per.surname='siffredi' AND
person.id = f.person_id)
Your schema design isn't very good for what you are trying to do... I would have a Person table as you have, which would also contain a unique identifier called PersonId. I would then have a Friends table which took two fields - Person1Id and Person2Id.
This gives you a couple of important advantages - first of all your system is able to handle more than one bloke called John Smith (because we join on Ids rather than Names...). Secondly, a person's details are only ever recorded in the Person table. One definition of truth...
With these data as input:
INSERT INTO Person VALUES
(1, 'Bob', 'Smith'),
(2, 'Jim', 'Jones')
INSERT INTO Friends VALUES
(1, 1, 'Mark', 'Tally'),
(2, 1, 'John', 'Smith'),
(3, 1, 'Jack', 'Pollock'),
(4, 2, 'Mark', 'Rush'),
(5, 2, 'Rocco', 'Siffredi'),
(6, 2, 'Mark', 'Bush')
you can use this query:
SELECT PersonId, COUNT(*) AS NoOfFriends
FROM (
SELECT DISTINCT PersonId, Name,
Surname = CASE WHEN NAME = 'Mark' THEN NULl
ELSE Surname
END
FROM Friends
WHERE Name = 'Mark' OR (Name = 'Rocco' AND Surname = 'Siffredi') ) t
GROUP BY PersonId
to get the distinct number of required friends per PersonID:
PersonId NoOfFriends
------------------------
1 1
2 2
You can now join with the above table expression on PersonId and filter it by NoOfFriends:
SELECT p.*
FROM Person AS p
INNER JOIN (
SELECT PersonId, COUNT(*) AS NoOfFriends
FROM (
SELECT DISTINCT PersonId, Name,
Surname = CASE WHEN NAME = 'Mark' THEN NULl
ELSE Surname
END
FROM Friends
WHERE Name = 'Mark' OR (Name = 'Rocco' AND Surname = 'Siffredi') ) t
GROUP BY PersonId ) s ON s.PersonId = p.ID
WHERE s.NoOfFriends = 2
so as to get only persons having the required combination of associated friends:
ID Name Surname
-------------------
2 Jim Jones
P.S. I have completely re-written my answer after #t-clausen.dk's comment.
Related
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
)
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
I'm wondering if it is possible to inner join an inner join with another inner join.
I have a database of 3 tables:
people
athletes
coaches
Every athlete or coach must exist in the people table, but there are some people who are neither coaches nor athletes.
What I am trying to do is find a list of people who are active (meaning play or coach) in at least 3 different sports. The definition of active is they are either coaches, athletes or both a coach and an athlete for that sport.
The person table would consist of (id, name, height)
the athlete table would be (id, sport)
the coaching table would be (id, sport)
I have created 3 inner joins which tell me who is both a coach and and an athlete, who is just a coach and who is just an athlete.
This is done via inner joins.
For example,
1) who is both a coach and an athlete
select
person.id,
person.name,
coach.sport as 'Coaches and plays this sport'
from coach
inner join athlete
on coach.id = athlete.id
and coach.sport = athlete.sport
inner join person
on athlete.id = person.id
That brings up a list of everyone who both coaches and plays the same sport.
2) To find out who only coaches sports, I have used inner joins as below:
select
person.id,
person.name,
coach.sport as 'Coaches this sport'
from coach
inner join person
on coach.id = person.id
3) Then to find out who only plays sports, I've got the same as 2) but just tweaked the words
select
person.id,
person.name,
athlete.sport as 'Plays this sport'
from athlete
inner join person
on athlete.id = person.id
The end result is now I've got:
1) persons who both play and coach the same sport
2) persons who coach a sport
3) persons who play a sport
What I would like to know is how to find a list of people who play or coach at least 3 different sports? I can't figure it out because if someone plays and coaches a sport like hockey in table 1, then I don't want to count them in table 2 and 3.
I tried using these 3 inner joins to make a massive join table so that I could pick the distinct values but it is not working.
Is there an easier way to go about this without making sub-sub-queries?
What I would like to know is how to find a list of people who play /
coach at least 3 different sports? I can't figure it out because if
someone plays and coaches a sport like hockey in table 1, then I don't
want to count them in table 2 and 3.
you can do something like this
select p.id,min(p.name) name
from
person p inner join
(
select id,sport from athlete
union
select id,sport from coach
)
ca
on ca.id=p.id
group by p.id
having count(ca.sport)>2
CREATE TABLE #person (Id INT, Name VARCHAR(50));
CREATE TABLE #athlete (Id INT, Sport VARCHAR(50));
CREATE TABLE #coach (Id INT, Sport VARCHAR(50));
INSERT INTO #person (Id, Name) VALUES(1, 'Bob');
INSERT INTO #person (Id, Name) VALUES(2, 'Carol');
INSERT INTO #person (Id, Name) VALUES(2, 'Sam');
INSERT INTO #athlete (Id, Sport) VALUES(1, 'Golf');
INSERT INTO #athlete (Id, Sport) VALUES(1, 'Football');
INSERT INTO #coach (Id, Sport) VALUES(1, 'Tennis');
INSERT INTO #athlete (Id, Sport) VALUES(2, 'Tennis');
INSERT INTO #coach (Id, Sport) VALUES(2, 'Tennis');
INSERT INTO #athlete (Id, Sport) VALUES(2, 'Swimming');
-- so Bob has 3 sports, Carol has only 2 (she both coaches and plays Tennis)
SELECT p.Id, p.Name
FROM
(
SELECT Id, Sport
FROM #athlete
UNION -- this has an implicit "distinct"
SELECT Id, Sport
FROM #coach
) a
INNER JOIN #person p ON a.Id = p.Id
GROUP BY p.Id, p.Name
HAVING COUNT(*) >= 3
-- returns 1, Bob
I have created a SQL with some test data - should work in your case:
Connecting the two results in the subselect with UNION:
UNION will return just non-duplicate values. So every sport will be just counted once.
Finally just grouping the resultset by person.Person_id and person.name.
Due to the HAVING clause, just persons with 3 or more sports will be returned-
CREATE TABLE person
(
Person_id int
,name varchar(50)
,height int
)
CREATE TABLE coach
(
id int
,sport varchar(50)
)
CREATE TABLE athlete
(
id int
,sport varchar(50)
)
INSERT INTO person VALUES
(1,'John', 130),
(2,'Jack', 150),
(3,'William', 170),
(4,'Averel', 190),
(5,'Lucky Luke', 180),
(6,'Jolly Jumper', 250),
(7,'Rantanplan ', 90)
INSERT INTO coach VALUES
(1,'Football'),
(1,'Hockey'),
(1,'Skiing'),
(2,'Tennis'),
(2,'Curling'),
(4,'Tennis'),
(5,'Volleyball')
INSERT INTO athlete VALUES
(1,'Football'),
(1,'Hockey'),
(2,'Tennis'),
(2,'Volleyball'),
(2,'Hockey'),
(4,'Tennis'),
(5,'Volleyball'),
(3,'Tennis'),
(6,'Volleyball'),
(6,'Tennis'),
(6,'Hockey'),
(6,'Football'),
(6,'Cricket')
SELECT person.Person_id
,person.name
FROM person
INNER JOIN (
SELECT id
,sport
FROM athlete
UNION
SELECT id
,sport
FROM coach
) sports
ON sports.id = person.Person_id
GROUP BY person.Person_id
,person.name
HAVING COUNT(*) >= 3
ORDER BY Person_id
The coaches & athletes, ie people who are coaches or athletes, are relevant to your answer. That is union (rows in one or another), not (inner) join rows in one and another). (Although outer join involves a union, so there is a complicated way to use it here.) But there's no point in getting that by unioning only-coaches, only-athletes & coach-athletes.
Idiomatic is to group & count the union of Athletes & Coaches.
select id
from (select * from Athletes union select * from Coaches) as u
group by id
having COUNT(*) >= 3
Alternatively, you want ids of people who coach or play a 1st sport and coach or play a 2nd sport and coach or play a 3rd sport where the sports are all different.
with u as (select * from Athletes union select * from Coaches)
select u1.id
from u u1
join u u2 on u1.id = u2.id
join u u3 on u2.id = u3.id
where u1.sport <> u2.sport and u2.sport <> u3.sport and u1.sport <> u3.sport
If you wanted names you would join that with People.
Is there any rule of thumb to construct SQL query from a human-readable description?](https://stackoverflow.com/a/33952141/3404097)
My query to get person and information about his friend or parent:
select civils.name, friends.fullName as full_name, friends.age as age, friends.height as height
from (query to get list people in city) as civils
inner join friends on civils.friendId = friends.Id and friends.name = "John"
union
select civils.name, parents.fullName as full_name, parents.age as age, parents.height as height
from (query to get list people in city) as civils
inner join parents on civils.parentId = parents.Id and parents.name = "John"
But i don't want to waste resource for twice getting civils. How to get list data in one time and use it for two inner joins like this:
select civils.name, full_name, age, height from (query to get list people in city) as civils
inner join friends on civils.friendId = friends.Id and friends.name = "John"
union
inner join parents on civils.parentId = parents.Id and parents.name = "John"
I'm using DB2.
Updated:
Sorry for unclear question, I expect the result formart like this:
civils.name | full_name | age | height
But if I tried solutions like suggested answers, the result format will be:
civils.name | friends.full_name | friends.age | friends.height | parents.full_name | parents.age | parents.height
and I have to do one more step to convert this list to my expected format list.
I'm a little concerned about what you may be doing in your civils reference, but you can wrap it as a CTE:
WITH Civil_List AS (<query to get list people in city>)
SELECT Civils.name, Friends.full_name, Friends.age, Friends.height
FROM Civils_List
JOIN Friends
ON Friends.id = Civils_List.friendId
AND Friends.name = 'John'
UNION
SELECT Civils.name, Parents.full_name, Parents.age, Parents.height
FROM Civils_List
JOIN Parents
ON Parents.id = Civils_List.friendId
AND Parents.name = 'John'
If I have three columns in my MySQL table people, say id, name, created where name is a string and created is a timestamp.. what's the appropriate query for a scenario where I have 10 rows and each row has a record with a name. The names could have a unique id, but a similar name none the less. So you can have three Bob's, two Mary's, one Jack and 4 Phil's.
There is also a hobbies table with the columns id, hobby, person_id.
Basically I want a query that will do the following:
Return all of the people with zero hobbies, but only check by the latest distinct person created, if that makes sense. Meaning if there is a Bob person that was created yesterday, and one created today.. I only want to know if the Bob created today has zero hobbies. The one from yesterday is no longer relevant.
select pp.id
from people pp, (select name, max(created) from people group by name) p
where pp.name = p.name
and pp.created = p.created
and id not in ( select person_id from hobbies )
SELECT latest_person.* FROM (
SELECT p1.* FROM people p1
WHERE NOT EXISTS (
SELECT * FROM people p2
WHERE p1.name = p2.name AND p1.created < p2.created
)
) AS latest_person
LEFT OUTER JOIN hobbies h ON h.person_id = latest_person.id
WHERE h.id IS NULL;
Try This:
Select *
From people p
Where timeStamp =
(Select Max(timestamp)
From people
Where name = p.Name
And not exists
(Select * From hobbies
Where person_id = p.id))