SQL Server - select min date and id from foreign key - sql

These are my tables:
USER:
id_user name email last_access id_company
1 jhonatan abc#abc.com 2014-12-15 1
2 cesar cef#cef.com 2014-12-31 1
3 john 123#123.com 2015-01-09 2
4 steven 897#asdd.cpom 2015-01-02 2
5 greg sd#touch.com 2014-12-07 1
6 kyle fb#fb.com 2014-11-20 1
COMPANY:
id_company company
1 Facebook
2 Appslovers
I need to know, what are the users which has the MIN last_access per company (just one). It could be like this:
id_user name last_access company
6 kyle 2014-11-20 Facebook
4 steven 2015-01-02 Appslovers
Is it possible ?

Use window function
SELECT id_user,
NAME,
last_access,
company
FROM (SELECT id_user,
NAME,
last_access,
company,
Row_number()OVER(partition BY company ORDER BY last_access) rn
FROM users u
JOIN company c
ON u.id_company = c.id_company) a
WHERE rn = 1
or join both the tables find the min last_access date per company then join the result back to the users table to get the result
SELECT id_user,
NAME,
a.last_access,
a.company
FROM users u
JOIN(SELECT u.id_company,
Min(last_access) last_access,
company
FROM users u
JOIN company c
ON u.id_company = c.id_company
GROUP BY u.id_company,
company) a
ON a.id_company = u.id_company
AND u.last_access = a.last_access

This can be done in many ways, for example by using a window function like row_number to partition the data and then selecting the top rows from each group like this:
;with cte (id_user, name, last_access, company, seq) as (
select
id_user,
name,
last_access,
company,
seq = row_number() over (partition by u.id_company order by last_access)
from [user] u
inner join [company] c on u.id_company = c.id_company
)
select id_user, name, last_access, company
from cte where seq = 1

Related

Sub query for two tables base on status using SQL

I have two tables Patient and PatientStatusLog using SQL tables
1.Patient
sno patientid status createdby Reegion
1 481910 D 1222 India
2 476795 D 1222 India
2.PatientStatusLog
sno patientid status comments createdby CreatedDate(dd/mm/yyyy)
1 481910 A mycommnet 1222 01/01/2000
2 481910 A mycommnet 1222 02/01/2000
3 481910 B mycommnet 1222 01/01/2000
4 481910 C mycommnet 1222 01/01/2000
I need output like below who have status A and createddate should recent pass date from PatientStatusLog table using patientid of both tables
Region status CreatedDate
India A 02/01/2000
You can use window functions:
select . . . -- whatever columns you want
from Patient p join
(select psl.*,
rank() over (partition by patientid order by createddate desc) as seqnum
from PatientStatusLog psl
) psl
on p.patientid = psl.patientid
where psl.seqnum = 1 and psl.status = 'A';
Note that this uses rank() because the creation date appears to have duplicates.
Try this:
SELECT TOP 1
p.Region,
psl.Status,
psl.CreatedDate
FROM
Patient [p]
JOIN PatientStatusLog [psl] ON psl.patientid = p.patientid
AND psl.status = 'A'
ORDER BY
psl.CreatedDate DESC
For the subquery request:
SELECT
p.Region,
x.status,
x.CreatedDate
FROM
(
SELECT TOP 1
PatientId,
Status,
CreatedDate
FROM
PatientStatusLog
WHERE
status = 'A'
ORDER BY
CreatedDate DESC
) AS x
JOIN Patient [p] ON p.PatientId = x.PatientId
Note that this is definitely not the best way to handle this, both from a code readability and optimization perspective.

SQL Server: finding duplicates between two parameters in different tables

