Query sql to get the first occurrence in a many to many relationship - sql

I have a User table that has a many to many relationship with Areas. This relationship is stored in the Rel_User_area table. I want to show the user name and the first area that appears in the list of areas.
Ex.
User
id | Name
1 | Peter
2 | Joe
Area
id | Name
1 | Area A
2 | Area B
3 | Area C
Rel_User_area
iduser | idarea
1 | 1
1 | 3
2 | 3
The result I want:
User Name | Area
Peter |Area A
Joe |Area C

Using the minimum area id to determine "First" you could use a correlated subquery (A subquery that refers to field(s) in the main query to filter results):
SELECT user.name, area.name
FROM
user
INNER JOIN Rel_User_Area RUA ON user.id = RUA.iduser
INNER JOIN Area ON RUA.idarea = area.id
WHERE area.id = (SELECT min(idarea) FROM Rel_User_Area WHERE iduser = RUA.iduser)
There's other ways of doing this that may be RDBMS specific. Like in Teradata I would use a QUALIFY clause that doesn't exist in MySQL, SQL Server, Oracle, Postgres, etc.. Regardless of the RDBMS the above should work.
SELECT user.name, area.name
FROM
user
INNER JOIN Rel_User_Area RUA ON user.id = RUA.iduser
INNER JOIN Area ON RUA.idarea = area.id
QUALIFY ROW_NUMBER() OVER (PARTITION BY user.id ORDER BY area.id ASC) = 1;

using the ID from Rel_user_Area you mentioned in comments...
This should be pretty platform independent.
SELECT U.name as Username, A.Name as Area
FROM (SELECT min(ID) minID, IDUser, IDarea
FROM Rel_user_Area
GROUP BY IDUser, IDarea) UA
INNER JOIN User U
on U.ID = UA.IDuser
INNER JOIN Area A
on A.ID = UA.IDArea
If Cross apply and top work (could substitute limit 1 vs top if Postgresql or mySQL)
This will run the cross apply SQL once for each record in user; thus you get the most recent rel_user_Area ID per user.
SELECT U.name as Username, A.Name as Area
FROM User U
on U.ID = UA.IDuser
CROSS APPLY (SELECT TOP 1 IDUser, IDArea
FROM Rel_user_Area z
WHERE Z.IDUSER = U.ID
ORDER BY ID ASC) UA
INNER JOIN Area A
on A.ID = UA.IDArea

Related

How to sum up max values from another table with some filtering

