mysql query within a query with privacy condition check - php

I have 3 tables.
myMembers
------------------------------------
id | username | privacy
------------------------------------
1 | userA | 0
2 | userB | 1
3 | userC | 0
4 | userD | 1
following
--------------------------------
id | user_id | follower_id
--------------------------------
1 | 2 | 1
posts
-------------------------------------
id | userID | username | statusMsg
--------------------------------------
1 | 4 | userD | Issac Newton is genius
2 | 2 | userB | Newton Saw apple
3 | 3 | userC | Newtonian Physics
4 | 1 | userA | Calculus came from Sir Newton
There is a search field. When a logged in user searches for 'keyword' in table 'posts', I want to omit results from those users who has set his privacy to '1' and WHERE searcher is not following user B.
The query should logically do this.
SELECT * from posts WHERE (match the keyword)
AND (
if (poster's privacy (which is set in myMembers)==1){
if (seacher is following poster){
select this post
}
}
else { select this post
}
)
LIMIT results to 5 rows
So for a keyword "Newton",
if userA is searching, rows 2,3,4 from 'posts' should be returned.
if userD is searching, only rows 1, 3 and 4 from 'posts' should be returned,
based on privacy and following
Edit: Tagging for future searches: IF condition within WHERE Clause in mySql

Please, try this query (also on SQL Fiddle):
SELECT p.id, p.user_id, m.username, m.privacy,
searcher.username "Searcher", p.status_msg
FROM posts p
JOIN members m ON m.id = p.user_id
LEFT JOIN following f ON p.user_id = f.user_id
JOIN members searcher ON searcher.username = 'userA'
WHERE (m.privacy = 0 OR (m.privacy = 1 AND f.follower_id = searcher.id)
OR m.id = searcher.id)
AND p.status_msg LIKE '%New%'
ORDER BY p.id
LIMIT 5;
I removed username field from posts table, as it is redundant. Also, I named tables and columns slightly different, so query might need cosmetic changes for your schema.
The first line in the WHERE clause is the one that you're looking for, it selects posts in the following order:
First posts from members without privacy;
Then posts from members that are followed by the current searcher;
Finally, posts of the member himself.
EDIT:
This query is using original identifiers:
SELECT p.id, p.`userID`, m.username, m.privacy,
searcher.username "Searcher", p.`statusMsg`
FROM posts p
JOIN `myMembers` m ON m.id = p.`userID`
LEFT JOIN following f ON p.`userID` = f.user_id
JOIN `myMembers` searcher ON searcher.username = 'userD'
WHERE (m.privacy = 0 OR f.follower_id = searcher.id OR m.id = searcher.id)
AND p.`statusMsg` LIKE '%New%'
ORDER BY p.id
LIMIT 5;
EDIT 2:
To avoid duplicates in case there're several followers for the user from the posts table, join and filtering conditions should be changed the following way (on SQL Fiddle):
SELECT p.id, p.user_id, m.username, m.privacy,
searcher.username "Searcher", p.status_msg
FROM posts p
JOIN members m ON m.id = p.user_id
JOIN members searcher ON searcher.username = 'userC'
LEFT JOIN following f ON p.user_id = f.user_id
AND follower_id = searcher.id
WHERE (m.privacy = 0 OR (m.privacy = 1 AND f.id IS NOT NULL)
OR m.id = searcher.id)
ORDER BY p.id
LIMIT 5;