I am trying to find duplicates in SQL Server where customers with the same forename, surname, and mobile number match. The thing is they are in different tables.
custid forename surname dateofbirth
-----------------------------------
1 David John 16-09-1985
2 David Jon 16-09-1985
3 Sarah Smith 10-08-2015
4 Peter Proca 11-06-2011
5 Peter Broca 11-06-2011
addid custid line1
-------------------------
1 1 0504135846
2 2 0504135846
3 3 0506523145
4 4 0503698521
5 5 0503698521
I am currently able to find duplicates by forename and surname, but if I want to find based on mobile numbers how can I bring it in?
select c.*
from
(select
c.*,
count(*) over (partition by left(surname, 3)) as cnt
from
customers c) c
order by
surname;
Use join:
select c.*
from (select c.*, t2.line1
count(*) over (partition by surname, forename, line1) as cnt
from customers c join
table2 t2
on t2.custid = c.custid
) c
order by surname;
Here you go, just JOIN on the table. Using HAVING might simplify your query as well
SELECT COUNT(*), c.forename, c.surname, mn.line1
FROM customers c
INNER JOIN mobilenumber mn ON c.custid=mn.custid
GROUP BY c.forename, c.surname, mn.line1
HAVING COUNT(*)>1
Also, you might need to LEFT JOIN if there is a chance that some records wont be in the mobilenumbers table.

Select based on max date from another table

