SELECT email, org_id, MIN(created_at) as first_user_created
FROM users
WHERE org_id IN (SELECT org_id FROM subscriptions)
GROUP BY email, org_id;
Result of the above query gives me multiple user records per org_id.
What I want: Per organization - return the email, org_id and first_user_created of user that got created the earliest.
One solution uses DISTINCT ON:
SELECT DISTINCT ON (u.org_id) u.email, u.org_id, u.created_at
FROM users u
INNER JOIN subscriptions s
ON s.org_id = u.org_id
ORDER BY u.org_id, u.created_at;
Another way to do this uses RANK():
WITH cte AS (
SELECT u.email, u.org_id, u.created_at,
RANK() OVER (PARTITION BY u.org_id ORDER BY u.created_at) rnk
FROM users u
INNER JOIN subscriptions s ON s.org_id = u.org_id
)
SELECT email, org_id, created_at
FROM cte
WHERE rnk = 1;
Related
I have a query that selects users with the districts which they visited and visits count.
select users.id, places.district, count(users.id) as counts from users
left join visits on users.id = visits.user_id
inner join places on visits.place_id = places.id
group by users.id, places.district
I need to select only those users who have visited provided district the most. For example, I have a user with id 1 who visited district A one time and district B three times. If I provide district B as parameter, user 1 will be in select. If I want to select users from district A, user 1 will not be in select.
I think that's ranking, then filtering:
select *
from (
select u.id, p.district, count(*) as cnt_visits,
rank() over(partition by u.id order by count(*) desc)
from users u
inner join visits v on u.id = v.user_id
inner join places p on p.id = v.place_id
group by u.id, p.district
) t
where rn = 1 and district = ?
Note that you don't actually need table users to get this result. We could simplify the query as:
select *
from (
select v.user_id, p.district, count(*) as cnt_visits,
rank() over(partition by u.id order by count(*) desc)
from visits v
inner join places p on p.id = v.place_id
group by v.user_id, p.district
) t
where rn = 1 and district = ?
This query handles top ties: if a user had the same, maximum number of visits in two different districts, both are taken into account. If you don't need that feature, then we can simplify the subquery with distinct on:
select *
from (
select distinct on (v.user_id) v.user_id, p.district, count(*) as cnt_visits
from visits v
inner join places p on p.id = v.place_id
group by v.user_id, p.district
order by v.user_id, cnt_visits desc
) t
where district = ?
I have a users table and each user has flights in a flights table. Each flight has a departure and an arrival airport relationship within an airports table. What I need to do is count up the unique airports across both departure and arrival columns (flights.departure_airport_id and flights.arrival_airport_id) for each user, and then assign them a rank via dense_rank and then retrieve the rank for a given user id.
Basically, I need to order all users according to how many unique airports they have flown to or from and then get the rank for a certain user.
Here's what I have so far:
SELECT u.rank FROM (
SELECT
users.id,
dense_rank () OVER (ORDER BY count(DISTINCT (flights.departure_airport_id, flights.arrival_airport_id)) DESC) AS rank
FROM users
LEFT JOIN flights ON users.id = flights.user_id
GROUP BY users.id
) AS u WHERE u.id = 'uuid';
This works, but does not actually return the desired result as count(DISTINCT (flights.departure_airport_id, flights.arrival_airport_id)) counts the combined airport ids and not each unique airport id separately. That's how I understand it works, anyway... I'm guessing that I somehow need to use a UNION join on the airport id columns but can't figure out how to do that.
I'm on Postgres 13.0.
I would recommend a lateral join to unpivot, then aggregation and ranking:
select *
from (
select f.user_id,
dense_rank() over(order by count(distinct a.airport_id) desc) rn
from flights f
cross join lateral (values
(f.departure_airport_id), (f.arrival_airport_id)
) a(airport_id)
group by f.user_id
) t
where user_id = 'uuid'
You don't really need the users table for what you want, unless you do want to allow users without any flight (they would all have the same, highest rank). If so:
select *
from (
select u.id,
dense_rank() over(order by count(distinct a.airport_id) desc) rn
from users u
left join flights f on f.user_id = u.id
left join lateral (values
(f.departure_airport_id), (f.arrival_airport_id)
) a(airport_id) on true
group by u.id
) t
where id = 'uuid'
You're counting the distinct pairs of (departure_airport_id, arrival_airpot_id). As you suggested, you could use union to get a single column of airport IDs (regardless of whether they are departure or arrival airports), and then apply a count on them:
SELECT user_id, DENSE_RANK() OVER (ORDER BY cnt DESC) AS user_rank
FROM (SELECT u.id AS user_id, COALESCE(cnt, 0) AS cnt
FROM users u
LEFT JOIN (SELECT user_id, COUNT DISTINCT(airport_id) AS cnt
FROM (SELECT user_id, departure_airport_id AS airport_id
FROM flights
UNION
SELECT user_id, arrival_airport_id AS airport_id
FROM flights) x
GROUP BY u.id) f ON u.id = f.user_id) t
I have 2 tables
1. User Master
user_id, user_full_name, user_dob...so on
2. Login Details
login_id, login_user_id, login_time, login_date, logout_time
Problem
2nd table has n number of rows against User Master table id
I need to make a join but the condition is that it should show only last login data of the user
example
user_full_name, user_login, user_logout so on...
If you want the result for a single user, you could use a simple INNER JOIN combined with an ORDER BY and TOP 1:
SELECT TOP 1 user_full_name, login_time, login_date, logout_time
FROM Users INNER JOIN Logins ON
Users.user_id = Logins.user_id
WHERE
Users.user_id = #user_id
ORDER BY login_date DESC, login_time DESC
(See SQLFiddle)
If you want the result for all users, you could use CROSS APPLY:
SELECT user_full_name, l.*
FROM Users u CROSS APPLY (
SELECT TOP 1 login_time, login_date, logout_time
FROM Logins
WHERE
u.user_id = Logins.user_id
ORDER BY login_date DESC, login_time DESC
) l
(See SQLFiddle)
A common solution for this problem is to use the row_number window function and filter for rows with row number 1 in each partition (by user, ordered by date/time):
WITH UserDetails AS (
SELECT
*
, ROW_NUMBER() OVER (PARTITION BY login_user_id
ORDER BY login_date DESC, login_time DESC) AS RN
FROM LoginDetails
)
SELECT *
FROM UserMaster M
JOIN UserDetails D ON M.user_id = D.login_user_id
WHERE D.RN = 1;
You could try using a TOP 1 inside the JOIN clause:
SELECT a.user_id, a.user_full_name, b.login_id...
FROM UserMaster a INNER JOIN Logins b ON b.login_date =
(
SELECT TOP 1 login_date
FROM Logins
WHERE login_user_id = a.user_id
ORDER BY login_date DESC
)
I'm trying to select all the users in a table that have the same Email but have a different Name. So far I have managed to get all the rows that have duplicate Email but I'm stuck on the next step.
SELECT * FROM users WHERE Email IN
(SELECT Email FROM users GROUP BY Email HAVING COUNT(*) > 1)
Thanks in advance
I think you just want count(distinct name) in the subquery:
SELECT *
FROM users
WHERE Email IN (SELECT Email
FROM users
GROUP BY Email
HAVING COUNT(distinct Name) > 1
) ;
I prefer having min(name) <> max(name) for the having clause. It is slightly more efficient.
However, the most efficient method is probably to use window functions:
select u.*
from (select u.*, min(name) over (partition by email) as minname,
max(name) over partition by email) as maxname
from users u
) u
where minname <> maxname;
Try this
SELECT *
FROM users U1
INNER JOIN
users U2
on U1.Email=U2.Email
AND U1.Name <> U2.Name
You can try the following:
SELECT u.*
FROM users u
LEFT JOIN (
SELECT Email
FROM users
GROUP BY Email
HAVING COUNT(*) = COUNT(DISTINCT name)
) tmp ON u.Email = tmp.Email
WHERE tmp.Email IS NOT NULL
Assume I have two tables:
users
sells
I need to alter this query: Show top three users who bought copies of softwareMy SQL is this:
select u.name, u.age, u.sex, u.email, s.selluid, max(count(u.uid)) FROM users u, sells s where u.usrid = s.selluid
Any idea about how to solve this problem? Thanks
Try this
select u.usrid, u.name, count(s.sellid)
from users u left join sells s on u.usrid=s.selluid
group by u.usrid, u.name order by count(s.sellid) desc;
You can solve this using an aggregation subquery with row_number():
select u.*, s.numsales
from users u join
(select s.selluid, count(*) as numsales,
row_number() over (order by count(*) desc) as seqnum
from sells s
group by s.selluid
) s
on u.userid = s.selluid
where seqnum <= 3;
One advantage to this approach is that you can readily get all the columns from users using just u.*.
SELECT x.*
FROM (
SELECT u.name
, u.age
, u.sex
, u.email
, s.selluid
, COUNT(*) as t
FROM users u JOIN sells s ON u.usrid = s.selluid
GROUP BY u.name
ORDER BY COUNT(*) DESC
) x
WHERE ROWNUM <= 3