What's wrong with my MySQL query? - php

I'm not getting any errors as such just a minor performance issue.
EXPLAIN
SELECT
a.nid,
a.title,
a.uid,
b.parent,
b.weight,
c.name,
d.value
FROM table1 AS a INNER JOIN table2 AS b ON a.vid = b.vid AND a.status = 1
INNER JOIN table3 AS c ON c.uid = a.uid
INNER JOIN table4 AS d ON d.content_id = a.nid AND d.value_type = 'percent' AND d.function = 'average'
When I look at which tables are being referenced, everything is fine, but from table4 where it should only be selecting the "value" field, I'm getting an ALL being called...
id select_type table type possible_keys key key_len ref rows Extra
1 SIMPLE a ref PRIMARY,vid,status,uid,node_status_type,nid status 4 const 1
1 SIMPLE b eq_ref PRIMARY PRIMARY 4 databasename.a.vid 1
1 SIMPLE c eq_ref PRIMARY PRIMARY 4 databasename.a.uid 1 Using where
1 SIMPLE d ALL NULL NULL NULL NULL 2 Using where
As you can see, it's selecting * from the final table (d). Why is it doing this when I only need ONE field selected from it? Can anyone help me out?

ALL means all rows, not all columns. Since it says there are no possible keys, I'd guess that you don't have an index on d.content_id or d.value_type or d.function.
If you wanted to be fancy, you could put an index across all 3 of those columns.

Are d.value_type and d.function indexed fields? That would be initial instinct as to the cause.

Add a multi-column index to table4 based on the content_type, value_type and function columns.
Your query isn't selecting all the columns from table4, it's selecting all the rows; this isn't much of a problem when there's only two.
Note that a MySQL query execution plan might not give the give the answer you expect when you're working with a small number of records; it can be faster for the database to do a full table scan in those circumstances.

Related

Slow MySQL query with multiple joins, max() and group by

I've got a serious problem. Our Intranet is getting slower and slower. One of the mainreasons seems to be a slow mysql-query (it appears in the slow-query.log).
That query is asked every time an intranet-site is opened.
It looks like this:
SELECT w.Datetime, w.User_ID, w.Status, e.Lastname
FROM worktimes AS w
INNER JOIN employees AS e ON w.User_ID=e.ID
RIGHT JOIN (SELECT max(Datetime) AS Datetime, User_ID
FROM worktimes
WHERE Datetime>".$today." // variable of today 0.00 o'clock
AND Location='".$llocation['ID']."' // variable of one of 9 locations
GROUP BY User_ID) AS v
ON v.User_ID=w.User_ID AND w.Datetime=v.Datetime
ORDER BY e.Lastname;
The worktimes-table is somewhat greater with up to 200k rows (momentary 90k to testing reasons) and 13 columns. The whole query goes through a loop with 3 to 9 cycles.Has someone an idea how to make the queries faster?
edit: As wished here is the EXPLAIN-result.
id select_type table type possible_keys key key_len ref rows Extra
1 PRIMARY <derived2> ALL NULL NULL NULL NULL 44006 Using temporary; Using filesort
1 PRIMARY w ALL NULL NULL NULL NULL 92378 Using where
1 PRIMARY e eq_ref PRIMARY,ID PRIMARY 4 ais_v1.w.User_ID 1 NULL
2 DERIVED worktimes ref Location Location 767 const 44006 Using index condition; Using where; Using temporary; Using filesort
The w table needs an index on INDEX(Location, Datetime). This should improve the performance.
you dont need to use worktimes twice
you can do it as follows:
SELECT max(w.Datetime) AS Datetime, w.User_ID, w.User_ID, w.Status, e.Lastname
FROM worktimes w left outer join employees e
on e.User_ID=w.User_ID
and w.Datetime>".$today." // variable of today 0.00 o'clock
AND w.Location='".$llocation['ID']."' // variable of one of 9 locations
GROUP BY w.User_ID
ORDER BY e.Lastname;
it will run faster than your existing query

Mysql php count number of inner join work faster with subqueries

