Subquery - records are not in ordered form - mysql

I have three tables:
user: id, name
keyword: id, name
userkeyword: id, user_id, keyword_id
I want to execute query in following way:
Display those users whose keyword/s are matched with the login user's
keywords. In the order of maximum number of keyword matched user
should display first
e.g : If userA having 4 matched keywords, userB having 8, userC having 1, userD having 6 then the result should be in the order of,
userB
userD
userA
userC
For that I have done with this query (assume login user's id is 1):
select *
from user
where id IN (
select user_id
from userkeywords
where keyword_id IN (
select keyword_id
from userkeywords
where user_id=1)
group by user_id
order by count(keyword_id) desc)
AND id != 1
Here the result is getting perfect but the order is not correct. I have merged two queries in following manner"
select *
from user
where id IN (?)
AND id!=1
+
select user_id
from userkeywords
where keyword_id IN (
select keyword_id
from userkeywords
where user_id=1)
group by user_id
order by count(keyword_id) desc
Second query returns user_id in correct order but when I merged both queries, order was changed (wrong).
Hope I have mentioned my query properly with enough detail.

A subquery returns an unordered set, so the order by in a subquery only matters for its limit clause, if there is any. Any database other than MySQL would give an error message for a purely decorative sort order.
There's no way to sort on a column that only exists in the where clause. You'd have to rewrite the query. One option is to replace your in conditions with joins:
select uk2.name
from userkeywords uk1
join userkeywords uk2
on uk1.keyword_id = uk2.keyword_id
and uk1.user_id <> uk2.user_id
join user u2
on u2.id = uk2.user_id
where uk1.user_id = 1
group by
uk2.name
order by
count(*) desc

This should do it.
select uk.user_id, u.name
from userkeywords uk
left join user u on u.id = uk.user_id
where uk.keyword_id IN (
select keyword_id
from userkeywords
where user_id=1)
group by uk.user_id
order by count(uk.keyword_id) desc) AND uk.user_id != 1
Also, JOIN provides better performance.

I would use an inner join to select the correct rows:
SELECT *
FROM user
INNER JOIN (
SELECT * FROM userkeyword
WHERE keyword_id IN (
SELECT keyword_id
FROM userkeyword
WHERE user_id=1
)
) uk
ON user.id = uk.user_id
GROUP BY u.id
ORDER BY count(*) DESC;

Related

How to join a table with specific conditions

I would like to select data from a table like this (the table name is conversations_users) :
I would like to be able to retrieve a conversation ID that includes only two users. As instance, if I search a conversation specific to users 1 and 3 the conversation number 6 should be the unique result, because the conversation 5 also includes user 2.
I have tried to perform a request like
SELECT * FROM conversations_users AS table1 JOIN
conversations_users AS table2 ON
table1.conversation_ID = table2.conversationID
WHERE table1.userID = 3 AND
table2.userID = 1
But it returns both conversations 5 and 6. How can I fix that ?
Thank you in advance,
Pierre
Add the ON clause:
SELECT * FROM conversations_users AS table1 JOIN
conversations_users AS table2
ON table1.conversation_ID = table2.conversation_ID
WHERE table1.userID = 3 AND
table2.userID = 1
Update:
To get only coversations, where only 1 and 3 are involved, you can use having clause:
SELECT table1.conversation_ID FROM conversations_users AS table1 JOIN
conversations_users AS table2
ON table1.conversation_ID = table2.conversation_ID
WHERE table1.userID = 3 AND
table2.userID = 1
Group by table1.conversation_ID
having Count(*) = 2
The query you need looks like:
SELECT conversation_ID, GROUP_CONCAT(DISTINCT userID ORDER BY userID) as users
FROM conversations_users
GROUP BY conversation_ID
HAVING users = '1,3'
The GROUP BY clause groups the rows having the same conversation_ID and from each group it generates a new record that contains the conversation_ID and the distinct values of userID, in ascending order, concatenated with comma (,).
The HAVING clause keeps only those records that have '1,3' in the column users computed by the GROUP BY clause.
The query produces the output you need but it is not efficient because it reads the entire table. It could be more efficient by picking first the conversations of users 1 and 3 and then applying the above only to them.
It looks like this:
SELECT conversation_ID, GROUP_CONCAT(DISTINCT userID ORDER BY userID) as users
FROM (
SELECT *
FROM conversations_users
WHERE userID in (1, 3)
) conversations
GROUP BY conversation_ID
HAVING users = '1,3'
In order to work faster than the previous query, the conversations_users must have an index on the userID column.
If you want to restrict to those conversations which involve exactly n number of users. I think below generic query should work. Replacing 'n' as per requirement.
select *
from conversations_users
where conversation_id IN (select conversation_id
from conversations_users
group by conversation_id
having count(userid) = 2)
Thanks,
Amitabh
The inner select grabs all conversationIDs with other users than 1 or 3
the outer select (with distinct) collects all conversations wich are NOT in this subset
SELECT DISTINCT conversationID
FROM conversations_users t1
WHERE conversationID NOT IN ( SELECT conversationID
FROM conversations_users
WHERE userID NOT in (1, 3)
)
You can use join with where condition in this case.
SELECT #, userid ,conversation_ID FROM user AS table1 JOIN
conversations_users AS table2
ON user_ID = conversation_ID
WHERE table1.userID = 3 AND
table2.userID = 1
Group by conversation_ID
You can apply suitable condition by where clause instead of group by

SQL that return specific ids in group of 5

