SQL LIMIT by distinct column value without subqueries - sql

user
id
name
age
1
anna
6
2
john
10
3
lord
50
cats
id
name
userID
1
miez
1
2
caty
1
3
random
2
4
idk
3
When using
SELECT U.id, C.name FROM user U
INNER JOIN cats C ON U.id = C.id
LIMIT 2
I get as a
result
UserID
CatName
1
miez
1
caty
What I want is to limit my rows by the distinct values of UserID, like this
SELECT U.id, C.name FROM user U
INNER JOIN cats C ON U.id = C.id
LIMIT 2 <distinct U.id rows>
UserID
CatName
1
miez
1
caty
2
random
People suggested using limit in subqueries and check if UserID is in the return
like
... WHERE UserID IN (SELECT id FROM User LIMIT 2)
but this only works well for small tables and is not an elegant solution for good performance.
My idea was using DENSE_RANK(), like:
SELECT U.id, C.name FROM user U
DENSE_RANK() OVER (ORDER BY U.id) as rows,
INNER JOIN cats C ON U.id = C.id
WHERE rows < 50
but it is not working either.

You can't use a column alias on the same level where you define it. You will have to wrap the query in a derived table. However if you want a specific number of row per user you need to use partition by, not order by
select id, name
from (
SELECT u.id,
c.name,
DENSE_RANK() OVER (PARTITION BY U.id ORDER BY c.name) as rnk
FROM user U
JOIN cats C ON U.id = C.userid
) t
WHERE t.rnk <= 2

Related

SQL query to find a record which has all matching records in another table

I have below 3 tables and I want to write a SQL query which will list the store present in all city: (here the result should be "Walmart")
Stores:
ID Name
1 Walmart
2 Target
3 Sears
Stores_City
ID Store_id City ID
1 1 10
2 1 20
3 2 10
4 1 30
City
ID Name
10 NewYork
20 Boston
30 Eagan
I am unable to find a query that works. Any help is appreciated!
select s.Name
from Stores s
inner join
(
select store_id, count(distinct city_id)
from stores_city
group by store_id
having count(distinct city_id) = (select count(*) from City)
) x
on x.store_id = s.id;
You can do it by grouping on store_id and checking for the count from stores table.
A straight join would work
Select distinct s.name from stores s inner join store _city SC on s.id=sc.id
Inner join city c on
Sc.city_id = c.id
Here is another way that will work:
select s.*
from stores s
where not exists (
select c.id
from city c
except
select sc.city_id
from stores_city sc
where sc.store_id = s.id
)
Try this:
SELECT
s.Name
FROM Stores s
WHERE NOT EXISTS (SELECT TOP 1
1
FROM City c
LEFT JOIN Stores_City sc
ON c.ID = sc.CityID
AND sc.Store_id = s.ID
WHERE sc.ID IS NULL)

How to Join only first row, disregard further matches

I have 2 tables
Table Users:
UserID | Name
Table Cars:
CarID | Car Name | FK_UserID
A user can have more than 1 car.
I want to join each user with 1 car only, not more.
Having looked at other threads here,
I've tried the following:
Select users.UserID, users.name, carid
from Users
join cars
on users.UserID =
(
select top 1 UserID
from users
where UserID = CarID
)
But it still returns more than 1 match for each user.
What am I doing wrong?
You can try like below using ROW_NUMBER() function
select userid, username, carname
from
(
Select users.UserID as userid,
users.name as username,
cars.carname as carname,
ROW_NUMBER() OVER(PARTITION BY users.UserID ORDER BY users.UserID) AS r
from Users
join cars
on users.UserID = cars.FK_UserID
) XXX
where r = 1;
with x as
(select row_number() over(partition by userid order by carid) as rn,
* from cars)
select u.userid, x.carid, x.carname
from users u join x on x.userid = u.userid
where x.rn = 1;
This is one way to do it using row_number function.
Another way to do it
select u.UserID,
u.name,
(select TOP 1 carid
from cars c
where u.UserID = c.FK_UserID
order by carid) carid -- Could be ordered by anything
from Users u
-- where only required if you only want users with cars
where exists (select * from car c where u.UserID = c.FK_UserID)
Best would be to do a subquery and use a group-by in it to return only 1 user and a car for each user. Then join that to the outer user table.
Here is an example:
select *
from user_table u
join (
select userid
, max(carname)
from cars
group by userid
) x on x.userId = u.userId
or you could use the row_number() examples above if you want a specific order (either this example or theirs will do the trick)

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.

How can I take the first match from table B to table A when there's multiple matches?

If I have a User table, like this:
UserId Name
------ ----
1 Jim
2 Mark
and an Order table like this:
OrderId UserId
------- ------
1 1
2 1
3 1
4 2
How can I just take any single Order of Jim's? It doesn't matter which, I just want one.
I have this:
Joining isn't the issue, it's just limiting it to one result as there are obviously 3.
SELECT * FROM User u
LEFT OUTER JOIN Order o on u.UserId = o.UserId
Thanks in advance.
This will select the first matched row:
SELECT TOP 1 * FROM User u
LEFT OUTER JOIN Order o ON u.UserID = o.UserID
If you know which user you're looking for ahead of time, you can add that to the WHERE clause:
WHERE u.UserID = #userID
And, if you ever need to make sure you're getting the First, or Most Recent order, you can use :
ORDER BY o.OrderID ASC/DESC
Select u.userid, name, min (orderID)
FROM User u
LEFT JOIN Order o on u.UserId = o.UserId
group by u.userid, name
To filter just for Jim
Select min (orderID)
FROM User u
LEFT JOIN Order o on u.UserId = o.UserId
where name='Jim'
the following query will give you one order per user (the lowest order number of each user)
select name, order_id
from users u,
orders o
where u.user_id = o.user_id
and o.order_id = (select min(order_id) from orders c where c.user_id=o.user_id)
select u.userid,
u.name,
o.orderid
from users u
join (select orderid,
userid,
row_number() over (partition by userid order by orderid) as rn
from orders) o
on o.userid = u.userid and o.rn = 1;
You can control which orders is being taken by tweaking the order by part of the window definition.

Simple SQL Select Query between 2 tables

I have 2 table
First table - userTable
usedID userName
1 someName
2 someOthername
Second Table - ratingTable
userID ratingValue
1 5
1 3
1 5
1 3
2 5
2 5
2 3
2 5
I need to write a SQL query that will get all userID in ascending order for number of times rated (5 star)
SELECT u.userID, u.userName, COUNT(*) AS ratingCount
FROM userTable u
INNER JOIN ratingTable r
ON u.userID = r.userID
AND r.ratingValue = 5
GROUP BY u.userID, u.userName
ORDER BY ratingCount
Here's one example:
select u.UserId
, count(r.ratingValue)
from userTable u
left join
ratingTable r
on u.userID = r.userID
and r.ratingValue = 5
group by
u.UserID
order by
count(r.ratingValue)
If the result does not require users without any five star ratings, you can even omit the userTable altogether.
I assume you mentioned 5 stars as the rating system you are using and not that you only wish to retrieve users with ratings of 5 stars.
SELECT u.userName, avg( r.ratingValue ) as averageRating
FROM userTable u
LEFT JOIN ratingTable r ON u.userID = r.userID
GROUP BY u.UserID
ORDER BY avg( r.ratingValue ) desc
This will get the average rating of each user and display their names.
userName averageRating
test1 4.5000
test2 1.7500