Order by result from another tables - php

HI i have skills tables as below
Various user add skills to their profile. Now i want to list all the skills decreasing order of their uses. Like as below
Php(10) , ASP (5) , Perl(1)
Its means 10 user added php as their skill, 5 user ASP etc.
I have stored the skills in user table in skills column with comma separated

Try this:
select id, name
from (
select *, (select sum(1) from user u where find_in_set(s.id, u.skills)) as cnt
from skills s
) t
order by cnt desc
-- limit 20

Assuming you have another table/relation to represent user_skills, that contains (for example) user_id and skill_id foreign keys, then you'd want to join this to the skills table and group the results similar to this:
select name, count(skill_id) as ranking
from skills join user_skills on skills.id = user_skills.skill_id
group by name
order by count(skill_id) desc, name asc;
It excludes skills which have not been selected by any users. If you wanted to include those too, change the join to a left join.
Edit: With the original question updated to include the definition of the users table, then perhaps this would suffice. Again you would need a left join to include skills that no users had picked.
select skills.name, count(users.id) as qty
from skills join users
on locate(concat(',', skills.id, ','), concat(',', users.skills, ',')) > 0
group by skills.name
order by count(users.id) desc, skills.name asc;

Related

MySQL Ordering from another table

I have been looking for a solution for this for over an hour now and decided to resort to asking here.
I am creating a "Twitter-like", following system for users of my Website and I wanted to be able to display each and every one of the users that a specific user follows or is followed by, I also want to then order this by the timestamp on the follow table, descending so that the latest follower is at the top.
The solutions I have come across seem to use inner joins etc. which is all well and good, but I was wondering whether there is a logical solution for my current query to do this.
Table structures:
users:
id | username
follows:
id | follower_id | following_id | timestamp
My current query:
SELECT * FROM users WHERE id IN (SELECT follower_id FROM follows WHERE following_id = $user_id) ORDER BY id ASC
Of course this will simply order by the user ID, how would I (using the current query structure), be able to add the order to list by the follows timestamp?
MySQL INNER JOIN
"SELECT users.* FROM users
INNER JOIN follows ON follows.follower_id = users.id
WHERE follows.following_id = $user_id
ORDER BY follows.timestamp DESC";
You can sort using multiple columns like this:
ORDER BY [column1] [ASC|DESC], [columm2] [ASC|DESC], ...
Therefore, edit your query's order by clause to include the second column and sort it descending.
You must use a join to add the column; here's the basic syntax of a join:
SELECT [table_name].[column_name], ...
FROM [table1]
JOIN [table2] ON [join condition]
...
Your code should look somewhat like this:
SELECT users.*
FROM users
JOIN follows ON users.id = follows.following_id
WHERE follows.following_id = $user_id
ORDER BY users.id ASC, follows.timestamp DESC
As far as I know, there is no way to do this without joining the tables; perhaps its possible to sort the returned list, but no guarantees):
SELECT * FROM users
WHERE id IN (SELECT follower_id FROM follows
WHERE following_id = $user_id
ORDER BY timestamp DESC)
ORDER BY id ASC;
The above may or may not work (I didn't test it); if it does not, you must use a join query.

Mysql group by fetch last row

SELECT * FROM conversation_1
LEFT JOIN conversation_2
ON conversation_1.c_id = conversation_2.c_id
LEFT JOIN user
ON conversation_2.user_id = user.user_id
LEFT JOIN message
ON conversation_1.c_id = message.c_id
WHERE conversation_1.user_id=1
GROUP BY message.c_id
conversation_1 conversation_2
c_id user_id c_id user_id
1 1 1 2
2 1 2 3
3 2
I have a message DB build in Mysql
I make 4 tables user, conversation_1, conversation_2, message
when user try to open his message box, it will fetch out all conversations(conversation_1)
than join to user conversation_2 and use conversation_2 to find out which user
than join to the message.
c_id user_id user_name message
1 2 Alex Hi user_1, this is user_2
2 3 John hi user_3, user_2 don't talk to me
it works fine, however I want to display the message from last row GROUP BY
currently it display the 1st row in this group.
ps.conversation_1.c_id is auto increment and the c_id will insert to conversation_2 who has join this conversation
select * from (SELECT * FROM conversation_1
LEFT JOIN conversation_2
ON conversation_1.c_id = conversation_2.c_id
LEFT JOIN user
ON conversation_2.user_id = user.user_id
LEFT JOIN message
ON conversation_1.c_id = message.c_id
WHERE conversation_1.user_id=1
order by conversation_1.c_id desc) finalData
GROUP BY message.c_id
Beware that, as documented under MySQL Extensions to GROUP BY:
MySQL extends the use of GROUP BY so that the select list can refer to nonaggregated columns not named in the GROUP BY clause. This means that the preceding query is legal in MySQL. You can use this feature to get better performance by avoiding unnecessary column sorting and grouping. However, this is useful primarily when all values in each nonaggregated column not named in the GROUP BY are the same for each group. The server is free to choose any value from each group, so unless they are the same, the values chosen are indeterminate. Furthermore, the selection of values from each group cannot be influenced by adding an ORDER BY clause. Sorting of the result set occurs after values have been chosen, and ORDER BY does not affect which values within each group the server chooses.
This is what is happening to select the message (and potentially other columns) in your existing query.
Instead, you want the groupwise maximum:
SELECT messages.* FROM messages NATURAL JOIN (
SELECT c_id, MAX(m_id) m_id FROM messages GROUP BY c_id
) t

MySQL query to find the most popular value in a column joined by another value in a second table

I have two tables:
users: user_id, user_zip
settings: user_id, pref_ex_loc
I need to find the single most popular 'pref_ex_loc' from the settings table based on a particular user_zip, which will be specified as the variable $userzip.
Here is the query that I have now and obviously it doesn't work.
$popularexloc = "SELECT pref_ex_loc, user_id COUNT(pref_ex_loc) AS countloc
FROM settings FULL OUTER JOIN users ON settings.user_id = users.user_id
WHERE users.user_zip='$userzip'
GROUP BY settings.pref_ex_loc
ORDER BY countloc LIMIT 1";
$popexloc = mysql_query($popularexloc) or die('SQL Error :: '.mysql_error());
$exlocrow = mysql_fetch_array($popexloc);
$mostpopexloc=$exlocrow[0];
echo '<option value="'.$mostpopexloc.'">'.$mostpopexloc.'</option>';
What am I doing wrong here? I'm not getting any kind of error from this either.
Give this a try:
select s.pref_ex_loc from settings s
join users u on (u.user_id = s.user_id)
where user_zip = $userzip
group by s.pref_ex_loc
order by count(*) desc
limit 1
As you said, this will give you the "single most popular 'pref_ex_loc' from the settings table based on a particular user_zip"
Well, for one thing you are missing a comma before the COUNT():
SELECT pref_ex_loc, user_id COUNT(...
You should have a comma between each field in your select-list:
SELECT pref_ex_loc, user_id, COUNT(...
I would recommend using COUNT(*) instead of COUNT(pref_ex_loc). In this case, either should give the right answer, but in MySQL COUNT(*) usually performs slightly better.
You're using outer join, but then in the WHERE clause you're testing one of the columns of users so it's effectively not an outer join anymore. In this query, I believe you simply need an INNER JOIN, unless you need to handle the possibility that none of the users reference any of your pref_ex_loc values. Read A Visual Explanation of SQL Joins.
Also, MySQL does not support FULL OUTER JOIN.
Your user_id in the select-list, when it is neither in the GROUP BY clause nor in an aggregate function, is an ambiguous field, taking its value from one arbitrary row in the group. You should remove user_id from the select-list.
Sort by the countloc DESC to get the greatest value first.
So here's what I see as a better query:
SELECT pref_ex_loc, COUNT(*) AS countloc
FROM settings INNER JOIN users ON settings.user_id = users.user_id
WHERE users.user_zip='$userzip' GROUP BY settings.pref_ex_loc
ORDER BY countloc DESC LIMIT 1
this will allow values (duplicate most popular) with the highest pref_ex_loc to be shown in the list.
It doesn't use LIMIT, because LIMIT forces the maximum number of rows to be shown. Now, here's the question, What if there are two or more rows that ties up with the most popular pref_ex_loc?
SELECT b.pref_ex_loc
FROM users a
INNER JOIN settings b
ON a.user_ID = b.user_ID
WHERE a.user_zip = 1 -- change the value here
GROUP BY b.pref_ex_loc
HAVING COUNT(*) =
(
SELECT MAX(totalCount)
FROM
(
SELECT b.pref_ex_loc, COUNT(*) totalCount
FROM users a
INNER JOIN settings b
ON a.user_ID = b.user_ID
WHERE a.user_zip = 1 -- change the value here
GROUP BY b.pref_ex_loc
) s
)
SQLFiddle Demo
SQLFiddle Demo (with duplicate most popular)
Try with this query:
SELECT user_id, COUNT(pref_ex_loc) AS countloc
FROM users LEFT JOIN settings ON users.user_id = settings.user_id
WHERE users.user_zip='$userzip' GROUP BY user_id ORDER BY countloc LIMIT 1

Parent Ordered by number of child tables

I have a tbl_user , which contains information about user, and I have a tbl_article, which contains articles + the ID of the tbl_user
We have a parent-child relation, because every user may have many articles, that's why I included user_id in the articles table.
I'd like to list the 10 users that have most articles... I've searched everywhere though I couldn't find it...I've thought about it , but in vain, I'm not good in SQL Queries.
Thank you in advance
SELECT TOP(10)
tbl_user.id,
COUNT(tbl_article.user_id)
FROM
tbl_user
LEFT JOIN
tbl_article
ON tbl_user.id = tbl_article.user_id
GROUP BY
tbl_user.id
ORDER BY
COUNT(tbl_article.user_id) DESC
LIMIT
10
Depending on which RDBMS you use, you may need TOP(10) or LIMIT 10, etc. I included both so you can see, but only use the one that is used by your RDBMS ;)
SELECT TOP 10
UserID, COUNT(Article)
FROM tbl_User u
INNER JOIN tbl_Article a
ON a.Userid = u.userid
GROUP BY userid
ORDER BY COUNT(article) DESC
All you need is a GROUP BY and a JOIN.
If there is a potential for users with 0 articles that you want to include, you should use a LEFT JOIN.
Optionally you can also COUNT(DISTINCT Article) if there is a concern about duplicates.

trying to optimize mysql query but when i add ORDER BY its takes long time

this is my query
SELECT U.id AS user_id,C.name AS country,
CASE
WHEN U.facebook_id > 0 THEN CONCAT(F.first_name,' ',F.last_name)
WHEN U.twitter_id > 0 THEN T.name
WHEN U.regular_id > 0 THEN CONCAT(R.first,' ',R.last)
END AS name,
FROM user U LEFT OUTER JOIN regular R
ON U.regular_id = R.id
LEFT OUTER JOIN twitter T
ON U.twitter_id = T.id
LEFT OUTER JOIN facebook F
ON U.facebook_id = F.id
LEFT OUTER JOIN country C
ON U.country_id = C.id
WHERE (CONCAT(F.first_name,' ',F.last_name) LIKE '%' OR T.name LIKE '%' OR CONCAT(R.first,' ',R.last) LIKE '%') AND U.active = 1
LIMIT 100
its realy fast, but in the EXPLAIN it don't show me it uses INDEXES (there is indexes).
but when i add ORDER BY 'name' before the LIMIT its takes long time why? there is a way to solve it?
tables: users 150000, regular 50000, facebook 50000, twitter 50000, country 250 and growing!
It takes a long time because it's a composite column, not a table column. The name column is a result of a case selection, and unlike simple selects with multiple join, MySQL has to use a different sorting algorithm for this kind of data.
I'm talking from ignorance here, but you could store the data in a temporary table and then sort it. It may go faster since you can create indexes for it but it won't be as fast, because of the different storage type.
UPDATE 2011-01-26
CREATE TEMPORARY TABLE `short_select`
SELECT U.id AS user_id,C.name AS country,
CASE
WHEN U.facebook_id > 0 THEN CONCAT(F.first_name,' ',F.last_name)
WHEN U.twitter_id > 0 THEN T.name
WHEN U.regular_id > 0 THEN CONCAT(R.first,' ',R.last)
END AS name,
FROM user U LEFT OUTER JOIN regular R
ON U.regular_id = R.id
LEFT OUTER JOIN twitter T
ON U.twitter_id = T.id
LEFT OUTER JOIN facebook F
ON U.facebook_id = F.id
LEFT OUTER JOIN country C
ON U.country_id = C.id
WHERE (CONCAT(F.first_name,' ',F.last_name) LIKE '%' OR T.name LIKE '%' OR CONCAT(R.first,' ',R.last) LIKE '%') AND U.active = 1
LIMIT 100;
ALTER TABLE `short_select` ADD INDEX(`name`); --add successive columns if you are going to order by them as well.
SELECT * FROM `short_select`
ORDER BY 'name'; -- same as above
Remember temporary tables are dropped upon connection termination, so you don't have to clean them, but you should anyway.
Without actually knowing your DB structure, and assuming you have all of the proper indexes on everything. An Order By statement takes some variable amount of time to sort the elements being returned by a query (index or not). If it is only 10 rows, it will seem almost instant, if you get 2000 rows, it will be a little slower, if you are sorting 15k rows joined across multiple tables, it is going to take some time to sort the returned result. Also make sure your adding indexes to the fields your sorting by. You may want to take the desired result and store everything in a presorted stub table for faster querying later as well (if you query this sorted result set often)
You need to create first 100 records from each name table separately, then union the results, join them with user and country, order and limit the output:
SELECT u.id AS user_id, c.name AS country, n.name
FROM (
SELECT facebook_id AS id, CONCAT(F.first_name, ' ', F.last_name) AS name
FROM facebook
ORDER BY
first_name, last_name
LIMIT 100
UNION ALL
SELECT twitter_id, name
FROM twitter
WHERE twitter_id NOT IN
(
SELECT facebook_id
FROM facebook
)
ORDER BY
name
LIMIT 100
UNION ALL
SELECT regular_id, CONCAT(R.first, ' ', R.last)
FROM regular
WHERE regular_id NOT IN
(
SELECT facebook_id
FROM facebook
)
AND
regular_id NOT IN
(
SELECT twitter_id
FROM twitter
)
ORDER BY
first, last
LIMIT 100
) n
JOIN user u
ON u.id = n.id
JOIN country с
ON c.id = u.country_id
Create the following indexes:
facebook (first_name, last_name)
twitter (name)
regular (first, last)
Note that this query orders slightly differently from your original one: in this query, 'Ronnie James Dio' would be sorted after 'Ronnie Scott'.
The use of functions on the columns prevent indexes from being used.
CONCAT(F.first_name,' ',F.last_name)
The result of the function is not indexed, even though the individual columns may be. Either you have to rewrite the conditions to query the name columns individually, or you have to store and index the result of that function (such as a "full name" column).
The index on [user.active] is unlikely to help you if most of the users are active.
I don't know what your application is all about, but I wonder if it hadn't been easier if you ditched the foreign keys in User table and instead put the UserID as a foreign key in the other tables instead.

Categories