MySQL query too slow, 70k rows 12 sec - php

This query runs for 12+ seconds on VPS. It joins 3 tables. Only first one "topcics" has about 70k rows, others are about 20 and "post_cc" about 1500.
SELECT topics.*, employee.username, accounts.ac_name, accounts.ac_mail
FROM topics
INNER JOIN employee ON employee.id_user = topics.id_owner
INNER JOIN accounts ON accounts.id_account = topics.id_account
WHERE topics.status IN ('1','3') AND ( topics.id_owner IN (12, 5) OR topics.id_post IN
(SELECT DISTINCT(id_post) FROM post_cc WHERE id_employee IN (12, 5) ) )
ORDER BY topics.creationdate DESC LIMIT 0,25
I have already tried (without any improvement) to remove subquery and first "employee" join. If I remove "accounts" join, query runs under 0.1 sec, but all tables data are needed for sorting purpose during paging.
Explain:
+----+--------------------+------------+-----------------+-----------------------+---------+---------+-----------------+-------+----------------------------------------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+--------------------+------------+-----------------+-----------------------+---------+---------+-----------------+-------+----------------------------------------------+
| 1 | PRIMARY | topics | ALL | id_owner,id_account | NULL | NULL | NULL | 75069 | Using where; Using temporary; Using filesort |
| 1 | PRIMARY | accounts | ALL | PRIMARY | NULL | NULL | NULL | 5 | Using where; Using join buffer |
| 1 | PRIMARY | employee | eq_ref | PRIMARY | PRIMARY | 3 | topics.st_owner | 1 | Using where |
| 2 | DEPENDENT SUBQUERY | post_cc | unique_subquery | PRIMARY | PRIMARY | 8 | func,const | 1 | Using index; Using where |
+----+--------------------+------------+-----------------+-----------------------+---------+---------+-----------------+-------+----------------------------------------------+
I have added suggested keys as index, it improved time for 2 sec., but it's still too slow.
Shortened tables:
topics
+--------------------+---------------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+--------------------+---------------------+------+-----+---------+----------------+
| id_post | int(10) unsigned | NO | PRI | NULL | auto_increment |
| id_account | int(10) unsigned | YES | MUL | 0 | |
| mail | varchar(256) | YES | MUL | NULL | |
| from_name | varchar(512) | YES | | NULL | |
| title | varchar(512) | YES | | NULL | |
| content | text | YES | | NULL | |
| id_owner | int(10) unsigned | YES | MUL | NULL | |
| creationdate | datetime | YES | | NULL | |
+--------------------+---------------------+------+-----+---------+----------------+
employee
+---------------------+-----------------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+---------------------+-----------------------+------+-----+---------+----------------+
| id_employee | mediumint(8) unsigned | NO | PRI | NULL | auto_increment |
| id_user | mediumint(8) unsigned | NO | | NULL | |
| id_owner | tinyint(1) | YES | | 0 | |
| active | tinyint(1) | YES | | 1 | |
| username | varchar(64) | YES | | NULL | |
| email | varchar(128) | YES | | NULL | |
+---------------------+-----------------------+------+-----+---------+----------------+
accounts
+----------------------------+---------------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+----------------------------+---------------------+------+-----+---------+----------------+
| id_account | int(10) unsigned | NO | PRI | NULL | auto_increment |
| ac_mail | int(10) unsigned | YES | UNI | NULL | |
| ac_name | varchar(512) | YES | | NULL | |
| last_sync_time | datetime | YES | | NULL | |
+----------------------------+---------------------+------+-----+---------+----------------+
post_cc
+------------------------+---------------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+------------------------+---------------------+------+-----+---------+-------+
| id_post | int(10) unsigned | NO | PRI | NULL | |
| id_employee | int(10) unsigned | NO | PRI | NULL | |
| notifications | tinyint(3) unsigned | YES | | 1 | |
+------------------------+---------------------+------+-----+---------+-------+

