SQL Self Join issues - sql

I've got 2 tables that I need to get data from...my users table looks like this:
Users Table
-------------
UserID
FirstName
LastName
WebLogin
WebPassword
Active
UserAlternates Table
---------------------
UserAlternateID
UserID
AlternateUserID
Users Table Data
------------------
1, John, Brown, jbrown, jbrown, true
2, Mark, Smith, msmith, msmith, true
3, Tim, Stone, tstone, tstone, true
UsersAlternate Table Data
--------------------------
1, 1, 2
2, 1, 3
3, 2, 1
4, 3, 2
5, 3, 1
The UserID refers back to the UserID in the Users table and so does the AlternateUserID. This is a case where our program can have users that are "alternates" to other users. So in the above example, if John Brown would have Mark & Tim as Alternates, and Mark would have John as an alternate while Time would have Mark and John as alternates. I'm drawing a blank on how to write the SQL to show the alternate users for a given userid. So if I passed in UserID = 1, it would return:
2, Mark, Smith
3, Tim, Stone
I tried this but it returns 2 rows of the same user data (in this case, 2 John Brown's):
CREATE PROCEDURE [dbo].[GetUserAlternates]
#UserID int
AS
SELECT u.FirstName, u.LastName, ua.AlternateUserID
FROM Users u
INNER JOIN UserAlternates ua ON u.UserID = ua.AlternateUserID
WHERE u.UserID = #UserID
Any ideas?

CREATE PROCEDURE dbo.GetUserAlternates
#UserID INT
AS
BEGIN
SET NOCOUNT ON;
SELECT u.FirstName, u.LastName, u.UserID
FROM dbo.Users AS u
INNER JOIN dbo.UserAlternates AS au
ON u.UserID = ua.AlternateUserID
WHERE ua.UserID = #UserID; -- key difference here!
END
GO

How about something like
SELECT u.*
FROM UserAlternates ua INNER JOIN
Users u ON ua.AlternateUserID = u.UserID
WHERE ua.UserID = #UserID
It does not seem from your request that you need to join to the Users table twice, as the UserAlternates table already contains the original UserID.

You've got the wrong table alias:
WHERE ua.UserID = #UserID
Note: ua not u.

SELECT ua.AlternateUserID, U2.FirstName, U2.Lastname
FROM Users u
INNER JOIN UserAlternates ua ON u.UserID = ua.UserID
INNER JOIN Users U2 on U2.UserID = UA.AlternateUserID
WHERE u.UserID = #UserID

Related

SQL Get records with max value for each group

I have 2 tables Journal and Users
Journal looks like this:
TransTime
RegNumber
UserID
5/26/2022 11:00:00
101
3
5/26/2022 11:30:00
102
2
5/26/2022 13:00:00
101
5
5/26/2022 14:30:00
103
4
5/26/2022 15:00:00
102
1
Users table
UserID
Name
1
Ross
2
Rachel
3
Chandler
4
Monica
5
Joey
What I would like to do is get a table of the Registers and their most recent user names. This should seem very simple. But since I am joining tables on the userID, I am getting all 5 records on the first table. But it should look like this:
RegNumber
LastUser
101
Joey
102
Ross
103
Monica
I have tried a variety of solutions but haven't found the right one. Any help is appreciated.
You can use a temptable or cte structure to rank your data based on RegNo and Trantime like below, then retrieve the most updated users for each journal:
CREATE TABLE #Journals (TranTime DATETIME, RegNo INT, UserId INT)
CREATE TABLE #Users (UserId INT, UserName NVARCHAR(100))
INSERT INTO #Users VALUES(1,'Ross'),(2,'Rachel'),(3,'Chandler'),(4,'Monica'),(5,'Joey')
INSERT INTO #Journals VALUES ('5/26/2022 11:00:00',101,3),('5/26/2022 11:30:00',102,2),
('5/26/2022 13:00:00',101,5),('5/26/2022 14:00:00',103,4),('5/26/2022 15:00:00',102,1)
;WITH cte as (
SELECT *,rn=ROW_NUMBER() OVER (PARTITION BY RegNo ORDER BY TranTime DESC)
FROM #Journals
)
SELECT RegNo, u.UserName
FROM cte
INNER JOIN #Users u ON u.UserId = cte.UserId
WHERE rn=1 --since sort by TranTime is descending, it'll give you the latest user for each specific RegNo
ORDER BY RegNo
Tested and it works on SQL Server 2016.
if you start with an inner join of the max transtime per regNumber, then join with user table:
Select J.RegNumber, U.Name
From Journal J
Inner join
(Select Max(TransTime) as TransTime, RegNumber
From Journal
Group by RegNumber) J2 on J.TransTime = J2.TransTime and J.RegNumber = J2.RegNumber
Inner join
Users U on J.UserID = U.UserID
Here is an option using a CTE:
;with cte as
(
Select RegNumber,
UserID = max(UserID)
From journal
group by RegNumber
)
Select RegNumber = C.RegNumber,
LastUser = U.Name
From cte C
Join users U ON U.Userid = C.UserID
order by C.RegNumber
This answer is not, at its core, substantively different from the others. However, in terms of being helpful to the target audience it's more readable, more self-documenting, and more standard in terms of formatting.
Sidebar: This SQL takes the data design at face value, as a given, with the implicit assumption that TransTime is the PK or at least uniquely indexed, possibly in conjunction with RegNumber. Bottom line, it would be good to have a little more info about the key structure along with the original question.
WITH LatestEntries AS
(
SELECT
MAX(TransTime) AS LatestTimeForReg
,RegNumber
FROM
Journal
GROUP BY
RegNumber
)
SELECT
J.RegNumber
,U.[Name] AS LastUser
FROM
LatestEntries LE
INNER JOIN Journal J ON LE.LatestTimeForReg = J.TransTime AND LE.RegNumber = J.RegNumber
INNER JOIN Users U ON J.UserID = U.UserID
ORDER BY
J.RegNumber
;
select u.Name, j.*
from journal j
inner join (
select max(TransTime) last_update, RegNumber
from journal
group by RegNumber
) t1
inner join j.RegNumber = t1.RegNumber
and t1.last_update = j.TransTime
left join Users_Journal uj on j.UserID= uj.UserID

SQL JOIN Returning no values

I am working with 3 tables with the following Names and columns:
*Table 1*
**Users**
UserID UserName UserTypeNumber
1 John 1N
2 Mary 1N
3 Doe 1N
4 Sullivan 2N
5 Sally 1N
**Key = UserID**
*Table 2*
**MobileUsers**
Userid MobileAccess
1 Yes
2 Yes
3 Yes
4 Yes
5 No
**Key = UserID**
*Table 3*
**PanCards**
UserID CARD_NUMBER
3 2222
4 3333
5 1111
Key = UserID
Explanation:
Every user is available in both Users and MobileUsers table
However, not all users are present in PanCards table. This is
because only users that have been assigned a Card appears in the
PanCards table. In this case Doe with UserID=3 and Sullivan with UserID=4 have a card, hence they appear in PanCards table
Problem:
What I want to achieve is to filter out the users from
MobileUsers table that have MobileAccess equivalent to Yes and thier UserTypeNumber equivalent to 1N in the Users table but are not present in the PanCards table.
What I have so far is the below SQL Query:
SELECT MobileUsers.Userid, MobileUsers.MobileAccess
FROM MobileUsers
INNER JOIN Users
ON MobileUsers.Userid = Users.UserID
INNER JOIN PanCards
ON Users.UserID = PanCards.UserID
WHERE MobileUsers.MobileAccess = 'Yes'
AND
Users.UserTypeID = '1N'
AND
MobileUsers.Userid NOT IN
(SELECT PanCards.UserID FROM PanCards)
Result is an Empty table
Userid MobileAccess
However what i want is to have the below result returned:
Userid MobileAccess
1 Yes
2 Yes
How do i fix this and get the correct results please ?
If you want to use JOIN, then use a LEFT JOIN for PanCards and check for no matches:
SELECT mu.*
FROM MobileUsers mu JOIN
Users u
ON mu.Userid = u.UserID LEFT JOIN
PanCards pc
ON pc.UserID = mu.UserID
WHERE mu.MobileAccess = 'Yes' AND
u.UserTypeID = '1N' AND
pc.UserID IS NULL;
I think your query would work without the join to PanCards.
WITH NotPan -- First get users that are not in the PanCards table
AS
(
select * from Users as u
where u.userid not in (select userid from PanCards)
)
-- Easy from here
select * from NotPan as n
inner join mobileUsers m on m.userid = n.userid
inner join Users as u on n.userid = u.userid
WHERE m.MobileAccess = 'Yes' and u.UserTypeNumber = '1N'

Query that joins two table where match exists and removes result if ID exists in another table

This query gets the the ID, First Name, Date of Birth by joining two tables together where a mutual match exists
Example of mutual match:
Amy likes Mary
Mary likes Amy
SELECT u.ID, u.firstname, u.dob, i.[Image]
FROM [dbo].[User] AS u
INNER JOIN [dbo].[Images] AS i ON u.ID = i.Id
WHERE u.ID IN (
SELECT userB FROM [dbo].[LikesRefined]
WHERE userA = #ID OR userB = #ID
UNION
SELECT userB FROM [dbo].[LikesRefined]
WHERE userA = #ID OR userB = #ID
);
I want to filter this result by using except clause on another table but I keep getting this error
All queries combined using a UNION, INTERSECT or EXCEPT operator must have an equal number of expressions in their target lists.
except
(select paidA
from [dbo].[Matches]
WHERE paidA = #ID
AND (userA = #ID or userB = #ID))
end
I'm still confused, but I think this may do the trick:
SELECT u.ID ,
u.firstname ,
u.dob ,
i.[Image]
FROM [dbo].[User] AS u
INNER JOIN [dbo].[Images] AS i ON u.ID = i.Id
INNER JOIN dbo.[LikesRefined] lr ON (u.ID = lr.userA OR u.ID = lr.userB)
LEFT OUTER JOIN dbo.Matches m ON u.ID = m.paidA
AND ( m.userA = u.ID OR m.userb = u.ID )
WHERE (lr.userA = #ID OR lr.userB = #ID)
AND m.paidA IS NULL
We get rid of the union operator and just check for the ID in either userA or userB in the LikeRefined table. Then we left join to dbo.Matches and just look for records where there is no match.
When using union operator all select statements must have the same number of columns

SQL Statement that Subs 0 for no Results

I am having an issue getting the results I want from my SQL statement. I know I'm probably missing something simple but I just can't see it.
Here are my tables.
Table: Users (RoleID is linked to ID in Roles Table)
ID, FirstName, LastName, RoleID
1, Matt, Ryan, 1
2, Chipper, Jones, 1
3, Julio, Jones, 2
4, Jason, Bourn, 3
Table: Roles
ID, Name
1, Field Rep
2, Tech
3, Admin
Table: FRrequests (UserID is linked to ID in Users table)
ID, UserID, Status
1, 1, Open
2, 1, Submitted
3, 1, Delayed
4, 1, Complete
What I want is an SQL statement that shows me a count of all the "Submitted" & "Delayed" requests for all the Field Reps. Below is an example of desired results.
Name Count
Chipper Jones 0
Matt Ryan 2
Here is the statement I have so far and the results it gives me.
SELECT Users.FirstName + ' ' + Users.LastName AS Name, COUNT(FRrequests.ID) AS 'Open Requests'
FROM Users INNER JOIN
Roles ON Users.RoleID = Roles.ID LEFT OUTER JOIN
FRrequests ON Users.ID = FRrequests.UserID
WHERE (Roles.Name = N'Field Rep') AND (FRrequests.Status = 'Submitted' OR FRrequests.Status = 'Delayed')
GROUP BY Users.FirstName, Users.LastName
Name Count
Matt Ryan 2
I know that the "AND (FRrequests.Status = 'Submitted' OR FRrequests.Status = 'Delayed')" part is what is breaking it. If I run it without that in the statement I get all the users but it counts all status not just submitted and delayed. I just can't figure out what I'm missing to get this to work. Any help would be greatly appreciated.
You are really close, try the following:
SELECT U.FirstName + ' ' + U.LastName AS Name, COUNT(F.ID) AS 'Open Requests'
FROM Users U
INNER JOIN Roles R
ON U.RoleID = R.ID
LEFT JOIN ( SELECT * FROM FRrequests
WHERE Status IN ('Submitted','Delayed')) F
ON U.ID = F.UserID
WHERE R.Name = N'Field Rep'
GROUP BY U.FirstName, U.LastName
You need to have a row for each Field Rep if you wish to include Field Reps that have no requests. Therefore you need to use a left outer join along the following lines:
SELECT
u.FirstName || ' ' || u.LastName as Name
, COALESCE(frr.Counter,0) as Count
FROM
Users u
LEFT OUTER JOIN (
SELECT
UserID
,count(*) as Counter
FROM
FRrequests
WHERE
Status IN ('Submitted', 'Delayed')
GROUP BY
UserID
) frr ON frr.UserID = u.ID
, Roles r
WHERE
u.ID = f.UserID
AND u.RoleID = r.ID
AND r.Name = 'Field Rep'
;
A left outer join will provide a join only where a match occurs, so it does not remove rows from the naturally joined set. In this case you will get a list of all Users who are Field Reps, together with a count of their FRrequests with a 'Submitted' or 'Delayed' status if any.
COALESCE is used (in Postgresql at least) to check for a null value and then coerce it to the supplied value, in this case 0. So if a NULL value occurs (because, for instance, there was no 'Counter' value returned from the left outer join) it is replaced by 0.

What is the better way of writing this query?

I have a simple join query as follows.
select *
from user u left join
user_roles ur
on ur.user_id = u.user_id
and ur.created_by = 'Mike'
where u.division = 'some division'
OR
select *
from user u left join
user_roles ur
on ur.user_id = u.user_id
where u.division = 'some division'
and ur.created_by = 'Mike'
The point is here is I have moved the additional filter clause condition from left join to where clause.
Does it make any difference if I join two tables on more than column or put it in where clause?
Yes it makes a big difference.
You are basicalling nullifying the left join and making it an inner join, hiding user roles not created by Mike
Bell Mike
Toe Mike
Bob Jerry
first query returns
Bell Mike
Toe Mike
Bob NULL
second Query returns
Bell Mike
Toe Mike
Yes - it makes an important difference
Any filter on the joined table should be in the join for it to work correctly - ie: the former will work, the second won't.
Try the following (SQL Server syntax) to see how the results differ
declare #u table (user_id int, division varchar(20))
declare #ur table (user_id int, created_by varchar(10))
insert #u values (1,'sales'), (2,'marketing'), (3,'engineering')
insert #ur values (1, 'mike'), (3,'james'), (3,'mike')
select * from #u u
left join #ur ur on ur.user_id = u.user_id and ur.created_by = 'Mike'
select * from #u u
left join #ur ur on ur.user_id = u.user_id
where ur.created_by = 'Mike'