SQL SUM and COUNT from different tables - sql

Tables:
UserReward:
UserRewardID PK
RewardID FK
UserID FK
UserBadge:
UserBadgeID PK
BadgeID FK
UserID FK
UserScore:
UserScoreID PK
UserID FK
LeaderboardID FK
I need to know the sum of score, the count of userbadge and the count of userReward.
I tried this but values are not right:
Select
u.username,
sum(us.score) as Soma_Score,
count(ur.userId) as Numero_de_rewards,
count(ub.userId) as Numero_de_crachas
from [user] u
join userscore us on u.userId = us.userID
join userbadge ub on ub.userid = u.userid
join userreward ur on ur.userid= u.userid
group by u.username

Have you looked at the rows before aggregating them? Your JOINs are duplicating many rows.
The best approach is to join the rows after aggregation:
with score(userid, score) as (
Select userid
, sum(us.score) as Soma_Score
from userscore us
group by userid
), rewards (userid, rewards) as (
select userid
, count(ur.userId) as Numero_de_rewards
from userreward ur
group by userid
), crachas (userid, crachas) as
select userid
, count(userId)
from userbadge
group by userid
)
select
u.userid
, score.score
, rewards.rewards
, crachas.crachas
from user u
left join score on u.userid=score.userid
left join rewards on u.userid=rewards.userid
left join crachas on u.userid=crachas.userid

Try:
SELECT
u.username,
(SELECT SUM(us.score) FROM userscore us WHERE us.userid = u.userid) as Soma_Score,
(SELECT COUNT(ur.userId) FROM userreward ur WHERE ur.userid = u.userid) as numero_de_rewards,
(SELECT COUNT(ub.userId) FROM userbadge ub WHERE ub.userid = u.userid) as numero_de_crachas
FROM [user] u

Related

Get results from multiple tables based on relationship table

I have dbo.Users tables
Id, Name
1, John
2, Mary
3, Michael
Then I have dbo.Phones table
Id, Phonenumber
10, 1234
11, 5555
Then I have dbo.Relationship table
Id, ChildId
1, 10
2, 11
How could I make a query that returns
Id, Name, Phonenumber
1, John, 1234
2, Mary, 5555
3, Michael, NULL
This is what I got so far.
SELECT u.Id, u.Name, p.Phonenumber
FROM dbo.Users as u
LEFT JOIN dbo.Phones as p
-- Something
SQL Fiddle
Think of the Relationship table as the middle-man between your Users and Phones tables here. It is a many-to-many relationship with a mapping table. Join your Users to the Relationship and then the Relationship to your Phones.
SELECT u.Id
,u.Name
,p.PhoneNumber
FROM dbo.Users u
LEFT JOIN dbo.Relationship r ON r.Id = u.Id
LEFT JOIN dbo.Phones p ON p.Id = r.ChildId
Think of it like:
Users: Hello Relationship, I have UserId = 1, what PhoneIds do I have for that UserId?
Relationship: Hi Users. I have PhoneId = 10 for you. I'll go talk to Phones to see what the number is.
Phones: Hi Relationships! I have PhoneNumber 1234 for you. It matches the PhoneId you gave me.
Join them on id field.I would use inner join depending on the requirement
SELECT distinct u.Id, u.Name, p.Phonenumber
FROM dbo.Users as u
LEFT JOIN dbo.Phones as p on u.id = p.id
or---
SELECT distinct u.Id, u.Name, p.Phonenumber
FROM dbo.Users as u
inner join T JOIN dbo.Phones as p on u.id = p.id
inner join dbo.relationship r on r.id = u.id
where----
try this :
Select u.Id, u.Name, p.Phonenumber
From
Users u
Left join Relationship r on r.Id = u.Id
Left join Phones p on r.ChildId = p.Phonenumber

SQL: Comparing MAX Dates from Two different Tables

