SQL Statement to Join Users and Last Login Date,Location - sql

I have two tables, a list of Users, and a UserLoginHistory table that has their history of login dates, IP Addresses, and Geolocations.
I want to create one SQL statement that will return one each User + their last LoginDate and Geolocation.
Users.UserId, Users.Name
100 Bill
101 Steve
UserLoginHx.UserId, UserLoginHx.LoginDate, UserLoginLocation
100 1/1/2018 New York
101 1/1/2018 Los Angeles
100 1/4/2018 Chicago
101 1/5/2018 Denver
....
Result desired in this example should return two rows as:
100 Bill 1/4/2018 Chicago
101 Steve 1/5/2018 Denver
Thanks. (so far nobody got close)

Try below query:
select UserLoginHx.UserId,users.name,a.logindate,a.location from UserLoginHx
inner join
(select UserLoginHx.UserId,max(UserLoginHx.LoginDate) as logindate
from UserLoginHx
group by UserLoginHx.UserId)a on a.UserId=UserLoginHx.UserId and a.logindate=UserLoginHx.LoginDate
inner join Users on Users.UserId=UserLoginHx.UserId

SELECT u.UserId, u. Name, ulh.LoginDate, ulh.UserLoginLocation
FROM Users u
JOIN UserLoginHistory ulh ON u.UserId = ulh.UserId

A JOIN clause is used to combine rows from two or more tables, based on a related column between them.
so for you need join two tables
SELECT u.*, uh.LoginDate, uh.UserLoginLocation
FROM Users u
JOIN UserLoginHistory uh ON u.UserId = uh.UserId