I have 3 tables
User Table
id
Name
1
Mike
2
Sam
Score Table
id
UserId
CourseId
Score
1
1
1
5
2
1
1
10
3
1
2
5
Course Table
id
Name
1
Course 1
2
Course 2
What I'm trying to return is rows for each user to display user id and user name along with the sum of the maximum score per course for that user
In the example tables the output I'd like to see is
Result
User_Id
User_Name
Total_Score
1
Mike
15
2
Sam
0
The SQL I've tried so far is:
select TOP(3) u.Id as User_Id, u.UserName as User_Name, SUM(maxScores) as Total_Score
from Users as u,
(select MAX(s.Score) as maxScores
from Scores as s
inner join Courses as c
on s.CourseId = c.Id
group by s.UserId, c.Id
) x
group by u.Id, u.UserName
I want to use a having clause to link the Users to Scores after the group by in the sub query but I get a exception saying:
The multi-part identifier "u.Id" could not be bound
It works if I hard code a user id in the having clause I want to add but it needs to be dynamic and I'm stuck on how to do this
What would be the correct way to structure the query?
You were close, you just needed to return s.UserId from the sub-query and correctly join the sub-query to your Users table (I've joined in reverse order to you because to me its more logical to start with the base data and then join on more details as required). Taking note of the scope of aliases i.e. aliases inside your sub-query are not available in your outer query.
select u.Id as [User_Id], u.UserName as [User_Name]
, sum(maxScore) as Total_Score
from (
select s.UserId, max(s.Score) as maxScore
from Scores as s
inner join Courses as c on s.CourseId = c.Id
group by s.UserId, c.Id
) as x
inner join Users as u on u.Id = x.UserId
group by u.Id, u.UserName;

Dynamically select the table to join in Postgres with case statements

My notifications table has a column called action_id and trigger_type. I want to INNER JOIN action_id with another table (Like users or posts) depending on the trigger_type. I wrote the following query but it throws an error.
Table structure
users
display_name
username
id
John
Doe
1
Larry
Doe
2
posts
post_title
post_body
id
user_id
Hello
Hello world
1
2
comments
comment_text
post_id
id
user_id
Hello
1
1
1
notifications
read
trigger_id
id
target_id
action_id
trigger_type
false
1
1
2
1
0
false
1
2
2
1
1
trigger_type = 0 means its a like 1 means its a comment
SELECT notifications.*, users.display_name, users.username, users.profile_pic, posts.title
FROM notifications
INNER JOIN users ON users.id = notifications.trigger_id
(
CASE notifications.trigger_type
WHEN 0 THEN INNER JOIN users ON users.id = notifications.action_id
WHEN 1 THEN INNER JOIN posts ON posts.id = notifications.trigger_id
)
You cannot conditionally join like that. Instead, use left join like this:
SELECT n.*,
-- whatever columns you want from the trigger user go here
un.display_name, un.username, un.profile_pic, p.title
FROM notifications n JOIN
users u
ON u.id = n.trigger_id LEFT JOIN
users un
ON un.id = n.action_id AND n.trigger_type = 0 LEFT JOIN
posts p
ON p.id = n.action_id AND n.trigger_type = 1;

Unify columns from different tables while selecting distinct rows

Tables
User
id
name
email
is_active
1
john
john#albert.com
FALSE
2
mike
mike#ss.com
TRUE
3
monica
monica#dunno.com
TRUE
4
joey
joey#as.com
FALSE
5
ross
ross#boss.com
FALSE
Subscriptions
id
house_id
plan name
status
1
1
A banana a month
inactive
2
2
An apple a month
active
3
3
A pear a month
active
House
id
name
1
John's House
2
Mike's House
3
Monica's House
4
Joey's House
5
Ross's House
House_Contact (legacy table)
id
house_id
is_primary
1
1
TRUE
2
2
FALSE
2
3
TRUE
House_User (new table)
id
house_id
is_owner
user_id
1
2
FALSE
2
2
4
FALSE
4
3
5
FALSE
5
Expected Results
The resulting table should include the following:
Does the user have a subscription regardless of status? If so, include, if not, disregard.
Get email & is_active from User table (if they have subscription)
Get is_primary OR is_owner (if they have a subscription)
Results should be distinct (no duplicate users)
house_id
email
is_owner
is_active
1
john#albert.com
TRUE
FALSE
2
mike#ss.com
FALSE
TRUE
3
monica#dunno.com
TRUE
TRUE
What I tried
SELECT
u.email AS "email",
u.is_active AS "is_active",
h.id AS "house_id",
is_owner
FROM
house c
INNER JOIN (
SELECT
house_id,
user_id
FROM
house_user) hu ON h.id = hu.house_id
INNER JOIN (
SELECT
id,
email,
is_active
FROM
USER) u ON hu.user_id = u.id
INNER JOIN (
SELECT
id,
email,
is_primary
FROM
house_contact) hc ON u.email = ch.email
INNER JOIN (
SELECT
house_id,
is_primary is_owner
FROM
house_contact
UNION
SELECT
house_id,
is_owner is_owner
FROM
house_user) t ON u.id = t.house_id)
ORDER BY
u.email
Results are half than if I remove the INNER JOIN with UNION statement. No idea how to proceed.
I'm particularly confused with unifying the column and the possible duplication.
My educated guess:
SELECT DISTINCT ON (u.id)
u.id, u.email, u.is_active, h.house_id, h.is_primary
FROM "user" u
LEFT JOIN (
SELECT hu.user_id, hu.house_id
, GREATEST(hc.is_primary, hu.is_owner) AS is_primary
FROM house_user hu
LEFT JOIN house_contact hc USING (house_id)
WHERE EXISTS (SELECT FROM subscription WHERE house_id = hu.house_id)
) h ON h.user_id = u.id
ORDER BY u.id, h.is_primary DESC NULLS LAST, h.house_id;
We don't need table house in the query at all.
I see three possible sources of conflict:
house_contact.is_primary vs. house_user.is_owner. Both seem to mean the same. The DB design is broken in this respect. Taking GREATEST() of both, which means true if either is true.
We don't care about subscription.status, so just make sure the house has at least one subscription of any kind with EXISTS, thereby avoiding possible duplicates a priori.
A user can live in multiple houses. We want only one row per user. So show the first house with is_primary (the one with the smallest house_id) if any. If there is no house, there is also no subscription. But the outer LEFT JOIN keeps the user in the result. Change to JOIN to skip users without subscription.
About DISTINCT ON:
Select first row in each GROUP BY group?
About sorting boolean values:
Sorting null values after all others, except special
Sort NULL values to the end of a table
You can use the joins as follows:
Select distinct hu.house_id, u.email, hu.is_owner, hc.is_primary
From user u join house_user hu on u.id = hu.user_id
Join subscriptions s on s.house_id = hu.house_id
Join house_contract hc on hc.house_id = s.house_id;
I have used distinct to remove duplicates if you have multiple data in the table for matching condition. You can remove it if not required in case it is not required.
From what I can tell, you want to start with a query like this:
select s.house_id, u.email, hu.is_owner, u.is_active
from subscriptions s left join
house_user hu
on s.house_id = hu.house_id left join
users u
on hu.user_id = u.id;
This does not return what you want, but it is rather unclear how your results are derived.

