t-sql query with deep joining - sql

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

Related

SQL SUM and COUNT from different tables

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

How to Join only first row, disregard further matches

I have 2 tables
Table Users:
UserID | Name
Table Cars:
CarID | Car Name | FK_UserID
A user can have more than 1 car.
I want to join each user with 1 car only, not more.
Having looked at other threads here,
I've tried the following:
Select users.UserID, users.name, carid
from Users
join cars
on users.UserID =
(
select top 1 UserID
from users
where UserID = CarID
)
But it still returns more than 1 match for each user.
What am I doing wrong?
You can try like below using ROW_NUMBER() function
select userid, username, carname
from
(
Select users.UserID as userid,
users.name as username,
cars.carname as carname,
ROW_NUMBER() OVER(PARTITION BY users.UserID ORDER BY users.UserID) AS r
from Users
join cars
on users.UserID = cars.FK_UserID
) XXX
where r = 1;
with x as
(select row_number() over(partition by userid order by carid) as rn,
* from cars)
select u.userid, x.carid, x.carname
from users u join x on x.userid = u.userid
where x.rn = 1;
This is one way to do it using row_number function.
Another way to do it
select u.UserID,
u.name,
(select TOP 1 carid
from cars c
where u.UserID = c.FK_UserID
order by carid) carid -- Could be ordered by anything
from Users u
-- where only required if you only want users with cars
where exists (select * from car c where u.UserID = c.FK_UserID)
Best would be to do a subquery and use a group-by in it to return only 1 user and a car for each user. Then join that to the outer user table.
Here is an example:
select *
from user_table u
join (
select userid
, max(carname)
from cars
group by userid
) x on x.userId = u.userId
or you could use the row_number() examples above if you want a specific order (either this example or theirs will do the trick)

Left join without multiple rows from right table

I have two tables (User and Salary). I want to do a left join from User to Salary. For each user I want their name and salary. In case they have no salary that field can be left empty. So far a left join is all we need. But I only want one row per user. Due to some defects there can be several salaries for one user (see table salary). I only want one row per user which can be selected randomly (or top 1). How do I do that? The expected output is presented in the bottom.
User Table:
User Name
1 Adam
2 Al
3 Fred
Salary Table
User Salary
1 1000
2 2000
2 2000
Expected table:
User Name Salary
1 Adam 1000
2 Al 2000
3 Fred null
Changed User to Userid as User is a reserved word in SQL
SELECT u.Userid, u.Name, MAX(S.Salary)
FROM Usertable u
LEFT JOIN Salarytable s ON u.Userid = s.userid
GROUP BY u.userid, u.name
SQL Fiddle: http://sqlfiddle.com/#!6/ce4a8/1/0
Try this:
select U.User, U.Name, min(S.Salary)
from UserTable U
left join SalaryTable S on S.User = U.User
group by U.User, U.Name
You can utilize a ROW_NUMBER to get the max (or min) salary:
SELECT *
FROM Usertable u
LEFT JOIN
(
select Userid, Salary,
row_number()
over (partition by Userid
order by Salary desc) as rn
from Salarytable
) as s
ON u.Userid = s.userid
AND rn = 1
And in Teradata you could apply the rn = 1filter using QUALIFY within the Derived Table:
SELECT *
FROM Usertable u
LEFT JOIN
(
select Userid, Salary,
row_number()
over (partition by Userid
order by Salary desc) as rn
from Salarytable
qualify rn = 1
) as s
ON u.Userid = s.userid
Use a derived table to get distinct rows from salaries table.
select u.userid, u.username, s.salary
from users u left join (select distinct userid, salary from salaries) s
on u.userid = s.userid
Also, renamed tables and columns. Table names should normally end with s (since pluralis.) Columns should not.
Or, do a GROUP BY:
select u.userid, u.username, max(s.salary)
from users u left join salaries s
on u.userid = s.userid
group by u.userid, u.username
Or skip the left join, instead do a correlated sub-query:
select u.userid, u.username, (select max(s.salary) from salaries s
where u.userid = s.userid)
from users
Try this
select distinct U.User, U.Name, S.Salary
from UserTable U
left join SalaryTable S on S.User = U.User

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

How can I take the first match from table B to table A when there's multiple matches?

If I have a User table, like this:
UserId Name
------ ----
1 Jim
2 Mark
and an Order table like this:
OrderId UserId
------- ------
1 1
2 1
3 1
4 2
How can I just take any single Order of Jim's? It doesn't matter which, I just want one.
I have this:
Joining isn't the issue, it's just limiting it to one result as there are obviously 3.
SELECT * FROM User u
LEFT OUTER JOIN Order o on u.UserId = o.UserId
Thanks in advance.
This will select the first matched row:
SELECT TOP 1 * FROM User u
LEFT OUTER JOIN Order o ON u.UserID = o.UserID
If you know which user you're looking for ahead of time, you can add that to the WHERE clause:
WHERE u.UserID = #userID
And, if you ever need to make sure you're getting the First, or Most Recent order, you can use :
ORDER BY o.OrderID ASC/DESC
Select u.userid, name, min (orderID)
FROM User u
LEFT JOIN Order o on u.UserId = o.UserId
group by u.userid, name
To filter just for Jim
Select min (orderID)
FROM User u
LEFT JOIN Order o on u.UserId = o.UserId
where name='Jim'
the following query will give you one order per user (the lowest order number of each user)
select name, order_id
from users u,
orders o
where u.user_id = o.user_id
and o.order_id = (select min(order_id) from orders c where c.user_id=o.user_id)
select u.userid,
u.name,
o.orderid
from users u
join (select orderid,
userid,
row_number() over (partition by userid order by orderid) as rn
from orders) o
on o.userid = u.userid and o.rn = 1;
You can control which orders is being taken by tweaking the order by part of the window definition.