I'm trying to do a simple Select query by getting the country based on the MAX Last update from the other table.
Order#
1
2
3
4
The other table contains the country and the last update:
Order# Cntry Last Update
1 12/21/2019 9:19 PM
1 US 1/10/2020 1:07 AM
2 JP 7/29/2020 12:15 PM
3 CA 4/12/1992 2:04 PM
3 GB 11/6/2001 9:26 AM
3 DK 2/1/2005 3:04 AM
4 CN 8/20/2013 12:04 AM
4 10/1/2015 4:04 PM
My desired result:
Order# Country
1 US
2 JP
3 DK
4
Not sure the right solution for this. So far i'm stuck with this:
SELECT Main.[Order#], tempTable.Cntry
FROM Main
LEFT JOIN (
SELECT [Order#], Cntry, Max([Last Update]) as LatestDate FROM Country
GROUP BY [Order#], Cntry
) as tempTable ON Main.[Order#] = tempTable.[Order#];
Thanks in advance!
If needs only number of order and country,maybe don't need two tables:
SELECT distinct order, country
FROM
(
SELECT order, LAST_VALUE (country) OVER (PARTITION by [order] order by last_update) country FROM Country
) X
In SQL Server, you can use a correlated subquery:
update main
set country = (select top (1) s.country
from secondtable s
where s.order# = main.order#
order by s.lastupdate desc
);
EDIT:
A select would look quite simimilar:
select m.*,
(select top (1) country
from secondtable s
where s.order# = main.order#
order by s.lastupdate desc
)
from main m
I don't have time to try it with sample data, but is that what you are looking for?
select order orde, cntry
from table
where last_update =
(select max(last_update) from table where order = orde)

Pulling Datetime when using COUNT(UserID)

I have the tables Users
UserID FirstName LastName Email
------ --------- -------- -----
1 Fred Smith fs#abc.com
2 Bob Hill bh#abc.com
3 Jane Doe jd#abc.com
and LoginSession
LoginSessionID UserID StartDate
-------------- ------ ---------
1 1 2014-11-23 08:37:14.836
2 1 2014-11-25 11:13:53.225
3 2 2014-12-01 03:15:33.846
4 1 2014-12-01 17:34:19.036
5 3 2014-12-05 12:55:01.998
6 1 2014-12-14 17:20:14.636
7 3 2014-12-15 10:02:17.376
What I am trying to do is find the users who have logged on only once and find out when that was.
I have managed to find the users who have logged on only once by using
SELECT
U.FirstName, U.LastName, COUNT(L.UserID) AS Visits
FROM
LoginSession L
JOIN
Users U ON U.UserID = L.UserID
GROUP BY
U.FirstName, U.LastName
HAVING
COUNT(L.UserID) = 1
But I also want to pull through the L.StartDate of those users. If I add it to the select query I get an error because it's not contained in an aggregate function or GROUP BY clause. If I add it the the GROUP BY line (to avoid that error) I get each and every login handily marked as 1 visit!
I also tried using a subquery but I got an error because it returned more than one result.
I really am totally stumped!
You can do this with aggregation:
select UserId, min(StartDate) as StartDate
from LoginSession ls
group by UserId
having count(*) = 1;
The min() returns the value you want, because there is only one row that matches. You can use an addition join to get additional information about the users.
select u.*, lsu.StartDate
from Users u join
(select UserId, min(StartDate) as StartDate
from LoginSession ls
group by UserId
having count(*) = 1
) lsu
on lsu.UserId = u.UserId;
You can use windowed version of COUNT:
SELECT FirstName, LastName, StartDate
FROM (
SELECT U.FirstName, U.LastName, L.StartDate,
COUNT(*) OVER (PARTITION BY U.UserID) AS cnt
FROM LoginSession L
JOIN Users U ON U.UserID = L.UserID ) AS t
WHERE t.cnt = 1
COUNT with OVER clause will return the number of records per U.UserID. Using an outer query you can fetch exactly these records.
Demo here

How to rank users and get a subset from this rank with my user and the above and below user by rank position

I am working on a query right now to get a ranking of my users. I have two tables one for users and the other one for profits where I save the amount and the user id to which is related. By getting the total of profits generated by a user I need to build a rank with three users, the user in the next higher ranked position to my user, my user and the user in the next lower ranked position to my user. For example:
id | name | total_profit | rank
-------+-----------------------------+--------------+------
10312 | John Doe | 7000.0 | 1
10329 | Michael Jordan | 5000.0 | 2
10333 | Kobe Bryant | 4000.0 | 3
10327 | Mike Bibby | 4000.0 | 3
10331 | Phil Jackson | 1000.0 | 4
In this if my user is Kobe Bryant I would need to get a rank with Michael Jordan, Kobe Bryant and Phil Jackson.
If my user is Mike Bibby I would need to get a rank with Michale Jordan, Mike Bybby and Phil Jackson.
Until now I have a query that returns me a full rank with all the users but I do not now what is a good way to get the three users that I want. I have tried to do this with ruby but I think it would be better if I do all this processing in the DB.
SELECT users.id, users.name, total_profit, rank() OVER(ORDER BY total_profit DESC)
FROM users
INNER JOIN (SELECT sum(profits.amount) AS total_profit, investor_id
FROM profits GROUP BY profits.investor_id) profits ON profits.investor_id = users.id
ORDER BY total_profit DESC;
I am using PostgresSQL 9.1.4
with s as (
select
users.id, users.name, total_profit,
rank() over(order by total_profit desc) as r
from
users
inner join
(
select sum(profits.amount) as total_profit,
investor_id
from profits
group by profits.investor_id
) profits on profits.investor_id = users.id
), u as (
select r from s where name = 'Kobe Bryant'
)
select distinct on (r) id, name, total_profit, r
from s
where
name = 'Kobe Bryant'
or r in (
(select r from u) - 1, (select r from u) + 1
)
order by r;
with cte_profits as (
select
sum(p.amount) as total_profit, p.investor_id
from profits as p
group by p.investor_id
), cte_users_profits as (
select
u.id, u.name, p.toral_profit,
dense_rank() over(order by p.total_profit desc) as rnk,
row_number() over(partition by up.total_profit order by up.id) as rn
from users as u
inner join cte_profits as p on p.investor_id = u.id
)
select c2.*
from cte as c
left outer join cte as c2 on
c2.id = c.id or
c2.rnk = c.rnk + 1 and c2.rn = 1 or
c2.rnk = c.rnk - 1 and c2.rn = 1
where c.name = 'Kobe Bryant'
order by c2.rnk
sql fiddle demo