Distinct Count with data from another table - sql

I have 4 tables
All ID related things are ints and the rest are texts.
I want to count the number of albums the user is tagged at so if a user is tagged in album1 once album2 once and album3 once it will show 3 and if more in any of them it will still show 3.
I tried to do:
SELECT COUNT(DISTINCT ALBUM_ID) FROM PICTURES WHERE ID=(SELECT PICTURE_ID FROM TAGS WHERE USER_ID=userId);
But this returned 1 although it was supposed to return 3 and the same happened without DISTINCT.
How can I get the amount?
EDIT:
I want to check only one user(I have the user's ID and name)

You must join users with LEFT joins to tags and pictures and aggregate:
SELECT u.id, u.name, COUNT(DISTINCT p.album_id) counter
FROM users u
LEFT JOIN tags t ON t.user_id = u.id
LEFT JOIN pictures p ON p.id = t.picture_id
GROUP BY u.id, u.name
If you want the result for a specific user only:
SELECT u.id, u.name, COUNT(DISTINCT p.album_id) counter
FROM users u
LEFT JOIN tags t ON t.user_id = u.id
LEFT JOIN pictures p ON p.id = t.picture_id
WHERE u.id = ?
GROUP BY u.id, u.name -- you may omit this line, because SQLite allows it
Or with a correlated subquery:
SELECT u.id, u.name,
(
SELECT COUNT(DISTINCT p.album_id)
FROM tags t INNER JOIN pictures p
ON p.id = t.picture_id
WHERE t.user_id = u.id
) counter
FROM users u
WHERE u.id = ?
Replace ? with the id of the user that you want.

Related

Only show users that having cars more than one

Dear all I have users table and cars table.
and I have following join query:
select
users.id as user_id,
users.username,
users.job,
cars.id,
cars.brand as car_brand
FROM users
LEFT JOIN cars on users.id = cars.user_id
GROUP BY users.username, users.id, cars.id;
Here is the snapshot:
How to query for users that having cars more than one?
I tried code below but it return empty data:
How to get users that having more than one cars? (username: Ismed)
You can do in this was as well.
select
users.id as user_id,
users.username,
users.job,
cars.id,
cars.brand as car_brand
FROM users
LEFT JOIN cars on users.id = cars.user_id
where exists (select username, count(*) multiplecars
FROM users u
JOIN cars c on u.id = c.user_id
where users.username = u.username
group by
u.username
having count(*) > 1 )
If the users have more than one car (even if same brand then this will bring those records) if you only want users with more than one branded care you can do count(distinct)
The simplest and probably most performance method is to use window functions:
select user_id, username, job, id, brand
from (select u.id as user_id, u.username, u.job,
c.id, c.brand as car_brand,
count(*) over (partition by u.id) as num_cars
from users u join
cars c
on u.id = c.user_id
) uc
where num_cars > 1;
Note that I changed the left join to a join. If you have two matches, you are requiring a match. I also introduced table aliases so the query is easier to write and to read.
SELECT users.username
FROM users
WHERE users.id IN(
select
users.id
FROM users
JOIN cars on users.id = cars.user_id
GROUP BY users.id
HAVING COUNT(*) > 1
);
Filter users first who has more then one car then get corresponding details

SQL join 3 tables while getting count from 2 of them

Back with another SQL question about joins. I have 3 tables:
user: id, username, name, city, state, private
rides: id, creator, title, datetime, city, state
posts: id, title, user, date, state, city
I need to get the users from the user table, and based on the id of user, get the number of posts and rides for each person. Such as, user with id 25 has 2 rides and 4 posts, while the user with id 27 has 2 rides and 2 posts. The problem I am having, is that both users are coming back with 4 posts and rides each.
user.id = rides.creator = posts.user //just so you know what fields equals the user id
Here is my code:
select u.id, u.username, u.state, u.city, count(p.id) as TotalPosts, count(r.id) as TotalRides
from user u
left join posts p on p.user=u.id
left join rides r on r.creator=u.id
where private='public'
group by u.id
order by u.username, u.state asc;
If I separate them out, and just join the posts or the rides, I get the correct totals back. I tried switching the order of the joins, but I got the same results. Not sure what is going on.
Any ideas or thoughts are appreciated.
Your problem is a Cartesian product along two different dimensions. The best solution is to pre-aggregate the data:
select u.id, u.username, u.state, u.city, p.TotalPosts, r.TotalRides
from user u left join
(select user, count(*) as totalposts
from posts p
group by user
) p
on p.user = u.id left join
(select creator, count(*) as totalrides
from rides r
group by creator
) r
on r.creator = u.id
where u.private = 'public'
group by u.id
order by u.username, u.state asc;
you can always use a sub select.
select u.*,
(select count(*) from posts where user = u.id) as 'posts',
(select count(*) from rides where creator = u.id) as 'rides'
from users u
where .....

SQL query to find the top 3 in a category

Calling all sql enthusiasts!
Quick info: using PostgreSQL.
I have a query that return the maximum number of likes for a user per category. What I want now, is to show the top 3 users with the most likes per category.
A helpful resource was using this example to solve the problem:
select type, variety, price
from fruits
where (
select count(*) from fruits as f
where f.type = fruits.type and f.price <= fruits.price
) <= 2;
I understand this, but my query is using joins and I am also a beginner, so I was not able to use this information effectively.
Down to business, this is my query for returning the MAX likes for a user per category.
SELECT category, username, MAX(post_likes) FROM (
SELECT c.name category, u.username username, SUM(p.like_count) post_likes, COUNT(*) post_num
FROM categories c
JOIN topics t ON c.id = t.category_id
JOIN posts p ON t.id = p.topic_id
JOIN users u ON u.id = p.user_id
GROUP BY c.name, u.username) AS leaders
WHERE post_likes > 0
GROUP BY category, username
HAVING MAX(post_likes) >= (SELECT SUM(p.like_count)
FROM categories c
JOIN topics t ON c.id = t.category_id
JOIN posts p ON t.id = p.topic_id
JOIN users u ON u.id = p.user_id WHERE c.name = leaders.category
GROUP BY u.username order by sum desc limit 1)
ORDER BY MAX(post_likes) DESC;
Any and all help would be greatly appreciated. I am having a difficult time wrapping my head around this problem. Thank!
If you want the most likes per category, use window functions:
SELECT cu.*
FROM (SELECT c.name as category, u.username as username,
SUM(p.like_count) as post_likes, COUNT(*) as post_num,
ROW_NUMBER() OVER (PARTITION BY c.name ORDER BY COUNT(*) DESC) as seqnum
FROM categories c JOIN
topics t
ON c.id = t.category_id JOIN
posts p
ON t.id = p.topic_id JOIN
users u
ON u.id = p.user_id
GROUP BY c.name, u.username
) cu
WHERE seqnum <= 3;
This always returns three rows per category, even if there are ties. If you want to do something else, then consider DENSE_RANK() or RANK() instead of ROW_NUMBER().
Also, use as for column aliases in the FROM clause. Although optional, one day you will leave out a comma and be grateful that you are in the habit of using as.

SQL combining 3 tables and get just the row with the latest date

I have 3 tables user, session and log. The user table stores all user relevant information while the session just connects the user with the log. And i want to get a list of all users with the latest log entry. The table design looks like this:
user (id, name, ...)
session (id, user_id)
log (id, session_id, time, type, ...)
My current query looks like this
SELECT *
FROM USER AS u
INNER JOIN session AS s
ON u.id = s.user_id
INNER JOIN log AS l
ON l.session_id = s.id
ORDER BY l.time DESC
But it's not hard to imagine that this just returns the data of all 3 tables sorted by date. How do i achieve a result that i just get every user just once with the data from the latest log entry ordered by the time of log (desc)?
Thanks in advance for your help.
You can use DISTINCT ON in conjunction with ORDER BY to get the latest row per user by log date. This will allow you to select the additional fields you need:
SELECT DISTINCT ON (u.id)
u.id,
u.Name,
l.type,
l.time
FROM user AS u
INNER JOIN session AS s ON u.id = s.user_id
INNER JOIN log AS l ON l.session_id = s.id
ORDER BY u.id, l.time DESC;
N.B. I don't know exactly what columns you need, but I have added a couple in to demonstrate as I don't like to advocate the use of SELECT *
For completeness there are a couple of other ways to achieve this, the first is to select the max in a subquery and join back to the outer query on both user_id and time:
SELECT u.id,
u.Name,
l.type,
l.time
FROM user AS u
INNER JOIN session AS s
ON u.id = s.user_id
INNER JOIN log AS l
ON l.session_id = s.id
INNER JOIN
( SELECT s.user_id, MAX(l.time) AS time
FROM session AS s
INNER JOIN log AS l
ON l.session_id = s.id
GROUP BY s.user_id
) AS MaxLog
ON MaxLog.user_id = u.id
AND MaxLog.time = l.time
ORDER BY l.time DESC;
Or you can use ROW_NUMBER():
SELECT id, Name, type, time
FROM ( SELECT u.id,
u.Name,
l.type,
l.time,
ROW_NUMBER() OVER(PARTITION BY u.id ORDER BY l.time DESC) AS RowNumber
FROM user AS u
INNER JOIN session AS s
ON u.id = s.user_id
INNER JOIN log AS l
ON l.session_id = s.id
) u
WHERE RowNumber = 1;
I've assumed some schema (user.user_name?), but you can do this by grouping and an aggregate like Max:
SELECT u.user_id,
u.user_name,
Max(l.time) AS LastLogTime
FROM USER AS u
LEFT JOIN session AS s
ON u.id = s.user_id
INNER JOIN log AS l
ON l.session_id = s.id
GROUP BY u.user_id,
u.user_name;
You won't be able to select * as we need to use GROUP BY
Similarly, ORDER BY l.time isn't applicable any more - you could still order by e.g. user_name
I've also LEFT JOINED - this way, if the user has no sessions, it will still return a record for the user, possibly with a LastLogTime of NULL.

Writing a Mathematical Formula in SQL?

I have these tables: users, comments, ratings, and items
I would like to know if it is possible to write SQL query that basically does this:
user_id is in each table. I'd like a SQL query to count each occurrence in each table (except users of course). BUT, I want some tables to carry more weight than the others. Then I want to tally up a "score".
Here is an example:
user_id 5 occurs...
2 times in items;
5 times in comments;
11 times in ratings.
I want a formula/point system that totals something like this:
items 2 x 5 = 10;
comments 5 x 1 = 5;
ratings 11 x .5 = 5.5
TOTAL 21.5
This is what I have so far.....
SELECT u.users
COUNT(*) r.user_id
COUNT(*) c.user_id
COUNT(*) i.user_id
FROM users as u
JOIN COMMENTS as c
ON u.user_id = c_user_id
JOIN RATINGS as r
ON r.user_id = u.user_id
JOIN ITEMS as i
i.user_id = u.user_id
WHERE
????
GROUP BY u.user_id
ORDER by total DESC
I am not sure how to do the mathematical formula portion (if possible). Or how to tally up a total.
Final Code based on John Woo's Answer!
$sql = mysql_query("
SELECT u.username,
(a.totalCount * 5) +
(b.totalCount) +
(c.totalCount * .2) totalScore
FROM users u
LEFT JOIN
(
SELECT user_id, COUNT(user_id) totalCount
FROM items
GROUP BY user_id
) a ON a.user_id= u.user_id
LEFT JOIN
(
SELECT user_id, COUNT(user_id) totalCount
FROM comments
GROUP BY user_id
) b ON b.user_id= u.user_id
LEFT JOIN
(
SELECT user_id, COUNT(user_id) totalCount
FROM ratings
GROUP BY user_id
) c ON c.user_id = u.user_id
ORDER BY totalScore DESC LIMIT 10;");
Maybe this can help you,
SELECT u.user_ID,
(a.totalCount * 5) +
(b.totalCount) +
(c.totalCount * .2) totalScore
FROM users u LEFT JOIN
(
SELECT user_ID, COUNT(user_ID) totalCount
FROM items
GROUP BY user_ID
) a ON a.user_ID = u.user_ID
LEFT JOIN
(
SELECT user_ID, COUNT(user_ID) totalCount
FROM comments
GROUP BY user_ID
) b ON b.user_ID = u.user_ID
LEFT JOIN
(
SELECT user_ID, COUNT(user_ID) totalCount
FROM ratings
GROUP BY user_ID
) c ON c.user_ID = u.user_ID
ORDER BY totalScore DESC
but based on yur query above,thismay also work
SELECT u.users
(COUNT(*) * .5) +
COUNT(*) +
(COUNT(*) * 2) totalcore
FROM users as u
LEFT JOIN COMMENTS as c
ON u.user_id = c_user_id
LEFT JOIN RATINGS as r
ON r.user_id = u.user_id
LEFT JOIN ITEMS as i
ON i.user_id = u.user_id
GROUP BY u.user_id
ORDER by totalcore DESC
The only difference is by using LEFT JOIN. You will not use INNER JOIN in this situation because there are chances that user_id is not guaranteed to exists on every table.
Hope this makes sense
Here's an alternative approach:
SELECT
u.user_id,
SUM(s.weight) AS totalScore
FROM users u
LEFT JOIN (
SELECT user_id, 5.0 AS weight
FROM items
UNION ALL
SELECT user_id, 1.0
FROM comments
UNION ALL
SELECT user_id, 0.5
FROM ratings
) s
ON u.user_id = s.user_id
GROUP BY
u.user_id
I.e. for every occurrence of every user in every table, a row with a specific weight is produced. The UNIONed set of weights is then joined to the users table for subsequent grouping and aggregating.