Try the following:
SET #my_user_id= 1;
SELECT * FROM posts p
INNER JOIN myMembers m ON p.user_id= m.id
WHERE statusMsg LIKE '%'
AND privacy=0
AND user_id IN (SELECT follower_id FROM following f WHERE f.user_id=#my_user_id)
LIMIT 5

try this:
SELECT a.*
FROM posts a
LEFT JOIN (SELECT user_id
FROM following a1
INNER JOIN myMembers b1
ON a1.follower_id = b1.id
WHERE a1.follower_id = 1 AND
b1.privacy = 1
) b
ON a.userID = b.user_id AND
WHERE a.statusMsg LIKE '%search%' AND
b.user_id IS NULL
LIMIT 5;
or better approach without subquery:
SELECT a.*
FROM posts a
LEFT JOIN myMembers b
ON a.userID = b.id AND
b.privacy = 1
LEFT JOIN following c
ON a.userID = c.user_id AND
c.follower_id = 1
WHERE a.statusMsg LIKE '%search%' AND
b.id IS NULL AND
c.user_id IS NULL
LIMIT 5;
See: A Visual Explanation of SQL Joins

Related

Stock with slow SQL query

I have been working with the same SQL query for a couple of hours and it is finally working. But, it is very slow.. I have been trying to optimize it, but no luck, any help. Here is the query (Lots of left joins...):
$sql ="SELECT u.id, u.display_name, IFNULL(SUM(r.total_rating)/COUNT(r.total_rating), 0) AS avg_rating, s.title AS study FROM users u
LEFT JOIN rating r ON u.id = r.user_id
LEFT JOIN usermeta m ON u.id = m.user_id
LEFT JOIN usermeta m1 ON u.id = m1.user_id
LEFT JOIN studies s ON m.meta_value = s.id
WHERE m.meta_key = 'study' AND m1.meta_key = 'subjects' AND m1.meta_value REGEXP '$subjectsvalues'
GROUP BY u.id, r.total_rating
ORDER BY avg_rating DESC
LIMIT 10";
Table structure for user table:
id | display_name | email
-------------------------
1 | Khar | ...
2 | SantaCruz | ...
Table structure for rating table:
id | rating_title | total_rating | user_id
-------------------------------------------
1 | dffd | 5 | 1
2 | fddfdffdd | 4 | 1
Table structure for usermeta table:
id | user_id | meta_key | meta_value
-------------------------------------
1 | 1 | study | 132
2 | 1 | subjects | 121,231
Table structure for studies table:
id | title
----------
1 | dsdsf
2 | sdfdf
Subject values are handled like so:
$subjectsvalues = '';
$subjects = explode(",", $subjects);
foreach($subjects as $val) {
$subjectsvalues = $subjectsvalues.",".$val.",|";
}
$subjectsvalues = $subjectsvalues."notdata";
First, left joins are unnecessary. So try this:
SELECT u.id, u.display_name, AVG(r.total_rating) AS avg_rating, s.title AS study
FROM users u JOIN
rating r
ON u.id = r.user_id JOIN
usermeta m
ON u.id = m.user_id JOIN
usermeta m1
ON u.id = m1.user_id JOIN
studies s
ON m.meta_value = s.id
WHERE m.meta_key = 'study' AND m1.meta_key = 'subjects' AND m1.meta_value REGEXP '$subjectsvalues'
GROUP BY u.id, r.total_rating
ORDER BY avg_rating DESC
LIMIT 10;
Then, I would be inclined to try indexes on usermeta(meta_key, user_id, meta_value). I assume the main ids in the tables are all primary keys.
I can't suggest an edit to Gordon Linoff's answer, so here's an improved version. You don't really need to join with usermeta twice unless usermeta is being compared with itself.
SELECT u.id, u.display_name, AVG(r.total_rating) AS avg_rating, s.title AS study
FROM users u JOIN
rating r
ON u.id = r.user_id JOIN
usermeta m
ON u.id = m.user_id JOIN
studies s
ON m.meta_value = s.id
WHERE m.meta_key = 'study' OR (m.meta_key = 'subjects' AND m.meta_value REGEXP '$subjectsvalues')
GROUP BY u.id, r.total_rating
ORDER BY avg_rating DESC
LIMIT 10;
Also, could you please explain the utility of your regular expression? It most likely won't match anything as $ indicating end of the pattern is placed at start of regex.

How to order by COUNT with SUM and MINUS of multi tables

I'm trying to show post by order them with sum of comment and like.
There are three table using in this query post,comment and like
for table like it has column type that keep value like or unlike.
SQL
SELECT (SELECT COUNT(id) AS count_comment
FROM comment WHERE comment.post_id = post.post_id),
(SELECT COUNT(id) AS count_like
FROM like WHERE like.post_id = post.post_id AND like.type = 'like'),
(SELECT COUNT(id) AS count_unlike
FROM like WHERE like.post_id = post.post_id AND like.type = 'unlike'),
post.* FROM post
ORDER BY (count_comment + count_like - count_unlike) DESC;
So, this is an example when it shows on the page
post_id | comment | like | unlike | (comment+like-unlike)
4 | 5 | 3 | 1 | 7
1 | 2 | 3 | 0 | 5
2 | 1 | 1 | 4 | -2
... | ... | ... | ... | ...
My problem is my SQL is very slow, please suggest another way if it can. I've tried to use JOIN but i can't figured out how its SQL should be, please help thanks.
Using a derived table for each of the counts, the query below counts comments, likes, unlikes for each post and then joins the counts to the post table by post_id.
SELECT
p.post_id,
COALESCE(c.comment_count,0) comment_count,
COALESCE(l.like_count,0) like_count,
COALESCE(ul.unlike_count,0) unlike_count,
(COALESCE(c.comment_count,0)
+ COALESCE(l.like_count,0)
- COALESCE(ul.unlike_count,0)) total
FROM post p
LEFT JOIN (
SELECT c.post_id,
COUNT(*) comment_count
FROM comment c
GROUP BY c.post_id
) c ON c.post_id = p.post_id
LEFT JOIN (
SELECT l.post_id,
COUNT(*) like_count
FROM like l
WHERE l.type = 'like'
GROUP BY l.post_id
) l ON l.post_id = p.post_id
LEFT JOIN (
SELECT ul.post_id,
COUNT(*) unlike_count
FROM like ul
WHERE ul.type = 'unlike'
GROUP BY ul.post_id
) ul ON ul.post_id = p.post_id
ORDER BY total DESC

Slow query using "where in" and "order by select count"

I want to show post from users that specified user is followed and i have two tables at below. but its query is very slow.
table user
id | username
1 | name1
2 | name2
3 | name3
..
..
table post
id | poster_id | post_content
1 | 2
2 | 3
3 | 10
..
..
table follow
followerid | followtoid
1 | 2
1 | 3
2 | 10
..
..
Assume that all tables have more than 1000 rows.
This's SQL
SELECT *
FROM post
WHERE poster_id IN (
SELECT followtoid
WHERE followerid = $_SESSION['userid']
)
And this's the second cast is very slow too.
I want to list all member by order from their total posts.
SELECT *
FROM user
ORDER BY (
SELECT COUNT(id)
FROM post
WHERE post_id = user.id
) DESC;
Try indexing post.userid, post.poster_id, followtoid.followerid and user.user_id, using CREATE INDEX, and use LEFT JOIN clause on your queries instead:
SELECT *
FROM user u
LEFT JOIN SELECT poster_id, COUNT(*) as count FROM post p GROUP BY poster_id
ON (u.user_id = p.poster_id)
ORDER BY count DESC;
and:
SELECT * FROM post AS p
LEFT JOIN (SELECT followerid FROM followtoid) AS f
ON (p.userid=f.followerid)
WHERE p.userid = {$_SESSION['userid']}
Use a JOIN for the first query
SELECT p.*
FROM post p
JOIN follow f ON p.post_id = f.followtoid
WHERE f.followerid = $_SESSION['userid']
and a JOIN plus a GROUP BY for the second
SELECT u.*, tbl.postCount
FROM user u
JOIN (
SELECT poster_id, COUNT(*) AS postCount
FROM post p
GROUP BY posterID
) tbl ON tbl.poster_id = u.id
ORDER BY postCount DESC
You can accomplish the second query without a subquery:
SELECT u.*, COUNT(p.poster_id) as postCount
FROM user u
LEFT JOIN post p
ON (u.user_id = p.poster_id)
GROUP BY u.user_id
ORDER BY postCount DESC;

How to join four tables in MySQL [PHP]

I have four tables and I want to join all of them. I have successfully joined 3 tables, but when I try to join the fourth one, it doesn't work. I have set error_reporting(E_ALL); and add or die(mysqli_error($con)); to the end of my query but it doesn't show any errors, just a white screen.
It stopped working when I tried to join the votes table.
My tables are votes users rings posts
Here is my query:
$sql = mysqli_query($con, "SELECT * FROM posts p
INNER JOIN rings r ON p.rid = r.id
INNER JOIN users u ON p.uid = u.id
INNER JOIN votes v ON p.pid = v.pid
WHERE p.rid IN ('$rja') AND p.uid != '$uid'
AND p.deleted = '0'
ORDER BY p.date_posted DESC"
) or die(mysqli_error($con));
$rja is an array.
Votes Table:
vid | pid | uid | vote_type
Users Table:
id | username | password | email
Posts Table:
pid | uid | rid | body | votes | deleted | date_posted
Rings Table:
id | title | category | rating | user_created
If you have any questions please comment
Try left join
SELECT * FROM posts p
INNER JOIN rings r ON p.rid = r.id
INNER JOIN users u ON p.uid = u.id
LEFT JOIN votes v ON p.pid = v.pid
WHERE p.rid IN ('$rja') AND p.uid != '$uid'
AND p.deleted = '0'
ORDER BY p.date_posted DESC"

MySql query, counting occurrences of user_id

My query is:
SELECT * FROM (SELECT user_id
FROM goals
LEFT JOIN goal_results ON goals.id = goal_results.goal_id
WHERE goals.enabled = 1 AND validation = 'accepted') AS u
My results are:
| user_id |
| 4 |
| 5 |
| 4 |
| 5 |
| 6 |
I need to get the count of each of them like:
4 > 2
5 > 2
6 > 1
I've tried many kind of queries with subqueries or using distinct but I'm lost and I don't reach my goal.
You can achieve this by using GROUP BY and COUNT. The subquery is not needed.
SELECT goal_results.user_id, COUNT(*) qty
FROM goals
LEFT JOIN goal_results ON goals.id = goal_results.goal_id
WHERE goals.enabled = 1 AND goal_results.validation = 'accepted'
GROUP BY goal_results.user_id
Adding group by will do.
SELECT u.user_id, count(*) FROM (SELECT user_id
FROM goals
LEFT JOIN goal_results ON goals.id = goal_results.goal_id
WHERE goals.enabled = 1 AND validation = 'accepted') AS u
group by u.user_id
You don't need to do this with a subquery:
SELECT g.user_id, count(*)
FROM goals g LEFT JOIN
goal_results gr
ON g.id = gr.goal_id
WHERE g.enabled = 1 AND validation = 'accepted'
GROUP BY g.user_id;
My guess, though, is that you really want:
SELECT g.user_id, count(gr.goal_id)
FROM goals g LEFT JOIN
goal_results gr
ON g.id = gr.goal_id
WHERE g.enabled = 1 AND validation = 'accepted'
GROUP BY g.user_id;
This will return 0 for users that have no goals. The first will return 1 for them.

Categories