Two tables, first one is users and 2nd is posts, table posts structure is id,body,parent_id,user_id in this table all posts are inserted with parent_id is null, and if its a comment the parent_id is set to post id.
What I'm trying to do is join users table -to get user details- and get count of comments on each post.
I tried a couple of queries
select p.id,
users.id as 'from_id',
users.fullname as 'from_fullname',
users.role as 'from_role',
users.picture as 'from_picture',
p.body,
p.time_posted as 'time_posted',
p.attachment,
p.parent_id,
count(c.id) as counts
from
wall p
join
users on users.id = p.user_id
left join
wall c on c.parent_id = p.id
where
p.class_id = 8 and p.parent_id is null
group by
p.id
order by
`counts` ---->EXPLAIN RESULTS
id select_type table type possible_keys key key_len ref rows Extra
1 SIMPLE p ref PRIMARY,parent_id,class_id,user_id parent_id 5 const 49920 Using index condition; Using where; Using temporary; Using filesort
1 SIMPLE users eq_ref PRIMARY PRIMARY 4 ischool.p.user_id 1 NULL
1 SIMPLE c ref parent_id parent_id 5 ischool.p.id 49920 Using index
This one takes average of around 2.7 sec to complete.
While my 2nd attempt
select p.id,
users.id as 'from_id',
users.fullname as 'from_fullname',
users.role as 'from_role',
users.picture as 'from_picture',
p.body,
(select count(*) from wall where parent_id= p.id ) as comments_count,
p.time_posted as 'time_posted',
p.attachment,
p.parent_id
from
wall p
left join
users on users.id = p.user_id
where
p.class_id = 8 and p.parent_id is NULL
order by
p.id DESC; --->Explain results
id select_type table type possible_keys key key_len ref rows Extra
1 PRIMARY p ref parent_id,class_id parent_id 5 const 49920 Using where
1 PRIMARY users eq_ref PRIMARY PRIMARY 4 ischool.p.user_id 1 NULL
2 DEPENDENT SUBQUERY wall ref parent_id parent_id 5 ischool.p.id 49920 Using index
This query takes 1.4 sec to complete
given that im using MYSQL innodb with index on each and every id column.
so is there a better method to fetch posts and comment counts ?
why is subquery working nearly 2x faster than join ?
Basically Joins are more faster than sub query. Generally, new developer prefers to use sub-query.
The execution plan of query may impact on performance. Almost all cases queries joins are more faster than sub-query.
The basic difference is that sub-query execute query and load all data and based on those data execute another query.
and joins are all tables all loads with data so it performs fast execution of query.
In you query, if you want to show why you sub-query is faster than join use "explain " command to show how query is actually executed.
Following links may helpful to understand concepts:
http://www.chrislondon.co/joins-vs-subqueries/
http://www.sitepoint.com/using-explain-to-write-better-mysql-queries/

MYSQL find rows where foreign key are not in related table

I have reservations that can be made in advance. On the day of the reservation, the devices reserved can be checked out.
I have two tables, reservations and checkouts. Reservations have many checkouts.
How would I construct a query that would return all reservations for a particular date that do NOT have associated checkout records?
To put it another way, all rows from reservations where reservation_id column does not contain the reservation's ID?
So far, my best guess is
SELECT * FROM reservations WHERE reservations.id NOT IN (SELECT reservation_id
FROM checkouts)
But that returns empty. Here's a rough idea what the tables look like
|reservations| |checkouts |
|id = 1 | |reservation_id = 1|
|id = 2 | |reservation_id = 2|
|id = 3 |
My result should be reservation 3.
P.S. If php is required, that's fine.
Most likely explanation for the query returning no rows is that there are rows in checkouts that have a NULL value for reservation_id. Consider:
4 NOT IN (2,3,5,NULL,11)
In interpreting this, the NULL value is seen as meaning "unknown what the value is". Is 4 in that list? The answer (coming back from SQL) is basically "unknown" whether 4 matches the "unknown" value in the list.
If that's what's causing the behavior, you can "fix" your current query by including WHERE reservation IS NOT NULL in the subquery.
SELECT r.*
FROM reservations r
WHERE r.id NOT IN ( SELECT c.reservation_id
FROM checkouts c
WHERE c.reservation_id IS NOT NULL
)
This may not be the most efficient approach to returning the specified result. An anti-join is a common pattern for returning this type of result. In your case, that would be an outer join, to return all rows from reservations, along with matching rows from checkouts, and then a predicate in the WHERE clause to filter out all the rows that had a match, leaving us with rows from reservations that didn't have a match.
For example:
SELECT r.*
FROM reservations r
LEFT
JOIN checkouts c
ON c.reservation_id = r.reservation_id
WHERE c.reservation_id IS NULL
It's also possible to get an equivalent result with a NOT EXISTS with a correlated subquery.
SELECT r.*
FROM reservations r
WHERE NOT EXISTS ( SELECT 1
FROM checkouts c
WHERE c.reservation_id = r.reservation_id
)

Problems with MYSQL query (efficiency)