Access SQL JOIN with subquery

Can someone tell me how I can convert this query to work in MS Access?
SELECT Shooters.idShooters, Shooters.firstname, Shooters.lastname, JoinedContingent.Count, JoinedShots.Count
FROM Shooters
INNER JOIN
(SELECT Shooters.idShooters, Count(Contingent.idContingent) AS Count
FROM Shooters LEFT JOIN Contingent ON Shooters.idShooters = Contingent.fidShooter
GROUP BY Shooters.idShooters)
AS JoinedContingent ON JoinedContingent.idShooters = Shooters.idShooters
INNER JOIN
(SELECT Shooters.idShooters, Count(Shots.idShots) AS Count
FROM Shooters LEFT JOIN Shots ON Shooters.idShooters = Shots.fidShooters
GROUP BY Shooters.idShooters)
AS JoinedShots ON JoinedShots.idShooters = Shooters.idShooters;
Background information:
I would like to count the foreign key occurrences for each Shooter in the table Contingent and Shots. The result should look like this:
idShooters | firstname | lastname | Count | Count
____________________________________________________________
1 John Doe 0 10
2 Jane Doe 1 20
.
.
.
I think this is what you want:
SELECT s.idShooters, s.firstname, s.lastname,
NZ(c.Count, 0), NZ(sh.Count, 0)
FROM (Shooters as s LEFT JOIN
(SELECT c.fidShooter, Count(*) AS Count
FROM Contingent as c
GROUP BY c.fidShooter
) as c
ON s.idShooters = c.fidShooter
) LEFT JOIN
(SELECT sh.fidShooters, Count(*) AS Count
FROM Shots as sh
GROUP BY sh.fidShooters
) as sh
ON s.idShooters = sh.fidShooters;
Note that I moved the outer join to the outer query. Actually, no joins are needed in the subqueries, so don't bother.

SQL query (Join without duplicates)

I have tables users and topics. Every user can have from 0 to several topics (one-to-many relationship).
How I can get only those users which have at least one topic?
I need all columns from users (without columns from topics) and without duplicates in table users. In last column I need number of topics.
UPDATED:
Should be like this:
SELECT user.*, count(topic.id)
FROM ad
LEFT JOIN topic ON user.id = topic.ad
GROUP BY user.id
HAVING count(topic.id) > 0;
but it takes 0 result. But it should not be 0.
Firstly you need to have your two tables, because you have left limited information about your table structure I will use an example to explain how this works, you should then be able to easily apply this to your own tables.
Firstly you need to have two tables (which you do)
Table "user"
id | name
1 | Joe Bloggs
2 | Eddy Ready
Table "topic"
topicid | userid | topic
1 | 1 | Breakfast
2 | 1 | Lunch
3 | 1 | Dinner
Now asking for a count against each user is done using the follwing;
SELECT user.name, count(topic.topicid)
FROM user
INNER JOIN topic ON user.id = topic.userid
GROUP BY user.name
If you use a left join, this will include records from the "user" table which does not have any rows in the "topic" table, however if you use an INNER JOIN this will ONLY include users who have a matching value in both tables.
I.e. because the user id "2" (which we use to join) is not listed in the topic table you will not get any results for this user.
Hope that helps!
use inner join and distinct
select distinct user_table.id
from user_table
inner join topics_table on topic_table.user_id = user_table.id
select u.id
, u.name
, count(b.topicName)
from user u
left join topic t on t.userid = u.id
group by u.id, u.name
You can select topic number per user and then join it with user data. Something like this:
with t as
(
select userid, count(*) as n
from topic
group by userid
)
SELECT user.*, t.n
FROM user
JOIN t ON user.id = t.userid