One likely suspect is that DEPENDENT SUBQUERY.
MySQL is processing that subquery for each row returned by the outer query (which isn't already filtered out by some other predicate.
To improve performance, consider re-writing that either as a JOIN operation or an EXISTS predicate.
To replace that with a JOIN operation, that will need to be an OUTER JOIN (rather than an INNER JOIN) because of the OR in the predicate.
As an example of one way to do that:
SELECT topics.*
, employee.username
, accounts.ac_name
, accounts.ac_mail
FROM topics
JOIN employee ON employee.id_user = topics.id_owner
JOIN accounts ON accounts.id_account = topics.id_account
LEFT
JOIN ( SELECT DISTINCT q.id_post
FROM post_cc q
WHERE q.id_employee IN (12, 5)
) p
ON p.id_post = topics.id_post
WHERE topics.status IN ('1','3')
AND ( topics.id_owner IN (12, 5)
OR p.id_post IS NOT NULL
)
ORDER BY topics.creationdate DESC LIMIT 0,25
I recommend you run an EXPLAIN on that, and see how that performs.
Another option is to consider an EXISTS predicate. Occassinally we can get this to perform better, but often times not.
SELECT topics.*
, employee.username
, accounts.ac_name
, accounts.ac_mail
FROM topics
JOIN employee ON employee.id_user = topics.id_owner
JOIN accounts ON accounts.id_account = topics.id_account
WHERE topics.status IN ('1','3')
AND ( topics.id_owner IN (12, 5)
OR EXISTS ( SELECT 1
FROM post_cc q
WHERE q.id_employee IN (12, 5)
AND q.id_post = topics.id_post
)
)
ORDER BY topics.creationdate DESC LIMIT 0,25
For performance, that's going to almost require a suitable covering index for the subquery in the EXISTS clause, for example:
ON post_cc (id_post, id_employee)
You can try running an EXPLAIN and see how that performs as well.
We see that MySQL isn't using an index on the topics table.
We might get MySQL to avoid an expensive "Using filesort" operation if we had an index with a leading column of creationdate.
And part of the problem is likely that OR in the predicate. We might try re-writing that query as two separate queries, and combining them with a UNION ALL set operation. But if we do that, we'd really like to see an index on topic being used (we probably won't improve performance by incurring two scans of 70,000 rows.
SELECT topics.*
, employee.username
, accounts.ac_name
, accounts.ac_mail
FROM topics
JOIN employee ON employee.id_user = topics.id_owner
JOIN accounts ON accounts.id_account = topics.id_account
WHERE topics.status IN ('1','3')
AND topics.id_owner IN (12, 5)
UNION ALL
SELECT topics.*
, employee.username
, accounts.ac_name
, accounts.ac_mail
FROM topics
JOIN employee ON employee.id_user = topics.id_owner
JOIN accounts ON accounts.id_account = topics.id_account
JOIN ( SELECT DISTINCT q.id_post
FROM post_cc q
WHERE q.id_employee IN (12, 5)
) p
ON p.id_post = topics.id_post
WHERE topics.status IN ('1','3')
AND ( topics.id_owner NOT IN (12, 5) OR topics.id_owner IS NULL )
ORDER BY 8 DESC LIMIT 0,25
With a query of that form, we're more likely to get MySQL use a suitable index on the topics table,
... ON topics (id_owner, status)
... ON topics (id_post, status, id_owner)

Why don't you use left join for post_cc table, and then use condition?
Something like this.
SELECT topics.*,
employee.username,
accounts.ac_name,
accounts.ac_mail
FROM topics
INNER JOIN employee ON employee.id_user = topics.id_owner
INNER JOIN accounts ON accounts.id_account = topics.id_account
LEFT JOIN post_cc ON id_employee IN (12, 5)
WHERE topics.status IN ('1','3')
AND ( topics.id_owner IN (12, 5) OR topics.id_post IN (post_cc.post_id)
ORDER BY topics.creationdate DESC LIMIT 0,25;

The culprit:
75069 (rows) | Using where; Using temporary; Using filesort
That's what we need to get rid of.
Possible solution: add an index on topics.creationdate.
As a side note, the query also has conditions on id_post, st_owner, and status, therefore a composite index on topics(creationdate, id_post, st_owner, status) (or any permutation of the last three columns -- test with your data set) might help even further. However, your query seems to pull most of your table anyways, so I expect a simple index will suffice.

Related

What is the best way to update a one table from another in SQL?

I have 2 tables the first one is the product-page visited
+------------+--------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+------------+--------------+------+-----+---------+----------------+
| id | int(11) | NO | PRI | NULL | auto_increment |
| idproduct | varchar(128) | YES | | NULL | |
| logdate | date | YES | | NULL | |
| idmagasin | int(20) | YES | | NULL | |
| idenseigne | int(20) | YES | | NULL | |
| commanded | int(2) | YES | | 0 | |
+------------+--------------+------+-----+---------+----------------+
And the second one is the product commanded
+-------------+--------------+------+-----+-------------------+----------------+
| Field | Type | Null | Key | Default | Extra |
+-------------+--------------+------+-----+-------------------+----------------+
| id | int(11) | NO | PRI | NULL | auto_increment |
| idproduct | varchar(255) | NO | | NULL | |
| idenseigne | int(11) | NO | | NULL | |
| idmagasin | int(11) | NO | | NULL | |
| ingredients | tinytext | YES | | NULL | |
| date | timestamp | NO | | CURRENT_TIMESTAMP | |
+-------------+--------------+------+-----+-------------------+----------------+
How can i update the column commanded in product_visited , if product_visited.idproduct = product_commanded.idproduct and product_visited.logdate = product_commanded.date
i'm confused to use inner join or exists
I want to update product_visited.commanded = 1 when the value of logdate and idproduct exists in product_commanded, it will mean the product visited is commanded too
I believe this is what you are looking for:
Update product_visited pv
set commanded = 1
Where exists (Select 1
from product_commanded pc
where pv.idproduct = pc.idproduct and pv.logdate = pc.date
);
Ok, I've made guesses with the join fields but you're after something like this;
UPDATE pv
SET pv.Commanded = 1
FROM Product_Visited pv
JOIN Product_Commanded pc
ON pv.logdate = pc.date
AND pv.idproduct = pc.id
The inner join means that you're only going to update records in Product_Visited where there are matching rows in Product_Commanded based on the join predicates you give it.
Note: this is a SQL Server answer. May or may not work in MySQL
Sounds like you want to update commanded whenever a record exists for same product in commanded table?
in any database:
Update product_visited set commanded = 1
Where exists(Select * from product_commanded
where product_id = product_visited.Product_id)

Select all categories with latest post, user, and topic information

I am working on a custom forum for a web project. I have categories with id, topics with id and category, and posts with id and topic and user id. What I'm trying to do is display a list of the categories with data from the categories table along with data from the posts table for the newest post in that category, data for that posts's associated user, as well as some data for the topic associated with that latest post.
I've been banging my head on the wall trying to figure the query out, but I just don't have a good enough understanding of complex mysql queries to know what pattern or technique to use here. Here is what I have so far:
SELECT u1.*, fp1.*, ft1.*, fc1.* from forum_posts AS fp1
LEFT JOIN users AS u1 ON u1.id = fp1.post_by
LEFT JOIN forum_topics AS ft1 on ft1.id = fp1.post_topic
LEFT JOIN forum_categories AS fc1 on fc1.id = ft1.topic_cat
GROUP BY fc1.id
ORDER BY fp1.id ASC;
But this does not return the results I'm looking for. The problem is in trying to get the newest post for each category and the associate topic for that post.
Here is the DB structure for each table:
forum_categories
+-----------------+---------------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+-----------------+---------------------+------+-----+---------+----------------+
| id | bigint(20) unsigned | NO | PRI | NULL | auto_increment |
| cat_name | varchar(255) | NO | UNI | NULL | |
| cat_description | varchar(500) | NO | | NULL | |
| cat_views | bigint(20) unsigned | YES | | 0 | |
| status | tinyint(1) | NO | | 1 | |
+-----------------+---------------------+------+-----+---------+----------------+
forum_topics
+---------------+---------------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+---------------+---------------------+------+-----+---------+----------------+
| id | bigint(20) unsigned | NO | PRI | NULL | auto_increment |
| topic_subject | varchar(255) | NO | | NULL | |
| topic_date | datetime | NO | | NULL | |
| topic_cat | bigint(20) unsigned | NO | MUL | NULL | |
| topic_by | bigint(20) unsigned | NO | MUL | NULL | |
| topic_views | bigint(20) unsigned | YES | | 0 | |
+---------------+---------------------+------+-----+---------+----------------+
forum_posts
+--------------+---------------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+--------------+---------------------+------+-----+---------+----------------+
| id | bigint(20) unsigned | NO | PRI | NULL | auto_increment |
| post_content | text | NO | | NULL | |
| post_date | datetime | NO | | NULL | |
| post_topic | bigint(20) unsigned | NO | MUL | NULL | |
| post_by | bigint(20) unsigned | NO | MUL | NULL | |
+--------------+---------------------+------+-----+---------+----------------+
users
+-----------+---------------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+-----------+---------------------+------+-----+---------+----------------+
| id | bigint(20) unsigned | NO | PRI | NULL | auto_increment |
| user_type | varchar(50) | YES | | NULL | |
| email | varchar(255) | NO | UNI | NULL | |
| username | varchar(255) | YES | UNI | NULL | |
| password | char(60) | NO | | NULL | |
| image | text | YES | | NULL | |
| status | tinyint(1) | NO | | 1 | |
+-----------+---------------------+------+-----+---------+----------------+
Here is an image of the output I'm trying to achieve. "Categories" shows the data from the forum_categories table and under "Recent" is the user, post and topic data for the latest post:
Please help me out. Thank you.
While it's simple enough to join all the tables together to work out the topic, category and user for each post, you also need one additional step - you need to join to a subquery that retrieves the max(post_id) per category.
Here's one way you can do that:
select fc.cat_name, fc.cat_description, fc.cat_views, u.username, fp.post_date, ft.topic_subject
from forum_categories fc
inner join forum_topics ft
on fc.id = ft.topic_cat
inner join forum_posts fp
on fp.post_topic = ft.id
inner join users u
on fp.post_by = u.id
inner join (
select topic_cat, max(fp.id) most_recent_post
from forum_topics ft
inner join forum_posts fp
on fp.post_topic = ft.id
group by topic_cat
) q
on q.topic_cat = ft.topic_cat
and fp.id = q.most_recent_post;
There's a demo you can play with here: http://sqlfiddle.com/#!9/3736b/1

MySQL: Key/value store queries optimization

I am seeking a help with setting up correct indexes (i've tried too many and now i am a bit lost), correct MySQL engine (MyIsam, InnoDB...) and help with my queries (JOINs, ...). Also I have headache when I am thinking that these queries should return count(*).
My times were more than 5 - 10 seconds per query without counting, but I am not sure if I can get better times for this big database.
I am trying to optimize this MySQL tables:
Items (~600k rows):
+-------+------------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+-------+------------------+------+-----+---------+----------------+
| id | int(11) unsigned | NO | PRI | NULL | auto_increment |
| type | varchar(255) | NO | PRI | NULL | |
+-------+------------------+------+-----+---------+----------------+
Items_Relationships (~1M rows):
+-------------+------------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+-------------+------------------+------+-----+---------+-------+
| lft_item_id | int(11) unsigned | NO | PRI | NULL | |
| rgt_item_id | int(11) unsigned | NO | PRI | NULL | |
| rel_type | varchar(255) | NO | PRI | NULL | |
+-------------+------------------+------+-----+---------+-------+
Items_Values (~4M rows):
+---------+------------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+---------+------------------+------+-----+---------+----------------+
| id | int(11) unsigned | NO | PRI | NULL | auto_increment |
| item_id | int(11) unsigned | NO | PRI | 0 | |
| name | varchar(255) | YES | MUL | NULL | |
| value | longtext | YES | | NULL | |
| lang | varchar(2) | YES | | NULL | |
+---------+------------------+------+-----+---------+----------------+
I am running basically these common queries:
1. query - All items with value "status" > 1:
SELECT `company`.`id` AS `id`,
`company`.`type` AS `type`
FROM `items` AS `company`
INNER JOIN `items_values` AS `value_name` ON (`company`.`id` = `value_name`.`item_id`)
WHERE `company`.`type` = 'company'
AND `value_name`.`name` = 'status'
AND CONVERT(`value_name`.`value`, SIGNED) > 1
GROUP BY `company`.`id`
ORDER BY `company`.`id` DESC
LIMIT 0, 30
2. query - All items with some values in relationship with other items:
SELECT `company`.`id` AS `id`,
`company`.`type` AS `type`
FROM `items` AS `company`
INNER JOIN `items_values` AS `value_status` ON (`value_status`.`item_id` = `company`.`id`)
INNER JOIN `items_relationships` AS `companies_categories` ON (`companies_categories`.`lft_item_id` = `company`.`id`)
INNER JOIN `items_values` AS `category_rgt` ON (`category_rgt`.`item_id` = `companies_categories`.`rgt_item_id`)
WHERE `company`.`type` = 'company'
AND `company`.`type` = 'company'
AND `value_status`.`name` = 'status'
AND CONVERT(`value_status`.`value`, SIGNED) >= 1
AND `category_rgt`.`name` = 'rgt'
AND (CONVERT(category_rgt.value, UNSIGNED) BETWEEN 2805 AND 4222)
AND `companies_categories`.`rel_type` = 'company_category'
GROUP BY `company`.`id`
ORDER BY `company`.`id` DESC LIMIT 10
OFFSET 0
Thx in advance!
If you ask about indexes, so you have almost everything you need already indexed.
I have just question about
| name | varchar(255) | YES | MUL | NULL | |
So I would prefer to set it as well as primary key.
I have only one suggestion about table structures.
If you have mixed string and numbers in your
| value | longtext | YES | | NULL | |
create another column int_value SIGNED or unsigned is even better.
And you should set that column as index as well (as soon as you need that column used as a filter and/or search criteria)
And fill that field on insert/update where it is applicable.
This modification will increase performance of query where you should not use CAST and/or CONVERT for millions of records like you do here:
AND CONVERT(`value_status`.`value`, SIGNED) >= 1
AND (CONVERT(category_rgt.value, UNSIGNED) BETWEEN 2805 AND 4222)
So I have no more comments about structure.
But I would ask you to try my query, just like an experiment if it faster than yours. Unfortunately I have no possibility to debug with any data. If you provide some sqlfiddle that will help a lot.
SELECT `company`.`id` AS `id`,
`company`.`type` AS `type`
FROM `items` AS `company`
INNER JOIN (
SELECT
item_id,
FROM items_values
WHERE name = 'status'
AND CONVERT(value, SIGNED) >= 1
) AS value_status
ON value_status.item_id = company.id
INNER JOIN
(
SELECT
lft_item_id
FROM
items_relationships
INNER JOIN (
SELECT
item_id
FROM
items_values
WHERE name = 'rgt'
AND (CONVERT(value, UNSIGNED) BETWEEN 2805 AND 4222)
) AS category_rgt
ON category_rgt.item_id = items_relationships.rgt_item_id
WHERE items_relationships.rel_type = 'company_category'
) as companies_categories
ON (`companies_categories`.`lft_item_id` = `company`.`id`)
WHERE `company`.`type` = 'company'
GROUP BY `company`.`id`
ORDER BY `company`.`id` DESC
LIMIT 10

Inner join but I don't understand the foreign key bit with a nother table

I have 4 tables like so
Product Table:
+--------------+-------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+--------------+-------------+------+-----+---------+----------------+
| productID | int(11) | NO | PRI | NULL | auto_increment |
| name | varchar(45) | YES | | NULL | |
| price | int(11) | YES | | NULL | |
| manufacturer | varchar(45) | YES | | NULL | |
| rating | int(2) | YES | | NULL | |
| categoryID | int(11) | YES | MUL | NULL | |
+--------------+-------------+------+-----+---------+----------------+
Category Table:
+--------------+-------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+--------------+-------------+------+-----+---------+----------------+
| categoryID | int(11) | NO | PRI | NULL | auto_increment |
| categoryName | varchar(64) | YES | | NULL | |
+--------------+-------------+------+-----+---------+----------------+
User:
+-----------+-------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+-----------+-------------+------+-----+---------+----------------+
| userID | int(11) | NO | PRI | NULL | auto_increment |
| firstname | varchar(45) | YES | | NULL | |
| age | int(11) | YES | | NULL | |
| location | varchar(45) | YES | | NULL | |
| username | varchar(45) | YES | | NULL | |
| password | varchar(45) | YES | | NULL | |
+-----------+-------------+------+-----+---------+----------------+
Review:
+------------+------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+------------+------------+------+-----+---------+----------------+
| reviewID | int(11) | NO | PRI | NULL | auto_increment |
| reviewTime | date | YES | | NULL | |
| reviewData | mediumtext | YES | | NULL | |
| productID | int(11) | YES | MUL | NULL | |
| userID | int(11) | YES | MUL | NULL | |
+------------+------------+------+-----+---------+----------------+
I basically need a select statement that shows the following
reviewID - reviewTime - reviewData - product.name - category.name - user.firstname
I have come up with the following so far
SELECT r.reviewID, r.reviewTime, r.reviewData, p.name, u.firstname
FROM review r
INNER JOIN product p on p.productID = r.productID
INNER JOIN user u on u.userID = r.userID;
This gives me everything but the category name as thats where I am stuck.
The database is linked like this;
I just don't understand how to pass the categoryname from category table through to the review table
select r.reviewID, r.reviewTime, r.reviewData, p.name, u.firstname, c.categoryName
from review r
left join product p on p.productID = r.productID
left join user u on u.userID = r.userID
left join category c on c.categoryID = p.categoryID
Note: Based on your schema, I have used LEFT JOINs so that all reviews will get returned whether there is related data or not.
Your current query:
SELECT r.reviewID, r.reviewTime, r.reviewData, p.name, u.firstname
FROM review r
INNER JOIN product p on p.productID = r.productID
INNER JOIN user u on u.userID = r.userID;
Since you're already joining on the product table and the product table has a reference to categoryID, just add another join:
SELECT
r.reviewID, r.reviewTime, r.reviewData, p.name, u.firstname, c.categoryName
FROM review r
INNER JOIN product p on p.productID = r.productID
INNER JOIN user u on u.userID = r.userID;
INNER JOIN category c ON c.categoryID = p.categoryID
Add INNER JOIN category c on c.categoryID = p.categoryID
You are not joining category table add
Join category c on c.categoryID = p.categoryID
And add select
c.name
Also change joins to left join only if it is possible that any link between tables does not exists.

Need to speed up MySQL query, it's at 3+ seconds with only 4,000 records, has 3 joins

Here's the situation:
I have a few tables (described below) that track residents in an apartment building, their unit number, and when they were "seen" last (we have lots of older people with health issues, so it's important to check on them every 2 days or so; sometimes they die here and that's how we know how to check on them).
The contstraints for a "check" are that they have to have been seen in the past 48 hours; if not, the query should pull their record up. Here are the table definitions I'm using:
The "people" table, where resident info is stored:
MariaDB [olin2]> describe people;
+-------------+-------------+------+-----+-------------------+----------------+
| Field | Type | Null | Key | Default | Extra |
+-------------+-------------+------+-----+-------------------+----------------+
| id | int(11) | NO | PRI | NULL | auto_increment |
| fname | varchar(32) | NO | MUL | NULL | |
| lname | varchar(32) | NO | | NULL | |
| dob | date | YES | | NULL | |
| license_no | varchar(24) | NO | | NULL | |
| date_added | timestamp | NO | | CURRENT_TIMESTAMP | |
| status | varchar(8) | NO | | Allow | |
| license_exp | date | YES | | NULL | |
+-------------+-------------+------+-----+-------------------+----------------+
The "units" table, where unit numbers are stored (people switch units so I didn't want them in the "people" table):
MariaDB [olin2]> describe units;
+----------+-------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+----------+-------------+------+-----+---------+----------------+
| id | int(11) | NO | PRI | NULL | auto_increment |
| number | varchar(3) | NO | MUL | NULL | |
| resident | int(11) | NO | | NULL | |
| type | varchar(16) | NO | | NULL | |
+----------+-------------+------+-----+---------+----------------+
and the "wellness" table, where the "checks" are stored (the resident's id number, when they were seen and by whom, etc.):
MariaDB [olin2]> describe wellness;
+--------------+-------------+------+-----+-------------------+----------------+
| Field | Type | Null | Key | Default | Extra |
+--------------+-------------+------+-----+-------------------+----------------+
| wellness_id | int(11) | NO | PRI | NULL | auto_increment |
| people_id | int(11) | NO | | NULL | |
| time_checked | timestamp | NO | | CURRENT_TIMESTAMP | |
| check_type | varchar(1) | NO | | NULL | |
| username | varchar(16) | NO | | NULL | |
| return_date | timestamp | YES | | NULL | |
+--------------+-------------+------+-----+-------------------+----------------+
The "return_date" field in the "wellness" table is for when a resident leaves for more than 2 days, then they won't be included in the results when they are displayed (they actually will be included in the query results, but I use PHP to filter those out).
Here's the query I have been using... It worked well for a few weeks, but as there were more and more records added it's been getting noticably slower (right now its 3.5 seconds to return the results):
select p.id, w.time_checked, w.username, w.return_date
from people p
left join units u on p.id = u.resident
left join wellness w on p.id = w.people_id
left join wellness as w2 on w.people_id = w2.people_id
and w.time_checked < w2.time_checked
where w2.people_id is null
and w.time_checked < (now() - interval 48 hour)
order by u.number
I know my problem is the joins, but I don't know how to get the results I need without them and/or how to optimize this query to speed it up... Here's a sample of results (if needed):
+----+---------------------+----------+---------------------+
| id | time_checked | username | return_date |
+----+---------------------+----------+---------------------+
| 8 | 2013-12-01 11:00:13 | tluce | 0000-00-00 00:00:00 |
+----+---------------------+----------+---------------------+
1 row in set (3.44 sec)
So, in this result set, resident 8 hasn't been seen for 3 days... the result is correct but the 3.44 sec isn't acceptable for my users to have to wait.
Any ideas on how I can improve this?
EDIT (More Info):
I realize updating the wellness entry for each person would be easier and quicker to access; however I like to have these data on-hand because I generate graphs from it to show A) when we most often see a particular resident and B) which staff members check on people the most often (aka - who's doing their job and who's not)
I DO use indexes, and here's the results of an EXPLAIN on my query:
+------+-------------+-------+--------+---------------+---------+---------+----------------- -+------+--------------------------------------------------------------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+------+-------------+-------+--------+---------------+---------+---------+----------------- -+------+--------------------------------------------------------------------+
| 1 | SIMPLE | u | ALL | NULL | NULL | NULL | NULL | 107 | Using temporary; Using filesort |
| 1 | SIMPLE | p | eq_ref | PRIMARY,idx | PRIMARY | 4 | olin2.u.resident | 1 | Using where |
| 1 | SIMPLE | w | ALL | NULL | NULL | NULL | NULL | 7074 | Using where; Using join buffer (flat, BNL join) |
| 1 | SIMPLE | w2 | ALL | NULL | NULL | NULL | NULL | 7074 | Using where; Not exists; Using join buffer (incremental, BNL join) |
+------+-------------+-------+--------+---------------+---------+---------+----------------- -+------+--------------------------------------------------------------------+
The indexes in the people table: id, fname, lname, license_no
The wellness table: wellness_id
The units table: id, number
Under possible keys each field that has NULL means that index was not used. so you can add index to the fields you use to join. for example units.residents. you can do the same thing to wellness.people_id –
You have too many joins for this result. The only reason it seems like you have the "people" table in there is to attach to "units" but you have flags in both "units" and "wellness" that you can join off of. I also don't believe the second join of the wellness table is necessary.
This is all you should need:
Select W.people_id, w.time_checked, w.username, w.return_date
FROM units u
left join wellness w on u.resident = w.people_id
where w.time_checked < (now() - interval 48 hour)
order by u.number
You may want to also look at indexing your people_id fields in tables where it is not the primary key. That will help speed up any queries you try and run off these tables.

Categories