First at all i am nood into SQL thing, Now i am working on a class project where
I have some tables like
Table user
user_id | username | name
1 | nihan | Nihan Dip
2 | dip | Meaw ghew
more | more | more
Table Friend
you | friend_id
1 | 2
1 | 27
2 | 9
more | more
Table Follow
user_id | follows
1 | 99
7 | 34
Table post
post_id | user_id | type | content | post_time
1 | 1 | text | loren toren | timestamp
2 | 2 | text | ipsum | timestamp
Now i want to get post by users friend and who he follows and offcourse his so i made this SQL
SELECT
username, name,content, post_time
FROM
post
INNER JOIN
user ON user.user_id = post.user_id
WHERE
post.user_id IN (SELECT
friend_id
FROM
friend
WHERE
you = 1
UNION ALL
SELECT
follows
FROM
follow
WHERE
user_id = 1)
OR post.user_id = 1
ORDER BY post_time DESC
LIMIT 10
this query works just fine. I just wanted to know is there anymore optimization could be done? Then how? Please teach me :)
Instead of using IN try it with JOIN add add few more indexes.
SELECT DISTINCT u.name, u.username,
p.content, p.post_time
FROM post p
INNER JOIN user u
ON u.user_id = p.user_id
INNER JOIN
(
SELECT friend_id id
FROM friend
WHERE you = 1
UNION ALL
SELECT follows id
FROM follow
WHERE user_id = 1
) s ON p.user_id = s.ID
ORDER BY post_time DESC
LIMIT 10
Related
Similar to stackoverflow, I have a database of users who vote, comment, and make other actions. I am trying to return a sorted result of the top 10 users who have made the most actions based on the combined count of all of the actions a user has made, along with the actual count of total actions said user made.
Below is my table structure.
Users Table
Typical users data such as an incrementing id, username, email, etc.
| id | username |
-----------------
| 1 | bob |
| 2 | jane |
Votes Table
Has an incrementing id, user_id fk and type of vote made.
| id | user_id | type |
| 1 | 1 | up_vote |
| 2 | 2 | up_vote |
Comments Table
Same as the votes table, typical stuff here.
| id | user_id | comment |
---------------------------------
| 1 | 1 | hello, world |
| 1 | 1 | goodbye, world |
Intended results:
results needed
| total_actions | user_id | username |
-------------------------------------|
| 3 | 1 | bob |
| 1 | 2 | jane |
What I actually know how to do, albeit probably not the most efficient way...
Users sorted by most votes, along with the count
select `users`.*,
(
select count(*)
from `votes`
where `users`.`id` = `votes`.`user_id`
) as `votes_count`
from `users`
order by `votes_count` desc
limit 10
Users sorted by most comments, along with the count
select `users`.*,
(
select count(*)
from `comments`
where `users`.`id` = `comments`.`user_id`
) as `comments_count`
from `users`
order by `comments_count` desc
limit 10
Any help would be greatly appreciated!
You can left join aggregate queries that compute the total votes and comments per user, and sort in the outer query, like so:
select
coalesce(v.cnt, 0) + coalesce(c.cnt, 0) total_actions,
u.id,
u.username
from users u
left join (select user_id, count(*) cnt from votes group by user_id) v
on v.user_id = u.id
left join (select user_id, count(*) cnt from comments group by user_id) c
on c.user_id = u.id
order by total_actions desc
limit 10
While I prefer GMB's method (using LEFT JOIN with each subquery) I'll show here how to combine your existing queries. Just use both correlated subqueries, and add them together to get the total.
select `users`.*,
(
select count(*)
from `votes`
where `users`.`id` = `votes`.`user_id`
) +
(
select count(*)
from `comments`
where `users`.`id` = `comments`.`user_id`
) as total_actions
from `users`
order by total_actions desc
limit 10
Suppose I have a table named users consist of columns: user_id, user_name, user_created_by.
+------------------+----------------------+-------------------+
| user_id + user_name + user_created_by +
+------------------+----------------------+-------------------+
| 1 | John | 1 |
| 2 | Ann | 1 |
| 3 | Paul | 2 |
| 4 | King | 2 |
| 5 | Dirk | 3 |
+------------------+----------------------+-------------------+
The value of user_created_by is the user_id who created that record. Now, I want to make a query that results one specific row with added column let's say user_created_by_name which is the user_name of the user_id from the user_created_by. Suppose we want to get "Paul"'s record with who (the name) create it (temporary new column). For ease of understanding this is my expected result:
+----------+--------------+-------------------+------------------------+
| user_id | user_name | user_created_by | user_created_by_name |
+----------+--------------+-------------------+------------------------+
| 3 | Paul | 2 | Ann |
+----------+--------------+-------------------+------------------------+
this is my query using codeigniter:
$query=$this->db->query("SELECT *,
(SELECT user_name FROM users WHERE user_id = user_created_by)
AS "user_created_by_name" FROM users WHERE user_id=3);
But my result are:
+----------+--------------+-------------------+------------------------+
| user_id | user_name | user_created_by | user_created_by_name |
+----------+--------------+-------------------+------------------------+
| 3 | Paul | 2 | NULL |
+----------+--------------+-------------------+------------------------+
You culd use a self join (join the same table two time) using alias for fere to the tables as different sets of data
SELECT a.user_id, a.user_name, a.user_created_by, b.user_name as user_created_by_name
from users a
inner join user b on a.user_created_by = b.user_id
where a.user_id = 3
use self join
select u1.user_id, u1.name as user_name,
u2.user_created_by
,u2.user_name as createdby from users u1
join users u2 on u1.user_id=u2.user_created_by
where u1.user_id=3
You can solve this problem using a JOIN.
$sql = "SELECT users.user_id, users.user_name, user_created_by_name.user_name,
FROM users JOIN users AS user_created_by_name ON users.user_id = user_created_by_name.user_id WHERE users.user_id = 3";
$query=$this->db->query($sql);
If you you have users that were not created by another user use a LEFT JOIN instead:
$sql = "SELECT users.user_id, users.user_name, user_created_by_name.user_name,
FROM users LEFT JOIN users AS user_created_by_name ON users.user_id = users.user_id WHERE user_created_by_name.user_id = 3";
$query=$this->db->query($sql);
This will work:
SELECT a.user_id as User_id,
a.user_name as Name,
b.user_id as Created_by_user_id,
b.user_name as Created_by_name
FROM users AS a
INNER JOIN users AS b
ON a.user_id = b.user_created_by
WHERE a.user_id = 3
It is called a self-join, which is used when combining two records of the same table.
I've sql with NOT EXIST and it works very slowly in big db:
SELECT *
FROM
(
SELECT * FROM profiles ORDER BY id DESC
/* I need this order HERE! More info: https://stackoverflow.com/q/43516402/2051938 */
) AS users
WHERE
NOT EXISTS (
SELECT *
FROM request_for_friendship
WHERE
(
request_for_friendship.from_id = 1
AND
request_for_friendship.to_id = users.id
)
OR
(
request_for_friendship.from_id = users.id
AND
request_for_friendship.to_id = 1
)
)
LIMIT 0 , 1;
And I think I need to get request_for_friendship with some WHERE and after that check NOT EXIST, like this:
SELECT users.*
FROM
(
SELECT * FROM profiles ORDER BY id DESC
) AS users,
(
SELECT *
FROM request_for_friendship
WHERE
request_for_friendship.from_id = 1
OR
request_for_friendship.to_id = 1
) AS exclude_table
WHERE
NOT EXISTS
(
SELECT *
FROM exclude_table /* #1146 - Table 'join_test.exclude_table' doesn't exist */
WHERE
request_for_friendship.from_id = users.id
OR
request_for_friendship.to_id = users.id
)
LIMIT 0 , 1;
But it doesn't work: #1146 - Table 'join_test.exclude_table' doesn't exist
My tables:
1) profiles
+----+---------+
| id | name |
+----+---------+
| 1 | WILLIAM |
| 2 | JOHN |
| 3 | ROBERT |
| 4 | MICHAEL |
| 5 | JAMES |
| 6 | DAVID |
| 7 | RICHARD |
| 8 | CHARLES |
| 9 | JOSEPH |
| 10 | THOMAS |
+----+---------+
2) request_for_friendship
+----+---------+-------+
| id | from_id | to_id |
+----+---------+-------+
| 1 | 1 | 2 |
| 2 | 1 | 3 |
| 3 | 1 | 8 |
| 5 | 4 | 1 |
| 6 | 9 | 1 |
+----+---------+-------+
How to do some like this or better for perfomance?
p.s. I need to get only 1 row from table
Demo: http://rextester.com/DTA64368
I've already tried LEFT JOIN, but I've problem with order with him. mysql: how to save ORDER BY after LEFT JOIN without reorder?
First, do not use subqueries unnecessarily. Second, split the NOT EXISTS into two conditions:
SELECT p.*
FROM profiles p
WHERE NOT EXISTS (SELECT 1
FROM request_for_friendship rff
WHERE rff.from_id = 1 AND
rff.to_id = p.id
) AND
NOT EXISTS (SELECT 1
FROM request_for_friendship rff
WHERE rff.to_id = 1 AND
rff.from_id = p.id
)
ORDER BY id DESC;
This can now make use of two indexes: request_for_friendship(to_id, from_id) and request_for_friendship(from_id, to_id). Each index is needed for one of the NOT EXISTS conditions.
I still think there's ways to optimize this as 'in' is generally slower.
SELECT *
FROM profiles p
WHERE NOT EXISTS (SELECT 1
FROM request_for_friendship
WHERE (request_for_friendship.from_id,
request_for_friendship.to_id)
in ((1,p.id),
(p.id,1))
)
Get rid of the id in request_for_friendship. It wastes space and performance. The table has a "natural" PRIMARY KEY, which I will get to in a moment.
Since it seems that the relationship seems to commutative, let's make use of that by sorting the from and to -- put the smaller id in from and the larger is to. See LEAST() and GREATEST() functions.
Then you need only one EXISTS(), not two. And have
PRIMARY KEY(from_id, to_id)
Now to rethink the purpose of the query... You are looking for the highest id that is not "related" to id #1, correct? That sounds like a LEFT JOIN.
SELECT
FROM profiles AS p
LEFT JOIN request_for_friendship AS r ON r.to = p.id AND r.from = 1
WHERE r.to IS NULL
ORDER BY id DESC
LIMIT 1;
This may run about the same speed as the EXISTS -- Both walk through profiles from the highest id, reaching into the other table to see if a row is there.
If there is no such id, then the entire profiles table will be scanned, plus a the same number of probes into the other table.
I'm trying to build a commenting system on my website but having issues with ordering the comments correctly. This is a screenshot of what I had before it went wrong:
And this is the query before it went wrong:
SELECT
com.comment_id,
com.parent_id,
com.is_reply,
com.user_id,
com.comment,
com.posted,
usr.username
FROM
blog_comments AS com
LEFT JOIN
users AS usr ON com.user_id = usr.user_id
WHERE
com.article_id = :article_id AND com.moderated = 1 AND com.status = 1
ORDER BY
com.parent_id DESC;
I now want to include each comment's votes from my blog_comment_votes table, using a LEFT OUTER JOIN, and came up with this query, which works, but screws with the order of results:
SELECT
com.comment_id,
com.parent_id,
com.is_reply,
com.user_id,
com.comment,
com.posted,
usr.username,
IFNULL(c.cnt,0) votes
FROM
blog_comments AS com
LEFT JOIN
users AS usr ON com.user_id = usr.user_id
LEFT OUTER JOIN (
SELECT comment_id, COUNT(vote_id) as cnt
FROM blog_comment_votes
GROUP BY comment_id) c
ON com.comment_id = c.comment_id
WHERE
com.article_id = :article_id AND com.moderated = 1 AND com.status = 1
ORDER BY
com.parent_id DESC;
I now get this order, which is bizarre:
I tried adding a GROUP BY clause on com.comment_id but that failed too. I can't understand how adding a simple join can alter the order of results! Can anybody help back on the correct path?
EXAMPLE TABLE DATA AND EXPECTED RESULTS
These are my relevant tables with example data:
[users]
user_id | username
--------|-----------------
1 | PaparazzoKid
[blog_comments]
comment_id | parent_id | is_reply | article_id | user_id | comment
-----------|-----------|----------|------------|---------|---------------------------
1 | 1 | | 1 | 1 | First comment
2 | 2 | 1 | 1 | 20 | Reply to first comment
3 | 3 | | 1 | 391 | Second comment
[blog_comment_votes]
vote_id | comment_id | article_id | user_id
--------|------------|------------|--------------
1 | 2 | 1 | 233
2 | 2 | 1 | 122
So the order should be
First comment
Reply to first comment +2
Second Comment
It's difficult to say without looking at your query results, but my guess is that it's because you are only ordering by parent id and not saying how to order when two records have the same parent id. Try changing your query to look like this:
SELECT
com.comment_id,
com.parent_id,
com.is_reply,
com.user_id,
com.comment,
com.posted,
usr.username,
COUNT(c.votes) votes
FROM
blog_comments AS com
LEFT JOIN
users AS usr ON com.user_id = usr.user_id
LEFT JOIN
blog_comment_votes c ON com.comment_id = c.comment_id
WHERE
com.article_id = :article_id AND com.moderated = 1 AND com.status = 1
GROUP BY
com.comment_id,
com.parent_id,
com.is_reply,
com.user_id,
com.comment,
com.posted,
usr.username
ORDER BY
com.parent_id DESC, com.comment_id;
I have two tables :
users:
___________________________
|user_id | username |
|_______________|___________|
| 1 | Dolly |
| 2 | Didi |
|_______________|___________|
forum:
_____________________________________________________________
|match_static_id| comment | timpstamp | user_id |
|_______________|___________|______________________|__________|
| 1 | Hi | 2013-07-10 12:15:03 | 2 |
| 1 | Hello | 2013-07-09 12:14:44 | 1 |
|_______________|___________|______________________|__________|
this query is working fine and it uses just thw forum table:
SELECT forum.match_static_id,
count(forum.match_static_id) 'comments_no', max(forum.timestamp)'timestamp'
FROM forum
GROUP BY forum.match_static_id
Order BY timestamp DESC
But the following query is using two tables :
SELECT forum.match_static_id,
count(forum.match_static_id) 'comments_no', max(forum.timestamp)'timestamp', users.username
FROM forum
INNER JOIN users on users.id = forum.user_id
GROUP BY forum.match_static_id
Here I want to get the user of the max(timestamp) but i get the wrong user could any body give my a clue about this, please?
Order BY timestamp DESC
Try this:
SELECT f1.match_static_id,
f2.comments_no,
f2.maxtimestamp,
users.username
FROM forum AS f1
INNER JOIN
(
SELECT match_static_id,
max(timestamp) maxtimestamp,
count(comment) AS comments_no
FROM Forum
GROUP BY match_static_id
) AS f2 ON f1.match_static_id = f2.match_static_id
AND f1.timestamp = f2.maxtimestamp
INNER JOIN users on users.user_id = f1.user_id;
See it in action here:
SQL Fiddle Demo