I have articles table with id and created_by
And users table with id and name
I need to write SQL that retrieve 5 or less articles for users 1,2,3,4,5,6,...
If I use limit 5, it will limit the result to 5 records, but I need to limit the result to 5 for each specific user.
I can use something like this:
(SELECT id, created_by FROM content where created_by = 1 limit 5)
union
(SELECT id, created_by FROM content where created_by = 2 limit 5)
But I have about 20 users, so I do not think it is efficient.
You can use variables to simulate ROW_NUMBER window function, not available in MySQL:
SELECT ArticleId, UserId, UserName
FROM (
SELECT a.id AS ArticleId, u.id AS UserId, u.name AS UserName,
#row_number:= IF (#uid = u.id,
IF (#uid:=u.id, #row_number+1, #row_number+1),
IF (#uid:=u.id, 1, 1)) AS rn
FROM articles AS a
CROSS JOIN (SELECT #row_number:=0, #uid:=0) vars
INNER JOIN users AS u ON a.created_by = u.id
WHERE u.id IN (1, 2, 3, 4, 5, 6)
ORDER BY u.id, a.id ) t
WHERE t.rn <= 5
The above query will pick the top 5 articles per user (as per articleId ordering). If a user has less than 5 articles, then all articles of the user are selected.
Note the usage of nested conditionals in order to guarantee that #uid is first read and subsequently set.
Demo here
Something like this should work... (abstract implementation)
SELECT id_user FROM TABLE
WHERE id_user IN
(SELECT id_user
FROM TABLE
GROUP BY id_user
HAVING count(articles)<=5)

can this sql query be simplified?

I have 2 tables. MySql
users : A_ID,name
event : B_ID, A_ID,cat_id,date
Now I want to get all users , who participated more at events on a given period of time, and need to add based on category too. I am doing something like this :
select name from users ,
(select A_id, count(*)
from event
where date<=givendate
group by A_id
order by count(*) desc ) e
where users.A_id=e.a_id
limit 0,5
Is there any easy and prof way to write that script ?
Thanks
Your query looks OK apart from a few minor points:
Your ORDER BY and LIMIT should be in the outer select otherwise the order of the results is indeterminate.
Use the JOIN keyword to join two tables.
Try this:
SELECT u.name
FROM users AS u
JOIN
(
SELECT A_id, COUNT(*) AS cnt
FROM event
WHERE date <= givendate
AND cat_id = 42
GROUP BY A_id
) AS e
USING (A_id)
ORDER BY e.cnt DESC
LIMIT 5

How to find most repeating element from a table?

I have a table tbl_user and I want to find which user_name(s) have the most repeating values in the table. I only want to retrieve those user_id(s).
I also need a separate query to find the count the of duplicate user_name(s) in the table .
First query to find the biggest amount of repeated usernames:
SELECT user_name, COUNT(user_id) AS amount
FROM tbl_user
GROUP BY user_name
ORDER BY amount DESC
And then grab id`s:
# faster way using username from last query
SELECT user_id
FROM tbl_user
WHERE user_name = '$user_name'
# longer way
SELECT user_id
FROM tbl_user
WHERE user_name = (
SELECT user_name
FROM tbl_user
GROUP BY user_name
ORDER BY COUNT(amount) DESC
LIMIT 1
)
To find the count of duplicates:
SELECT count(id) AS num_duplicates FROM tbl_users GROUP BY user_name ORDER BY num_duplicates DESC
To find the user_id's of the most duplicated user_name:
SELECT user_id FROM tbl_user
WHERE username IN ( SELECT user_id
FROM tbl_users
GROUP BY user_name
ORDER BY count(id) DESC LIMIT 0, 1)
SELECT COUNT(*) FROM tbl_user
GROUP BY tbl_user.user_name
Will give you the number of duplicate user_names. You can use a scripting language to select out the highest one if you like, or just look at the list if it's not something you need automated.
If you mean you want to count the occurences of a particular username, something like this may work for you.
select user_name,user_id,count(*) as mycount from tbl_user group by user_name order by mycount desc limit 5
This should get the top 5 entries
You could select the user_names which are duplicated with this SQL:
SELECT COUNT(1) AS duplicates, user_name
FROM tbl_user
GROUP BY user_name
HAVING duplicates > 1
Having done this, you can then use it as a subquery to find out the count of duplicate user_names
SELECT COUNT(1) AS count_of_user_names, SUM(duplicates) AS duplicate_records
FROM (
SELECT COUNT(1) AS duplicates, user_name
FROM tbl_user
GROUP BY user_name
HAVING duplicates > 1
) AS subquery
Or use it to find out the user ids:
SELECT user_id
FROM tbl_user
WHERE user_name IN (
SELECT user_name
FROM tbl_user
GROUP BY user_name
HAVING COUNT(1) > 1
)

How to select latest row by a user_id

I have the following table:
content: id, user_id, markdown
How do I select the latest id created by a certain user?
SELECT * FROM content
WHERE user_id = 2
So if rows 12,13 and 14 have user_id as 2, I want to select row 14
SELECT * FROM content
WHERE user_id = 2
ORDER BY id DESC
LIMIT 1
Also, if you have a table of users and you want to get the latest record for each:
SELECT c.*
FROM content c
INNER JOIN (SELECT user_id, max(id) as maxid
FROM content
GROUP BY user_id) as c1 on c.id = c1.maxid
In MySQL I think you'd need:
SELECT *
FROM content
WHERE user_id = 2
ORDER BY id DESC
LIMIT 1
You could do a subselect and select the MAX timestamp (which may be safer) - but it doesn't look like you have a timestamp.