Try the following query-:
with cte as
(
select a.*,LoginDate,UserLoginLocation,
ROW_NUMBER() over (partition by a.UserId order by Login desc) rn
from Users a
join UserLoginHistory b
on a.UserId=b.UserId
)select * from cte where rn=1
SQL Server

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 query, want to get the latest record of joining table (or null if it doesn't match)

I have 2 tables
User:
ID
NAME
1
John
2
Jane
3
Jim
login:
id
date
1
2021-01-29
3
2021-02-27
1
2021-03-11
3
2021-04-18
I want to get the result like:
name
date
John
2021-03-11
Jane
null
Jim
2021-04-18
How shall I write the SQL query?
I tried quite a few join but never got the 2nd record (Jane/Null) out from the query. Need some help here, thanks a ton in advance!
You can use left join and group by:
select u.id, u.name, max(l.date)
from user u left join
login l
on l.id = u.id
group by u.id, u.name;
Note: This includes the id as well. If name is known to be unique that is not necessary.
complimenting #gordons answer another option is to use a subquery like follows
SELECT user.name, login.last_login
FROM user
LEFT JOIN (SELECT id, max(date) AS last_login FROM logins group by id) AS login
ON login.id = user.id

SQL Query: Using AND/OR in the WHERE clause

I'm currently working on a project where I have a list of dental patients, and I'm supposed to be displaying all the patients who have two specific procedure codes attached to their profiles. These patients must have BOTH procedure codes, not one or the other. At first I thought I could accomplish this by using a basic AND statement in my WHERE clause, like so.
SELECT [stuff]
FROM [this table]
WHERE ProcedureCode = 'ABC123' AND ProcedureCode = 'DEF456';
The query of course returns nothing because the codes are entered individually and you can't have both Procedure 1 and Procedure 2 simultaneously.
I tried switching the "AND" to "OR" just out of curiosity. Of course I'm now getting results for patients who only have one code or the other, but the patients who have both are showing up too, and the codes are displayed as separate results, like so:
Patient ID Last Name First Name Code Visit Date
1111111 Doe Jane ABC123 11-21-2015
5555555 Smith John ABC123 12-08-2015
5555555 Smith John DEF456 12-08-2015
My SQL is pretty rusty these days. I'm trying to think of a way to filter out patients like Jane Doe and only include patients like John Smith who have both procedure codes. Ideas?
ADDING INFO BASED ON CHRISTIAN'S ANSWER:
This is what the updated query looks like:
SELECT PatientID, LastName, FirstName, Code, VisitDate
FROM VisitInfo
WHERE PatientID IN
(
SELECT PatientID
FROM VisitInfo
WHERE Code = 'ABC123' OR Code = 'DEF456'
GROUP BY PatientID
HAVING COUNT(*) > 1
)
AND (Code = 'ABC123' OR Code = 'DEF456');
So I'm still getting results like the following where a patient is only showing one procedure code but possibly multiple instances of it:
Patient ID Last Name First Name Code Visit Date
1111111 Doe Jane ABC123 11-02-2015
1111111 Doe Jane ABC123 11-21-2015
5555555 Smith John ABC123 12-08-2015
5555555 Smith John DEF456 12-08-2015
5555555 Smith John ABC123 12-14-2015
9999999 Jones Mike DEF456 11-22-2015
9999999 Jones Mike DEF456 12-06-2015
Even though Jane Doe and Mike Jones have 2 results, they're both the same code, so we don't want to include them. And even though John Smith still has 2 of the same code, his results also include both codes, so we want to keep him and other patients like him.
ANOTHER UPDATE:
I just learned that I now need to include a few basic demographic details for the patients in question, so I've joined my VisitInfo table with a PatientInfo table. The updated query looks like this:
SELECT v.PatientID, v.LastName, v.FirstName, v.Code, v.VisitDate, p.DateOfBirth, p.Gender, p.PrimaryPhone
FROM VisitInfo v JOIN PatientInfo p ON v.PatientID = p.PatientID
WHERE v.PatientID IN
(
SELECT PatientID
FROM VisitInfo
WHERE Code = 'ABC123' OR Code = 'DEF456'
GROUP BY PatientID
HAVING COUNT(*) > 1
)
AND (Code = 'ABC123' OR Code = 'DEF456');
I wasn't sure if the new JOIN would affect anyone's answers...
SELECT *
FROM TABLE
WHERE Patient_ID IN
(
SELECT Patient_ID
FROM TABLE
WHERE Code = 'ABC123' OR Code = 'DEF456'
GROUP BY Patient_ID
HAVING COUNT(*) = 2
)
AND (Code = 'ABC123' OR Code = 'DEF456')
UPDATE 1:
As a patient can have multiple ´procedure codes´, this way will work better:
SELECT *
FROM TABLE T1
WHERE EXISTS (SELECT 1
FROM TABLE T2
WHERE T1.Patient_ID = T2.Patient_ID
AND T2.Code = 'ABC123')
AND EXISTS (SELECT 1
FROM TABLE T2
WHERE T1.Patient_ID = T2.Patient_ID
AND T2.Code = 'DEF456')
AND T1.Code IN ('ABC123','DEF456')
There's a bunch of ways to skin this particular cat - here's another one:
WITH ABC123 AS (SELECT DISTINCT PATIENTID
FROM VISITINFO
WHERE PROCEDURECODE = 'ABC123'),
DEF456 AS (SELECT DISTINCT PATIENTID
FROM VISITINFO
WHERE PROCEDURECODE = 'DEF456')
SELECT v.PATIENTID, v.LASTNAME, v.FIRSTNAME, v.PROCEDURECODE, v.VISITDATE,
p.DateOfBirth, p.Gender, p.PrimaryPhone
FROM VISITINFO v
INNER JOIN ABC123 a
ON a.PATIENTID = v.PATIENTID
INNER JOIN DEF456 d
ON d.PATIENTID = v.PATIENTID
INNER JOIN PatientInfo p
ON v.PatientID = p.PatientID
WHERE v.PROCEDURE_CODE IN ('ABC123', 'DEF456')
ORDER BY v.PATIENTID, v.VISITDATE, v.PROCEDURECODE;
What we're doing here is using a couple of CTE's to get the PATIENTID's for each patient who has the the procedures in question performed. We then start with all records in VISITINFO and inner join those with the two CTE's. Because an INNER JOIN requires that matching information exist in the tables on both sides of the join this has the effect of retaining only the visits which match the information in both of the CTE's, based on the join criteria which in this case is the PATIENTID.
Best of luck.
Select the records with the two codes, but use COUNT OVER to count the distinct codes per patient. Only keep those records with a count of 2 codes for the patient, i.e. patients with both codes.
select patient_id, last_name, first_name, code, visit_date
from
(
select mytable.*, count(distinct code) over (partition by patient_id) as cnt
from mytable
where code in ('ABC123','DEF456')
) data
where cnt = 2;

SQL Query to display heirarchical data

I have two tables - 'Users' and 'Supervision'
For this example, my users table is very simple:-
Users
=====
ID (PK)
UserName
Some users manage other users, so I've built a second table 'Supervision' to manage this:-
Supervision
===========
UserID
SuperID - this is the ID of the staff member that the user supervises.
This table is used to join the Users table to itself to identify a particular users supervisor. It might be that a user has more than one supervisor, so this table works perfectly to this end.
Here's my sample data in 'Users':-
userID userName
1 Bob
2 Margaret
3 Amy
4 Emma
5 Carol
6 Albert
7 Robert
8 Richard
9 Harry
10 Arthur
And my data in 'Supervision':-
userID superID
1 2
1 3
2 4
2 5
3 4
3 5
6 1
6 7
7 8
7 9
9 10
If I want to see who directly reports to Bob, writing an SQL query is straightforward, and tells me that Margaret and Amy are his direct reports.
What I want to do however is to write a query that shows everybody who comes under Bob, so it would need to look at Bobs direct reports, and then their direct reports, and so on - it would give Margaret, Amy, Emma and Carol as the result in this case.
I'm assuming this requires some kind of recursion but I'm completely stuck..
You should use recursive CTE:
WITH RCTE AS
(
SELECT * FROM dbo.Supervision WHERE UserID = 1
UNION ALL
SELECT s.* FROM dbo.Supervision s
INNER JOIN RCTE r ON s.userID = r.superID
)
SELECT DISTINCT u.userID, u.userName
FROM RCTE r
LEFT JOIN dbo.Users u ON r.superID = u.userID
SQLFiddle DEMO
Sounds to me like you need a Recursive CTE. This article serves as a primer, and includes a fairly similar example to the one you have:
http://blog.sqlauthority.com/2012/04/24/sql-server-introduction-to-hierarchical-query-using-a-recursive-cte-a-primer/
Hope it helps.
WITH MyCTE
AS (
-- ID's and Names
SELECT SuperID, ID
FROM Users
join dbo.Supervision
on ID = dbo.Supervision.UserID
WHERE UserID = 1
UNION ALL
--Who Manages who...
SELECT s.SuperID, ID
FROM Supervision s
INNER JOIN MyCTE ON s.UserID = MyCTE.SuperID
WHERE s.UserID IS NOT NULL
)
SELECT distinct MyCTE.ID, NAMES.UserName, '<------Reports to ' as Hierarchy, res_name.UserName
FROM MyCTE
join dbo.Users NAMES on
MyCTE.ID = NAMES.ID
join dbo.Users res_name
on res_name.ID = MyCTE.SuperID
order by MyCTE.ID, NAMES.UserName, res_name.UserName

Why is Count(*) returning unexpected number?

This is my query:
Select COUNT(*)
From
Users u
Inner Join
UsersLoginHistory uh On u.UserID = uh.UserID
Where
1 = 1
And
u.AccountID = 37
Group By u.UserID
What I'd like to be able to get is Count(*) should be returning a number after grouping on u.UserId. But it returns the Count(*) before the group by is made.
So I can rewrite the above query as:
Select COUNT(*)
From (
Select u.Username
From
Users u
Inner Join
UsersLoginHistory uh On u.UserID = uh.UserID
Where
1 = 1
And
u.AccountID = 37
Group By u.UserID
) v
But I need to find out why is the Count(*) returning records before a group by is made and how can I fix the 1st query itself.
EDIT: Sample Records
Users table
UserId Username
102 tom.kaufmann
UserLoginHistory table
UsersLoginHistoryID UserID LoginDateTime LogoutDateTime IPAddress
1 102 2012-09-28 01:16:00 NULL 115.118.71.248
2 102 2012-09-28 01:29:00 2012-09-28 01:29:00 127.0.0.1
3 102 2012-09-28 01:32:00 2012-09-28 01:32:00 127.0.0.1
4 102 2012-09-28 01:41:00 NULL 115.118.71.248
5 102 2012-09-28 01:43:00 2012-09-28 07:04:00 115.118.71.248
and so on..
Haven't writted every single record in this DB.
Based on your second query which you say returns the desired results (and assuming UserID is the PK of Users) I presume this is what you need
SELECT Count(UserID)
FROM Users u
WHERE u.AccountID = 37
AND EXISTS (SELECT *
FROM UsersLoginHistory uh
WHERE u.UserID = uh.UserID)
This will be more efficient than expanding out all the joined rows then collapsing them again with Group By u.UserID and counting the number of rows that result.
Change the first line to:
Select COUNT(*), u.UserID
This should provide you a list of UserIds and the count of entries in the UsersLoginHistory table.
SELECT u.UserId
, COUNT(uh.*)
FROM Users u
INNER JOIN UsersLoginHistory uh ON u.UserID = uh.UserID
WHERE u.AccountID = 37
GROUP BY u.UserID
But I need to find out why is the Count(*) returning records before a group by is made and how can I fix the 1st query itself
It is counting the number of lines for each UserID (number of logins), which is exactly how group by is supposed to work.
COUNT is an aggregate function and this is how it's supposed to work. You get count per grouping.
In your first query you are querying the number of userloginhistory per user. In your second query you are querying number of users with login history.
http://msdn.microsoft.com/en-us/library/ms173454.aspx