Stock with slow SQL query - php

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.

Related

COUNT number of rows on table with LEFT JOIN returns 1 when not exists

I've two tables, for example:
post:
id | author | content | date
1 | Lucas | Hello! | 2016
2 | Igor | Hi! | 2016
comment:
id | post_id | content | date
1 | 2 | hehehe | 2016
2 | 1 | hahaha | 2016
3 | 2 | huhuhu | 2016
And I to do a SELECT that return all posts and a COUNT of rows of all comments with post.id = comment.id.
So, I tried:
SELECT p.id, p.author, p.content, p.date, COUNT(*) AS numComments FROM post p LEFT JOIN comment ON p.id = post_id WHERE p.author = '$author' GROUP BY p.id DESC LIMIT 12
And I got do it. But, even when no exists comments with p.id = post_id he returns 1.
So, I tried:
SELECT p.id, p.author, p.content, p.date, CASE WHEN COUNT(*) < 1 THEN '0' ELSE COUNT(*) END AS numComments FROM post p LEFT JOIN comment ON p.id = post_id WHERE p.author = '$author' GROUP BY p.id DESC LIMIT 12
But the result is the same. How to do this?
As outer joins return a row even if there's no matching data you need to count a column from the inner table, usually it's the column used in join:
SELECT p.id, p.author, p.content, p.date, COUNT(post_id) AS numComments
FROM post p LEFT JOIN comment ON p.id = post_id
WHERE p.author = '$author'
GROUP BY p.id -- seems to be mysql, otherwise you need to add more columns to the list
If you don't want to show rows with a zero count simply switch to an
INNER JOIN.
you can get count by this way, also last is order by not group by:
SELECT p.id, p.author, p.content, p.date,
(select COUNT(*) from comment where p.id = comment.post_id) AS numComments FROM post p
WHERE p.author = '$author'
ORDER BY p.id DESC LIMIT 12

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 to join multiple tables

I am looking for single query where I can connect multiple tables.
The Query is as follows
`
SELECT
a.name as module_name,
a.id,
b.id as subject_id,
b.SUBJECT_name,
c.id as course_id,
c.course_name,
d.id as cordinator_id,
d.cordinator_name
FROM module_table a
LEFT JOIN subject_table b ON b.id = a.subject_id
LEFT JOIN course_table c ON c.id = a.course_id
LEFT JOIN cordinator_table d ON d.id = a.cordinator_ids
WHERE a.id = $somevalue
ORDER BY a.id DESC
`
Above query is producing error and when I am connecting the two tables Its showing all right
SELECT
a.name as module_name,
a.id,
b.id as subject_id,
b.SUBJECT_name
FROM module_table a
LEFT JOIN subject_table b ON b.id = a.subject_id
WHERE a.id = $somevalue
ORDER BY a.id DESC`
The first Table has all foreign keys for subject and course table, further subject table is connected to coordinator table with common id column.. I want the corresponding names of the id given in the module table..
The last table is the result of query I want from where I can collect my required data
My table structures are below
MODULE CAN BE INCLUDED ONLY IN A SUBJECT AND SUBJECT CAN BE INCLUDED IN COURSE
EACH SUBJECT CAN HAVE ANY NUMBER OF CORDINATORS WHICH I AM KEEPING THEM AS JSON value
-----------------------------------------------------------------------
MODULE TABLE
_____________________________________________________
id | subject_id | course_id | cordinator_id | name
-----------------------------------------------------------------------
COURSE TABLE
__________________
id | course_name
-----------------------------------------------------------------------
SUBJECT TABLE
_________________________________________________
id | course_id | cordinator_id | SUBJECT_name
-----------------------------------------------------------------------
CORDINATOR TABLE
______________________________________
id | cordinator_ids | cordinator_name
Result TABLE
___________________________________________________________________________________________
id | module_name | subject_id | subject_name | course_id | course_name | cordinator_ids
I am able to join two tables successfully with LEFT Join but on third it is reporting an error.
The problem is that you have a few typos and forgot to JOIN course_table.
Here is the right query:
SELECT
a.name as module_name,
a.id,
b.id as subject_id,
b.SUBJECT_name,
c.id as course_id,
c.course_name,
d.id as cordinator_id,
d.cordinator_name
FROM module_table a
LEFT JOIN subject_table b ON b.id = a.subject_id
LEFT JOIN course_table c ON c.id = a.course_id
LEFT JOIN cordinator_table d ON d.id = a.cordinator_id
WHERE a.id = $somevalue
ORDER BY a.id DESC
On this line:
b.SUBJECT_name,
you had to put uppercase "SUBJECT_name" as in your table subject_table schema.
And your query was lacking this JOIN to allow you to select course_table fields:
LEFT JOIN course_table c ON c.id = a.course_id

Error return data null when using "case ... when" in php mysql?

I have a sample data:
users(id, name)
1 | peter
...
usermeta(user_id, meta_key, meta_value)
1 | level | 10
1 | display | pc
...
points(user_id, type, point)
1 | like | 5
2 | comment| 10
...
And mysql:
SELECT u.*,
(case when m.meta_key = 'level' then m.meta_value end) level ,
p.points AS point
FROM users u
LEFT JOIN points p ON p.user_id = u.id
LEFT JOIN usermeta AS m ON m.user_id = u.id
Result level = NULL, how to fix it?
id | name | level | point
1 | peter| NULL | 5
1 | peter| 10 | 10
Put m.meta_key = 'level' as the join condition.
SELECT u.*,
m.meta_value AS level ,
p.points AS point
FROM users u
LEFT JOIN points p ON p.uid = u.id
LEFT JOIN usermeta AS m ON m.user_id = u.id AND m.meta_key = 'level'
Have you tried providing an ELSE clause to your CASE? And according to your tables p.uid doesn't exist, it should be p.user_id, right?
Also, you should use INNER JOIN in this case as you want to retrieve only those cases in which the id field in the users table matches the ones in points and usermeta respectively. This should work properly:
SELECT
u.*,
CASE WHEN m.meta_key = 'level' THEN m.meta_value ELSE NULL END AS level,
p.points AS point
FROM users u
INNER JOIN points p ON p.user_id = u.id
INNER JOIN usermeta m ON m.user_id = u.id

mysql query within a query with privacy condition check

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

Categories