I'm having real problems with my Mysql statement, I need to join a few tables together, query them and order by the average of values from another table. This is what I have...
SELECT
ROUND(avg(re.rating), 1)AS avg_rating,
s.staff_id, s.full_name, s.mobile, s.telephone, s.email, s.drive
FROM staff s
INNER JOIN staff_homes sh
ON s.staff_id=sh.staff_id
INNER JOIN staff_positions sp
ON s.staff_id=sp.staff_id
INNER JOIN reliability re
ON s.staff_id=re.staff_id
INNER JOIN availability ua
ON s.staff_id=ua.staff_id
GROUP BY staff_id
ORDER BY avg_rating DESC
Now I believe this to work although I am getting this error "The SELECT would examine more than MAX_JOIN_SIZE rows; check your WHERE and use SET SQL_BIG_SELECTS=1 or SET SQL_MAX_JOIN_SIZE=# if the SELECT is okay".
I think this means that I have too many joins and because it is shared hosting it won't allow large queries to run I don't know.
What I would like to know is exactly what the error means (I have googled it but I don't understand the answers) and how I can work round it by maybe making my query more efficient?
Any help would be appreciated. Thanks
EDIT:
The reason I need the joins is so I can query the tables based on a search function like so...
SELECT
ROUND(avg(re.rating), 1)AS avg_rating
, s.staff_id, s.full_name, s.mobile, s.telephone, s.email, s.drive
FROM staff s
INNER JOIN staff_homes sh
ON s.staff_id=sh.staff_id
INNER JOIN staff_positions sp
ON s.staff_id=sp.staff_id
INNER JOIN reliability re
ON s.staff_id=re.staff_id
INNER JOIN availability ua
ON s.staff_id=ua.staff_id
WHERE s.full_name LIKE '%'
AND s.drive = '1'
AND sh.home_id = '3'
AND sh.can_work = '1'
AND sp.position_id = '3'
AND sp.can_work = '1'
GROUP BY staff_id
ORDER BY avg_rating DESC
EDIT 2
This was the result of my explain. Also I'm not great with MYSQL how would I set up foreign keys?
id select_type table type possible_keys key key_len ref rows Extra
1 SIMPLE ua ALL NULL NULL NULL NULL 14 Using temporary; Using filesort
1 SIMPLE re ALL NULL NULL NULL NULL 50 Using where; Using join buffer
1 SIMPLE sp ALL NULL NULL NULL NULL 84 Using where; Using join buffer
1 SIMPLE sh ALL NULL NULL NULL NULL 126 Using where; Using join buffer
1 SIMPLE s eq_ref PRIMARY PRIMARY 4 web106-prestwick.ua.staff_id 1
EDIT 3: Thanks lc, it was my foreign keys, they were not set up correctly. Problem sorted
Maybe you should use more and/or better indexes on the tables.
According to the db you're using, the optimization may be faster or not with subqueries.
There may be 2 bottlenecks on your query:
try to remove the average function of your query. If your query speeds up, try to replace it with a subquery and see what happens.
The multiple joins often reduce performances, and there's nothing you can do except modifying your db schema. The simplest solution would be to have a table that precomputes data and reduces the work for your db engine. This table can be fulfilled with stored procedures triggered when you modify the data on the implied tables, or you can also modify the table values from your php application.

Retrieve a value through three tables in a single Query

I have been trying to understand joins for a while now, I've noticed quite a few examples showing how to retrieve a value based on a two-table query using a left / inner join. I will try to draw out my example for you all, hopefully it will aid in the explanation of my question.
Table 1:
project_id | project_name
1 super-project
Table 2:
project_id | course_id ---> project_id and course_id are foreign keys here.
1 3
Table 3:
course_id | course_name ---> course_id is a primary key here
3 | Writing 101
My Goal:
I would like to be able to return "Writing 101" by using the project_id. So, if I know the project_id is 1, I would query the db to find that the course_id associated with the current project is 3. I would then use the course_id of 3 to query table 3 and find that the related course_name is writing 101.
Is there a way to do this in a single query using join of some sort?
select course_name from Table3
inner join Table2
on Table2.course_id = Table3.course_id
where Table2.project_id = 1
Notice there is only 1 join needed!
Also note that if the project_id = 1 is present in more than one course all of those courses will appear.
Can you explain why only one join is needed as opposed to the answer below which requires two joins?
The other answer does not require two joins. As the project_id is present in the "middle" table there is no need to look for it in Table1. Now, if you require the name of the project (which is not the case because you didn't mention that in the question) then you would have to join the Table1
Here you go:
SELECT c.course_name
FROM Table3 c
INNER JOIN Table2 pc ON pc.course_id = c.course_id
INNER JOIN Table1 p ON p.project_id = pc.project_id
WHERE project_id = 1

Categories