I have 3 Tables
User
Attendence
Payment
Now I like to get
GroupID, UserName, MAX(PaymetDate), MAX(AttendenceDate)
Where MAX(PaymetDate) IS LESS THAN MAX(AttendenceDate)
This what I have Tried
SELECT MAX(PaymetDate) AS Paied_Upto
FROM Payment
Group by GroupID
SELECT MAX(AttendenceDate) AS Last_ AttendenceDate
FROM Attendence FULL OUTER JOIN Users ON Attendence.Username = Users.Username
Group by Users.GroupID
But how do get them to work together?
Thank
Try this:
SELECT u.GroupID, u.UserName, py.LastPaymentDate, at.LastAttendenceDate
FROM User AS u,
(SELECT Username, Max(AttendenceDate) AS LastAttendenceDate FROM Attendence GROUP BY Username) AS at,
(SELECT GroupID, Max(PaymetDate) AS LastPaymentDate FROM Payment GROUP BY GroupID) AS py
WHERE u.UserName=at.Username
AND u.GroupID=py.GroupID
AND py.LastPaymentDate < at.LastAttendenceDate;
try this
select p.GroupID, u.UserName, MAX(p.PaymetDate), MAX(a.AttendenceDate)
from dbo.Users u
inner join dbo.Attandence a
ON u.UserName = a.UserName
Inner join dbo.Payment p
ON u.groupID = p.GroupID
GROUP BY p.GroupID, u.UserName
Having MAX(p.PaymentDate) < MAX(a.attendenceDate)
I think this does what you need (SqlFiddle link):
select UserName, GroupID, MaxAttendanceDate, MaxPaymentDate
from (
select
u.UserName,
u.GroupID,
(select max(AttendanceDate)
from Attendance a
where a.UserName = u.UserName) as MaxAttendanceDate,
(select max(PaymentDate)
from Payment p
where p.GroupID = u.GroupId) as MaxPaymentDate
from [User] u
) x
where MaxAttendanceDate > MaxPaymentDate

t-sql query with deep joining

I'm having a little trouble of getting a fast sql-query on this. I've managed to get a query to return the result I want but it takes about 2 sek to run even with the right indexes.
I have these tables:
[Login]
loginID
loginTime
userID
[user]
userID
userName
[companyParticipant]
userID
companyID
[company]
companyID
organisationID
CompanyName
What I want to show is all the top 10 latest logins persons with loginTime. Where the user is in a company that I am a participant or a company within the organisations where I am member of a company of that organisation
To get my organisations:
SELECT organisationID
FROM companys
WHERE companyID IN (
SELECT companyID
FROM companyParticipant
WHERE userID = #userID)
GROUP BY organisationID
So what i want i a query like this:
SELECT TOP 10 userName, LoginTime
FROM ....
ORDER BY loginID
SELECT userName, loginTime
FROM
(
SELECT u.userName, l.loginTime,
rn = row_number() over (partition BY u.userName
ORDER BY l.loginTime DESC)
FROM companyParticipant cp
JOIN companys c ON c.companyID = cp.companyID
JOIN companys c2 ON c2.organisationID = c.organisationID -- same organisation
JOIN companyParticipant cp2 ON cp2.companyID = c2.companyID -- participants of same org
JOIN login l ON l.userID = cp2.userID
JOIN [user] u ON u.userID = l.userID
WHERE cp.userID = #userID
) X
WHERE rn = 1
ORDER BY loginTime DESC
This query goes under a second and is superfast. Seems strange
no need for tiebreaking either beacuase there is always people logging in and a new login must be 10 minutes apart otherwise it just updates the last login.
SELECT TOP (10) l.loginID, l.loginTime,u.userName
FROM logins AS l WITH(NOLOCK) INNER JOIN
users AS u WITH(NOLOCK) ON l.UserID = u.UserID
WHERE (l.UserID <> #userID)
AND u.userID IN(SELECT u.userID FROM companyParticipants AS sp2 WHERE sp2.companyID IN (SELECT sc2.companyID FROM company AS sc2 WHERE sc2.organisationID IN(
SELECT sc.organisationID FROM company AS sc LEFT JOIN companyParticipants AS sp ON sc.companyID = sp.companyID WHERE sp.userID = #userID AND sc.organisationID > 0 GROUP BY sc.organisationID
)))
ORDER BY l.loginID DESC

Help me with this SQL Query

3 tables are defined as follows:
Users
User_ID INT
First_Name VARCHAR
Last_Name VARCHAR
Email VARCHAR
Roles
Role_ID INT
Role_Name VARCHAR
Access_Level INT
Roles_Users
User_ID INT
Role_ID INT
Roles_Users is a many-to-many linking table between Users and Roles. I want to pull back the following info:
First_Name, Last_Name, Email, Role_Name
What I have so far is:
SELECT
U.First_Name,
U.Last_Name,
U.Email,
R.Name AS Role_Name
FROM Users U
INNER JOIN Roles_Users RU ON U.User_ID = RU.User_ID
INNER JOIN Roles R ON RU.Role_ID = R.Role_ID
The tricky part (at least for me) is that I want to only pull back the Role_Name with the MIN(Access_Level) for that particular user. So basically the record set I want to pull will have each user only listed once with their lowest access level role name.
I'm sure this is pretty simple but it's just stumping me right now.
Thanks
YOu can use a CTE (Common Table Expression) in conjunction with the ROW_NUMBER windowing function like this:
;WITH MinAccessData AS
(
SELECT
U.First_Name,
U.Last_Name,
U.Email,
R.Name AS Role_Name,
ROW_NUMBER() OVER(PARTITION BY U.User_ID ORDER BY R.Access_Level) AS RowNum
FROM Users U
INNER JOIN Roles_Users RU ON U.User_ID = RU.User_ID
INNER JOIN Roles R ON RU.Role_ID = R.Role_ID
)
SELECT *
FROM MinAccessData
WHERE RowNum = 1
The CTE "partitions" your data by User_ID, e.g. each user gets a "partition". Inside that partition, the roles are ordered by Access_level and the smallest is the first one - so it gets RowNum = 1 - for each user.
So you then select from that CTE all those entries where the RowNum = 1 - this delivers all the entries for each user which have the smallest Access_Level value.
Alternatives without a CTE (just to have another tool in your box)
SELECT
U.First_Name,
U.Last_Name,
U.Email,
R.Name AS Role_Name
FROM Users U
INNER JOIN Roles_Users RU ON U.User_ID = RU.User_ID
INNER JOIN Roles R ON RU.Role_ID = R.Role_ID
INNER JOIN (SELECT
MIN(r.Access_Level) access_level,
ru.UserID,
FROM Roles r
INNER JOIN Roles_Users ru
ON r.Role_ID = ru.Role_ID
GROUP BY UserID
) minAccess
ON ru.UserId = minAccess.UserId
and ru.
ON r.access_level = minAccess .access_level
You can also use a CROSS APPLY
SELECT
U.First_Name,
U.Last_Name,
U.Email,
R.Name AS Role_Name
FROM Users U
CROSS APPLY (SELECT TOP 1
Role_Name
FROM Roles_Users RU ON U.User_ID = RU.User_ID
INNER JOIN Roles R ON RU.Role_ID = R.Role_ID
WHERE u.user_id = ru.user_id
ORDER BY
Access_Level desc
)
Correlated subquery:
SELECT
U.First_Name,
U.Last_Name,
U.Email,
(
select top 1 R.RName from Roles_Users RU ON U.User_ID = RU.User_ID
INNER JOIN Roles R ON RU.Role_ID = R.Role_ID
ORDER BY R.Access_Level
)
AS Role_Name
FROM Users U
In my opinion using a subquery is easier to read and write. In this code the correlated subquery will execute 1x per row returned. I like #Conrad's inner join solution, easiest and probably the most performant, and probably what i would use, just giving this as another option.
Not tested, but it goes something like this
SELECT
U.First_Name,
U.Last_Name,
U.Email,
R.Role_Name
FROM Users U
JOIN Roles_Users RU ON U.User_ID = RU.User_ID
JOIN (
SELECT ROLE_ID, MIN(ROLE_NAME) ROLE_NAME
FROM ROLES
GROUP BY ROLE_ID
HAVING ACCESS_LEVEL = MIN(ACCESS_LEVEL)
) R ON RU.Role_ID = R.Role_ID
SELECT Users.*, Roles.*
FROM
Users
JOIN Roles_Users ON Users.User_ID = Roles_Users.User_ID
JOIN Roles ON Roles.Role_ID = Roles_Users.Role_ID
WHERE
Access_Level = (
SELECT MIN(Access_Level)
FROM
Roles_Users
JOIN Roles ON Roles.Role_ID = Roles_Users.Role_ID
WHERE Users.User_ID = Roles_Users.User_ID
)
NOTE: This will not list users without any role.

How do I limit a LEFT JOIN to the 1st result in SQL Server?

I have a bit of SQL that is almost doing what I want it to do. I'm working with three tables, a Users, UserPhoneNumbers and UserPhoneNumberTypes. I'm trying to get a list of users with their phone numbers for an export.
The database itself is old and has some integrity issues. My issue is that there should only ever be 1 type of each phone number in the database but thats not the case. When I run this I get multi-line results for each person if they contain, for example, two "Home" numbers.
How can I modify the SQL to take the first phone number listed and ignore the remaining numbers? I'm in SQL Server and I know about the TOP statement. But if I add 'TOP 1' to the LEFT JOIN select statement its just giving me the 1st entry in the database, not the 1st entry for each User.
This is for SQL Server 2000.
Thanks,
SELECT Users.UserID,
Users.FirstName, Users.LastName,
HomePhone, WorkPhone, FaxNumber
FROM Users
LEFT JOIN
(SELECT UserID, PhoneNumber AS HomePhone
FROM UserPhoneNumbers LEFT JOIN UserPhoneNumberTypes ON UserPhoneNumbers.UserPhoneNumberTypeID=UserPhoneNumberTypes.UserPhoneNumberTypeID
WHERE UserPhoneNumberTypes.PhoneNumberType='Home') AS tmpHomePhone
ON tmpHomePhone.UserID = Users.UserID
LEFT JOIN
(SELECT UserID, PhoneNumber AS WorkPhone
FROM UserPhoneNumbers LEFT JOIN UserPhoneNumberTypes ON UserPhoneNumbers.UserPhoneNumberTypeID=UserPhoneNumberTypes.UserPhoneNumberTypeID
WHERE UserPhoneNumberTypes.PhoneNumberType='Work') AS tmpWorkPhone
ON tmpWorkPhone.UserID = Users.UserID
LEFT JOIN
(SELECT UserID, PhoneNumber AS FaxNumber
FROM UserPhoneNumbers LEFT JOIN UserPhoneNumberTypes ON UserPhoneNumbers.UserPhoneNumberTypeID=UserPhoneNumberTypes.UserPhoneNumberTypeID
WHERE UserPhoneNumberTypes.PhoneNumberType='Fax') AS tmpFaxNumber
ON tmpFaxNumber.UserID = Users.UserID
Whenever you want to select only a top row from a left table for each row in the right table you should consider using the APPLY operator instead of join, and move the join condition inside the left join:
SELECT u.UserID,
u.FirstName, u.LastName,
hn.PhoneNumber AS HomePhone
FROM Users u
OUTER APPLY (
SELECT TOP(1) PhoneNumber
FROM UserPhoneNumbers upn
LEFT JOIN UserPhoneNumberTypes upt
ON upn.UserPhoneNumberTypeID=upt.UserPhoneNumberTypeID
WHERE upt.PhoneNumberType='Home'
AND upn.UserID = u.UserID
ORDER BY ...) as hn
...
Assuming SQL Server 2005+, use ROW_NUMBER:
LEFT JOIN (SELECT UserID,
PhoneNumber AS HomePhone,
ROW_NUMBER() OVER (PARTITION BY userid ORDER BY what?) AS rank
FROM UserPhoneNumbers upn
LEFT JOIN UserPhoneNumberTypes upnt ON upnt.UserPhoneNumberTypeID = upn.UserPhoneNumberTypeID
AND upnt.PhoneNumberType='Home') AS tmpHomePhone
ON tmpHomePhone.UserID = Users.UserID
AND tmpHomePhone.rank = 1
Mind the what? placeholder for determining the first number. Omit the ORDER BY if you don't care at all...
Since it's SQL Server 2000 and ranking functions are out, you could make your subquery SELECTs aggregate:
SELECT UserID, MAX(PhoneNumber) AS HomePhone FROM [...] GROUP BY UserID
iff you don't care WHICH of a user's Home numbers are returned...
Hold on, just to understand the question.
You've got two tables:
Users (UserID --> x) UserPhones (UserID, PHoneType --> Phone Number)
and UserID / PhoneType isn't unique.
First off there's no need for temp tables:
Select
x
from
Users
inner join
(
Select
top 1 y
from
FoneTypes
where
UserID = users.UseriD
and phoneType = 'typex'
) as PhoneTypex on phonetypex.UserID = users.userID
Add inner joins as necessary.
Or am I missing something?
Select Users.UserID, Users.FirstName, Users.LastName
, PhoneNumbers.HomePhone
, PhoneNumbers.WorkPhone
, PhoneNumbers.FaxNumber
From Users
Left Join (
Select UPN.UserId
, Min ( Case When PN.PhoneNumberType = 'Home' Then UPN.PhoneNumber End ) As HomePhone
, Min ( Case When PN.PhoneNumberType = 'Work' Then UPN.PhoneNumber End ) As WorkPhone
, Min ( Case When PN.PhoneNumberType = 'Fax' Then UPN.PhoneNumber End ) As FaxPhone
From UserPhoneNumbers As UPN
Join (
Select Min(UPN1.UserPhoneNumberId) As MinUserPhoneNumberId
, UPNT1.PhoneNumberType
From UserPhoneNumbers As UPN1
Join UserPhoneNumberTypes As UPNT1
On UPNT1.UserPhoneNumberTypeID = UPN1.UserPhoneNumberTypeID
Where UPNT1.PhoneNumberType In('Home', 'Work', 'Fax')
Group By UPN1.UserID, UPNT.PhoneNumberType
) As PN
On PN.MinUserPhoneNumberId = UPN.UserPhoneNumberId
Group By UPN.UserId
) As PhoneNumbers
On PhoneNumbers.UserId = Users.UserId
In this solution, for each user and phone number type, I'm picking the lowest primary key value from the UserPhoneNumbers table (I guessed that the column was named UserPhoneNumberId).
I assume you have some primary key field on each joined table, since UserID is not unique. I'll assume your primary key is called ID. We'll take the records with the lowest ID. This meets your "first" criteria.
SELECT Users.UserID, Users.FirstName, Users.LastName, hp.HomePhone,
wp.WorkPhone, fn.FaxNumber
FROM Users
LEFT JOIN HomePhone hp ON hp.UserID = Users.UserID
LEFT JOIN HomePhone hp2 ON hp2.UserID = Users.UserID AND hp2.ID < hp.ID
LEFT JOIN WorkPhone wp ON wp.UserID = Users.UserID
LEFT JOIN WorkPhone wp2 ON wp2.UserID = Users.UserID AND wp2.ID < wp.ID
LEFT JOIN FaxNumber fn ON fn.UserID = Users.UserID
LEFT JOIN FaxNumber fn2 ON fn2.UserID = Users.UserID AND fn2.ID < fn.ID
WHERE hp2.ID IS NULL AND wp2.ID IS NULL AND fn2.ID IS NULL
There is a whole chapter on this type of issue, called "Ambiguous Gruops", in the book SQL Antipatterns.
You have to define what you mean by "first" when there are two numbers of the same type, and then add a condition to your join so that only the correct record meets the criteria. There's no other shortcut for this.
You could just use GROUP BY:
SELECT Users.UserID,
Users.FirstName, Users.LastName,
HomePhone, WorkPhone, FaxNumber
FROM Users
LEFT JOIN
(SELECT UserID, min(PhoneNumber) AS HomePhone
FROM UserPhoneNumbers LEFT JOIN UserPhoneNumberTypes ON UserPhoneNumbers.UserPhoneNumberTypeID=UserPhoneNumberTypes.UserPhoneNumberTypeID
WHERE UserPhoneNumberTypes.PhoneNumberType='Home'
GROUP BY userID) AS tmpHomePhone
ON tmpHomePhone.UserID = Users.UserID
LEFT JOIN
(SELECT UserID, min(PhoneNumber) AS WorkPhone
FROM UserPhoneNumbers LEFT JOIN UserPhoneNumberTypes ON UserPhoneNumbers.UserPhoneNumberTypeID=UserPhoneNumberTypes.UserPhoneNumberTypeID
WHERE UserPhoneNumberTypes.PhoneNumberType='Work'
GROUP BY userID) AS tmpWorkPhone
ON tmpWorkPhone.UserID = Users.UserID
LEFT JOIN
(SELECT UserID, min(PhoneNumber) AS FaxNumber
FROM UserPhoneNumbers LEFT JOIN UserPhoneNumberTypes ON UserPhoneNumbers.UserPhoneNumberTypeID=UserPhoneNumberTypes.UserPhoneNumberTypeID
WHERE UserPhoneNumberTypes.PhoneNumberType='Fax'
GROUP BY userID) AS tmpFaxNumber
ON tmpFaxNumber.UserID = Users.UserID
Instead of min(), you could use max() as well.
Or you could do it in one group by:
SELECT Users.UserID,
Users.FirstName, Users.LastName,
max(HomePhone) as HomePhone,
max(WorkPhone) as WorkPhone,
max(FaxNumber) as FaxNumber
FROM Users
LEFT JOIN
(SELECT UserID, PhoneNumber AS HomePhone
FROM UserPhoneNumbers LEFT JOIN UserPhoneNumberTypes ON UserPhoneNumbers.UserPhoneNumberTypeID=UserPhoneNumberTypes.UserPhoneNumberTypeID
WHERE UserPhoneNumberTypes.PhoneNumberType='Home') AS tmpHomePhone
ON tmpHomePhone.UserID = Users.UserID
LEFT JOIN
(SELECT UserID, PhoneNumber AS WorkPhone
FROM UserPhoneNumbers LEFT JOIN UserPhoneNumberTypes ON UserPhoneNumbers.UserPhoneNumberTypeID=UserPhoneNumberTypes.UserPhoneNumberTypeID
WHERE UserPhoneNumberTypes.PhoneNumberType='Work') AS tmpWorkPhone
ON tmpWorkPhone.UserID = Users.UserID
LEFT JOIN
(SELECT UserID, PhoneNumber AS FaxNumber
FROM UserPhoneNumbers LEFT JOIN UserPhoneNumberTypes ON UserPhoneNumbers.UserPhoneNumberTypeID=UserPhoneNumberTypes.UserPhoneNumberTypeID
WHERE UserPhoneNumberTypes.PhoneNumberType='Fax') AS tmpFaxNumber
ON tmpFaxNumber.UserID = Users.UserID