Optimize not in sub-query MYSQL - mysql

I've got table in MySQL db where >20 000 000 rows, the query below executes great on small amount of rows, but takes 2-3 secs if there are more. How can I optimize this to make it run < 1 at least?
Note - the problem is in sub-query SELECT read_state FROM messages...
Query:
SELECT sql_no_cache users.id AS uid,
name,
avatar,
avatar_date,
driver,
msg,
DATE,
messages.removed,
from_id = 528798 AS outbox ,
!(0 IN
(SELECT read_state
FROM messages AS msgs FORCE KEY(user_id_2)
WHERE (msgs.from_id = messages.from_id
OR msgs.from_id = messages.user_id)
AND msgs.user_id = 528798
AND removed = 0
)) AS read_state
FROM dialog,
messages,
users
WHERE messages.id = mid
AND ((uid1 = 528798
AND users.id = uid2)
OR (uid2 = 528798
AND users.id = uid1))
ORDER BY DATE DESC;
show index from messages;
+----------+------------+-------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
| Table | Non_unique | Key_name | Seq_in_index | Column_name | Collation | Cardinality | Sub_part | Packed | Null | Index_type | Comment | Index_comment |
+----------+------------+-------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
| messages | 0 | PRIMARY | 1 | id | A | 27531939 | NULL | NULL | | BTREE | | |
| messages | 1 | to_number | 1 | to_number | A | 22 | NULL | NULL | | BTREE | | |
| messages | 1 | from_id | 1 | from_id | A | 529460 | NULL | NULL | | BTREE | | |
| messages | 1 | from_id | 2 | to_number | A | 529460 | NULL | NULL | | BTREE | | |
| messages | 1 | user_id_2 | 1 | user_id | A | 655522 | NULL | NULL | | BTREE | | |
| messages | 1 | user_id_2 | 2 | read_state | A | 917731 | NULL | NULL | | BTREE | | |
| messages | 1 | user_id_2 | 3 | removed | A | 949377 | NULL | NULL | | BTREE | | |
| messages | 1 | idx_user_id | 1 | user_id | A | 809762 | NULL | NULL | | BTREE | | |
| messages | 1 | idx_from_id | 1 | from_id | A | 302548 | NULL | NULL | | BTREE | | |
+----------+------------+-------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
desc messages;
+------------+-------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+------------+-------------+------+-----+---------+----------------+
| id | int(11) | NO | PRI | NULL | auto_increment |
| from_id | int(11) | NO | MUL | NULL | |
| user_id | int(11) | NO | MUL | NULL | |
| group_id | int(11) | NO | | NULL | |
| to_number | varchar(30) | NO | MUL | NULL | |
| msg | text | NO | | NULL | |
| image | varchar(20) | NO | | NULL | |
| date | bigint(20) | NO | | NULL | |
| read_state | tinyint(1) | NO | | 0 | |
| removed | tinyint(1) | NO | | NULL | |
+------------+-------------+------+-----+---------+----------------+
EXPLAIN EXTENDED:
+----+--------------------+----------+-------------+---------------+-----------+---------+--------------------+--------+----------+---------------------------------------------------------------------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+--------------------+----------+-------------+---------------+-----------+---------+--------------------+--------+----------+---------------------------------------------------------------------------+
| 1 | PRIMARY | dialog | index_merge | uid1,uid2 | uid1,uid2 | 4,4 | NULL | 1707 | 100.00 | Using sort_union(uid1,uid2); Using where; Using temporary; Using filesort |
| 1 | PRIMARY | users | ALL | PRIMARY | NULL | NULL | NULL | 608993 | 100.00 | Range checked for each record (index map: 0x1) |
| 1 | PRIMARY | messages | eq_ref | PRIMARY | PRIMARY | 4 | numbers.dialog.mid | 1 | 100.00 | |
| 2 | DEPENDENT SUBQUERY | msgs | ref | user_id_2 | user_id_2 | 6 | const,const,const | 2607 | 100.00 | Using where |
+----+--------------------+----------+-------------+---------------+-----------+---------+--------------------+--------+----------+---------------------------------------------------------------------------+

Making a few guesses, something like this might be more efficient:-
SELECT DISTINCT users.id AS uid,
name,
avatar,
avatar_date,
driver,
msg,
`DATE`,
messages.removed,
from_id = 528798 AS outbox ,
CASE WHEN msgs.read_state IS NULL THEN 1 ELSE 0 END AS read_state
FROM messages
INNER JOIN dialog ON messages.id = dialog.mid
INNER JOIN users ON (dialog.uid1 = 528798 AND users.id = dialog.uid2) OR (dialog.uid2 = 528798 AND users.id = dialog.uid1)
LEFT OUTER JOIN messages msgs ON msgs.read_state = 0 AND msgs.user_id = 528798 AND removed = 0 AND (msgs.from_id = messages.from_id OR msgs.from_id = messages.user_id)
ORDER BY `DATE` DESC;
This is doing an extra join as a LEFT JOIN against messages again, and then using case to convert the result to 0 or 1.
the DISTINCT should cope when the LEFT JOIN can bring back multiple matching rows (if that is not possible then you can elminate the DISTINCT)
Suspect the OR clauses in the join onto users will not be that efficient. May be better to replace the INNER JOIN against users with 2 LEFT OUTER JOINs. Something like this:-
SELECT DISTINCT COALESCE(users1.id, users2.id) AS uid,
COALESCE(users1.name, users2.name),
COALESCE(users1.avatar, users2.avatar),
COALESCE(users1.avatar_date, users2.avatar_date),
COALESCE(users1.driver, users2.driver),
msg,
`DATE`,
messages.removed,
from_id = 528798 AS outbox ,
CASE WHEN msgs.read_state IS NULL THEN 1 ELSE 0 END AS read_state
FROM messages
INNER JOIN dialog ON messages.id = dialog.mid
LEFT OUTER JOIN users users1 ON (dialog.uid1 = 528798 AND users1.id = dialog.uid2)
LEFT OUTER JOIN users users2 ON (dialog.uid2 = 528798 AND users2.id = dialog.uid1)
LEFT OUTER JOIN messages msgs ON msgs.read_state = 0 AND msgs.user_id = 528798 AND removed = 0 AND (msgs.from_id = messages.from_id OR msgs.from_id = messages.user_id)
WHERE users1.id IS NOT NULL
OR users2.id IS NOT NULL
ORDER BY `DATE` DESC;

Related

what index to use for query

controller
#from = params[:from] ? 4.hours.since(Time.zone.parse(params[:from])) : today_start_time
#to = params[:to] ? 4.hours.since(Time.zone.parse(params[:to])) : #from
#franchise = current_user_accessible_franchises_filtered
#orders_type = Restaurant::DELIVERY_TYPES.detect { |t| t == params[:orders_type]} || Restaurant::DELIVERY_TYPES.first
#limit = (params[:limit] || 250).to_i
#to = 48.hours.since(#to) if #to == #from && #from == today_start_time
start_time = #from.utc.strftime('%Y-%m-%d %H:%M')
end_time = 24.hours.since(#to).utc.strftime('%Y-%m-%d %H:%M')
#orders = Order
.includes(:address, :franchise, :customer, :ordered_items, :rests, :driver)
.where(
"((orders.created_at >= ? AND orders.created_at <= ?) OR (orders.delivery_target >= ? AND orders.delivery_target <= ?)) AND orders.franchise_id in (?) AND orders.delivery_type = ?",
start_time, end_time, start_time, end_time, #franchise, #orders_type
)
.order("orders.status desc, orders.id desc")
log
(23732.8ms) SELECT COUNT(DISTINCT orders.id) FROM orders LEFT
OUTER JOIN addresses ON addresses.id = orders.address_id
LEFT OUTER JOIN franchises ON franchises.id =
orders.franchise_id LEFT OUTER JOIN customers ON
customers.id = orders.customer_id LEFT OUTER JOIN
ordered_items ON ordered_items.order_id = orders.id LEFT
OUTER JOIN ordered_items ordered_items_orders_join ON
ordered_items_orders_join.order_id = orders.id LEFT OUTER JOIN
restaurants ON restaurants.id =
ordered_items_orders_join.restaurant_id LEFT OUTER JOIN drivers
ON drivers.id = orders.driver_id WHERE (((orders.created_at >=
'2015-03-01 10:00' AND orders.created_at <= '2015-03-04 10:00') OR
(orders.delivery_target >= '2015-03-01 10:00' AND
orders.delivery_target <= '2015-03-04 10:00')) AND orders.franchise_id
in
(3,31,4,22,37,2,36,17,34,30,19,20,21,18,27,13,25,16,35,24,32,33,1,12,28,23,14,26,29,8,11)
AND orders.delivery_type = 'Mr. Delivery Restaurant')
EXPLAIN EXTENDED
mysql> EXPLAIN EXTENDED
SELECT DISTINCT `orders`.id
FROM `orders`
LEFT OUTER JOIN `addresses` ON `addresses`.`id` = `orders`.`address_id`
LEFT OUTER JOIN `franchises` ON `franchises`.`id` = `orders`.`franchise_id`
LEFT OUTER JOIN `customers` ON `customers`.`id` = `orders`.`customer_id`
LEFT OUTER JOIN `ordered_items` ON `ordered_items`.`order_id` = `orders`.`id`
LEFT OUTER JOIN `ordered_items` `ordered_items_orders_join`
ON `ordered_items_orders_join`.`order_id` = `orders`.`id`
LEFT OUTER JOIN `restaurants` ON `restaurants`.`id` = `ordered_items_orders_join`.`restaurant_id`
LEFT OUTER JOIN `drivers` ON `drivers`.`id` = `orders`.`driver_id`
WHERE ( ( (orders.created_at >= '2015-02-01 10:00'
AND orders.created_at <= '2015-02-04 10:00')
OR (orders.delivery_target >= '2015-02-01 10:00'
AND orders.delivery_target <= '2015-02-04 10:00') )
AND orders.franchise_id in (3,31,4,22,37,2,36,17,34,30,
19,20,21,18,27,13,25,16,35,24,32,33,1,12,28,23,14,26,29, 8,11 )
AND orders.delivery_type = 'Mr. Delivery Restaurant'
)
ORDER BY orders.status desc,
orders.id desc
LIMIT 250;
+----+-------------+---------------------------+--------+------------------------------------------------------------------------------------------------------------------------------+---------------------------------+---------+---------------------------------------------------------+--------+----------+----------------------------------------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+---------------------------+--------+------------------------------------------------------------------------------------------------------------------------------+---------------------------------+---------+---------------------------------------------------------+--------+----------+----------------------------------------------+
| 1 | SIMPLE | orders | ref | index_orders_on_created_at,index_orders_on_delivery_target,index_orders_on_franchise_id,index_orders_on_delivery_type,my_idx | index_orders_on_delivery_type | 77 | const | 549769 | 100.00 | Using where; Using temporary; Using filesort |
| 1 | SIMPLE | addresses | eq_ref | PRIMARY | PRIMARY | 4 | mrd_staging_new.orders.address_id | 1 | 100.00 | Using index; Distinct |
| 1 | SIMPLE | franchises | eq_ref | PRIMARY | PRIMARY | 4 | mrd_staging_new.orders.franchise_id | 1 | 100.00 | Using index; Distinct |
| 1 | SIMPLE | customers | eq_ref | PRIMARY | PRIMARY | 4 | mrd_staging_new.orders.customer_id | 1 | 100.00 | Using index; Distinct |
| 1 | SIMPLE | ordered_items | ref | index_ordered_items_on_order_id | index_ordered_items_on_order_id | 5 | mrd_staging_new.orders.id | 2 | 100.00 | Using index; Distinct |
| 1 | SIMPLE | ordered_items_orders_join | ref | index_ordered_items_on_order_id | index_ordered_items_on_order_id | 5 | mrd_staging_new.orders.id | 2 | 100.00 | Distinct |
| 1 | SIMPLE | restaurants | eq_ref | PRIMARY | PRIMARY | 4 | mrd_staging_new.ordered_items_orders_join.restaurant_id | 1 | 100.00 | Using index; Distinct |
| 1 | SIMPLE | drivers | eq_ref | PRIMARY | PRIMARY | 4 | mrd_staging_new.orders.driver_id | 1 | 100.00 | Using index; Distinct |
+----+-------------+---------------------------+--------+------------------------------------------------------------------------------------------------------------------------------+---------------------------------+---------+---------------------------------------------------------+--------+----------+----------------------------------------------+
8 rows in set, 1 warning (0.00 sec)
indices on Orders table
| Table | Non_unique | Key_name | Seq_in_index | Column_name | Collation | Cardinality | Sub_part | Packed | Null | Index_type | Comment | Index_comment |
+--------+------------+---------------------------------------+--------------+-----------------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
| orders | 0 | PRIMARY | 1 | id | A | 935943 | NULL | NULL | | BTREE | | |
| orders | 1 | index_orders_on_delivered_at | 1 | delivered_at | A | 935943 | NULL | NULL | YES | BTREE | | |
| orders | 1 | index_orders_on_updated_at | 1 | updated_at | A | 935943 | NULL | NULL | YES | BTREE | | |
| orders | 1 | index_orders_on_created_at | 1 | created_at | A | 935943 | NULL | NULL | YES | BTREE | | |
| orders | 1 | index_orders_on_pickup_at | 1 | pickup_at | A | 935943 | NULL | NULL | YES | BTREE | | |
| orders | 1 | index_orders_on_coupon_id | 1 | coupon_id | A | 14856 | NULL | NULL | YES | BTREE | | |
| orders | 1 | index_orders_on_driver_id | 1 | driver_id | A | 4159 | NULL | NULL | YES | BTREE | | |
| orders | 1 | index_orders_on_delivery_target | 1 | delivery_target | A | 935943 | NULL | NULL | YES | BTREE | | |
| orders | 1 | index_orders_on_franchise_id | 1 | franchise_id | A | 48 | NULL | NULL | YES | BTREE | | |
| orders | 1 | idx_for_customers_report | 1 | customer_id | A | 935943 | NULL | NULL | YES | BTREE | | |
| orders | 1 | idx_for_customers_report | 2 | delivered_at | A | 935943 | NULL | NULL | YES | BTREE | | |
| orders | 1 | idx_for_customers_report | 3 | created_at | A | 935943 | NULL | NULL | YES | BTREE | | |
| orders | 1 | idx_for_customers_report | 4 | status | A | 935943 | NULL | NULL | | BTREE | | |
| orders | 1 | idx_for_customers_report | 5 | franchise_id | A | 935943 | NULL | NULL | YES | BTREE | | |
| orders | 1 | orders_fetch_index_for_revenue_report | 1 | id | A | 935943 | NULL | NULL | | BTREE | | |
| orders | 1 | orders_fetch_index_for_revenue_report | 2 | created_at | A | 935943 | NULL | NULL | YES | BTREE | | |
| orders | 1 | orders_fetch_index_for_revenue_report | 3 | delivered_at | A | 935943 | NULL | NULL | YES | BTREE | | |
| orders | 1 | orders_fetch_index_for_revenue_report | 4 | status | A | 935943 | NULL | NULL | | BTREE | | |
| orders | 1 | orders_fetch_index_for_revenue_report | 5 | franchise_id | A | 935943 | NULL | NULL | YES | BTREE | | |
| orders | 1 | orders_fetch_index_for_revenue_report | 6 | customer_id | A | 935943 | NULL | NULL | YES | BTREE | | |
| orders | 1 | orders_fetch_index_for_revenue_report | 7 | address_id | A | 935943 | NULL | NULL | YES | BTREE | | |
| orders | 1 | index_orders_on_address_id | 1 | address_id | A | 935943 | NULL | NULL | YES | BTREE | | |
| orders | 1 | index_orders_on_customer_id | 1 | customer_id | A | 935943 | NULL | NULL | YES | BTREE | | |
| orders | 1 | index_orders_on_delivery_type | 1 | delivery_type | A | 4 | NULL | NULL | | BTREE | | |
| orders | 1 | index_orders_on_status | 1 | status | A | 8 | NULL | NULL | | BTREE | | |
| orders | 1 | index_orders_on_should_pay_restaurant | 1 | should_pay_restaurant | A | 4 | NULL | NULL | YES | BTREE | | |
| orders | 1 | my_idx | 1 | created_at | A | 935943 | NULL | NULL | YES | BTREE | | |
| orders | 1 | my_idx | 2 | delivery_target | A | 935943 | NULL | NULL | YES | BTREE | | |
| orders | 1 | my_idx | 3 | franchise_id | A | 935943 | NULL | NULL | YES | BTREE | | |
| orders | 1 | my_idx | 4 | delivery_type | A | 935943 | NULL | NULL | | BTREE | | |
| orders | 1 | my_idx | 5 | status | A | 935943 | NULL | NULL | | BTREE | | |
| orders | 1 | my_idx | 6 | id | A | 935943 | NULL | NULL | | BTREE | | |
total quantity
mysql> select count(*) from orders;
+----------+
| count(*) |
+----------+
| 965520 |
+----------+
1 row in set (0.32 sec)
I have tried to add a composite index
create index my_idx on orders(created_at, delivery_target, franchise_id, delivery_type);
even
create index my_idx on orders(created_at, delivery_target, franchise_id, delivery_type, status, id);
but without success.
Any ideas?
INDEX(delivery_type, -- because it is "= constant"
franchise_id) -- the only other viable column
Indexing any column hiding in an OR is virtually futile.
I do question your ORDER BY status while doing DISTINCT id and no mention of status.
You may be able to speed it up by turning the OR into a UNION:
( SELECT orders.status, orders.id
FROM ...
JOIN ...
WHERE orders.created_at ...
AND ...
ORDER BY ...
LIMIT 250
)
UNION DISTINCT
( SELECT orders.status, orders.id
FROM ...
JOIN ...
WHERE orders.delivery_target ... -- note difference
AND ...
ORDER BY ...
LIMIT 250
)
ORDER BY status DESC, id DESC -- yes, again
LIMIT 250; -- yes, again
But for this reformulation to work well, there needs to be new indexes. I am not sure whether these
INDEX(delivery_type, franchise_id, created_at),
INDEX(delivery_type, franchise_id, delivery_target),
or these
INDEX(delivery_type, created_at),
INDEX(delivery_type, delivery_target),
would work better. Add all 4; do EXPLAIN to see which ones it uses.

Very slow mysql query: left join multiple table and where clause for each table

I have one base table(id table) and multiple tables(data table) which have the data related to the id's and each table has different type data.
I want to filter id's by multiple condition from these different table.
I created query as follows, but this is too slow like 3 minutes. I checked explain information but still can not find better way. Could anyone find the way to get it faster?
query
select jj.id, jj.imgtitle, jj.alias
from jjtable jj
inner join jjtable_catg jjc on jj.catid = jjc.cid
left join jjtable_map jjtm_name on jj.id = jjtm_name.picid
left join jjtable_tags jjts_name on jjtm_name.tid = jjts_name.tid
left join jjtable_map_ge jjtm_ge on jj.id = jjtm_ge.picid
left join jjtable_tags jjts_ge on jjtm_ge.tid = jjts_ge.tid
left join jjtable_map_ban jjtm_ban on jj.id = jjtm_ban.picid
left join jjtable_tags jjts_ban on jjtm_ban.tid = jjts_ban.tid
left join jjtable_map_per jjtm_per on jj.id = jjtm_per.picid
left join jjtable_tags jjts_per on jjtm_per.tid = jjts_per.tid
left join jjtable_map_fea jjtm_fea on jj.id = jjtm_fea.picid
left join jjtable_tags jjts_fea on jjtm_fea.tid = jjts_fea.tid
left join jjtable_map_ev jjtm_ev on jj.id = jjtm_ev.picid
left join jjtable_tags jjts_ev on jjtm_ev.tid = jjts_ev.tid
left join jjtable_map_ag jjtm_ag on jj.id = jjtm_ag.picid
left join jjtable_tags jjts_ag on jjtm_ag.tid = jjts_ag.tid
left join jjtable_map_fa jjtm_fa on jj.id = jjtm_fa.picid
left join jjtable_tags jjts_fa on jjtm_fa.tid = jjts_fa.tid
left join jjtable_map_im jjtm_im on jj.id = jjtm_im.picid
left join jjtable_tags jjts_im on jjtm_im.tid = jjts_im.tid where jj.published = 1
and jj.approved = 1
and jjts_fea.tid in(87,90)
and jjts_fea.delete_flag = 0
and jjtm_fea.delete_flag = 0
and jjc.cid in(4,10)
and jjts_name.tid in(77)
and jjts_name.delete_flag = 0
and jjtm_name.delete_flag = 0
and jjts_per.tid in(28,36)
and jjts_per.delete_flag = 0
and jjtm_per.delete_flag = 0
and jjts_ag.tid in(98,99)
and jjts_ag.delete_flag = 0
and jjtm_ag.delete_flag = 0
and jjts_fa.tid in(104,107)
and jjts_fa.delete_flag = 0
and jjtm_fa.delete_flag = 0
group by jj.id
order by case when date(jj.date) > date_add(date(now()), interval -14 day) then jj.view end DESC, jj.id DESC
EDIT: Thank you for your comment. firstly I add information as your request.
WHY NEED THIS QUERY;
This query are intended to received the post data from "form input" tag which user select options from different categories to filter ID. That is why this query includes tables and data which are not used in this example.
The reason why there are so many left join is that each 'map' table has category mapping information to filter ID's. etc, user want to find ID's which are mapped with 'fea', 87, 90, and 'ag', 98, 99.
explain select;
+----+-------------+--------------+--------+-------------------+-----------------+---------+------------------------------------------------+------+---------------------------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+--------------+--------+-------------------+-----------------+---------+------------------------------------------------+------+---------------------------------+
| 1 | SIMPLE | jjts_name | const | PRIMARY | PRIMARY | 4 | const | 1 | Using temporary; Using filesort |
| 1 | SIMPLE | jjc | range | PRIMARY | PRIMARY | 4 | NULL | 2 | Using where |
| 1 | SIMPLE | jjts_pe | range | PRIMARY | PRIMARY | 4 | NULL | 2 | Using where; Using join buffer |
| 1 | SIMPLE | jjts_fea | range | PRIMARY | PRIMARY | 4 | NULL | 2 | Using where; Using join buffer |
| 1 | SIMPLE | jjts_ag | range | PRIMARY | PRIMARY | 4 | NULL | 2 | Using where; Using join buffer |
| 1 | SIMPLE | jjts_fa | range | PRIMARY | PRIMARY | 4 | NULL | 2 | Using where; Using join buffer |
| 1 | SIMPLE | jj | ref | PRIMARY,idx_catid | idx_catid | 4 | jjc.cid | 95 | Using where |
| 1 | SIMPLE | jjtm_ag | eq_ref | udx_picid_tid | udx_picid_tid | 8 | jj.id,jjts_ag.tid | 1 | Using where |
| 1 | SIMPLE | jjtm_fa | eq_ref | udx_picid_tid | udx_picid_tid | 8 | jj.id,jjts_fa.tid | 1 | Using where |
| 1 | SIMPLE | jjtm_fea | eq_ref | udx_picid_tid | udx_picid_tid | 8 | jj.id,jjts_fea.tid | 1 | Using where |
| 1 | SIMPLE | jjtm_pe | eq_ref | udx_picid_tid | udx_picid_tid | 8 | jjtm_fea.picid,jjts_per.tid | 1 | Using where |
| 1 | SIMPLE | jjtm_ev | ref | udx_picid_tid | udx_picid_tid | 4 | jjtm_fa.picid | 3 | Using index |
| 1 | SIMPLE | jjts_ev | eq_ref | PRIMARY | PRIMARY | 4 | jjtm_ev.tid | 1 | Using index |
| 1 | SIMPLE | jjtm_im | ref | udx_picid_tid | udx_picid_tid | 4 | jjtm_pe.picid | 6 | Using index |
| 1 | SIMPLE | jjts_im | eq_ref | PRIMARY | PRIMARY | 4 | jjtm_im.tid | 1 | Using index |
| 1 | SIMPLE | jjtm_ge | ref | udx_picid_tid | udx_picid_tid | 4 | jj.id | 23 | Using index |
| 1 | SIMPLE | jjts_ge | eq_ref | PRIMARY | PRIMARY | 4 | jjtm_ge.tid | 1 | Using index |
| 1 | SIMPLE | jjtm_ban | ref | udx_picid_tid | udx_picid_tid | 4 | jjtm_pe.picid | 9 | Using index |
| 1 | SIMPLE | jjts_ban | eq_ref | PRIMARY | PRIMARY | 4 | jjtm_ban.tid | 1 | Using index |
| 1 | SIMPLE | jjtm_name | eq_ref | udx_picid_tid | udx_picid_tid | 8 | jjtm_fea.picid,const | 1 | Using where |
+----+-------------+--------------+--------+-------------------+-----------------+---------+------------------------------------------------+------+---------------------------------+
20 rows in set (2 min 15.87 sec)
show columns;
mysql> show columns from jjtables;
+--------------+------------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+--------------+------------------+------+-----+---------+----------------+
| id | int(11) | NO | PRI | NULL | auto_increment |
| catid | int(11) | NO | MUL | 0 | |
| imgtitle | text | NO | | NULL | |
| alias | varchar(255) | NO | | | |
| date | datetime | NO | | NULL | |
| view | int(11) | NO | | 0 | |
| published | tinyint(1) | NO | | 0 | |
| approved | tinyint(1) | NO | | 0 | |
+--------------+------------------+------+-----+---------+----------------+
mysql> show columns from jjtable_catg;
+--------------+---------------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+--------------+---------------------+------+-----+---------+----------------+
| cid | int(11) | NO | PRI | NULL | auto_increment |
| name | varchar(255) | NO | | | |
| alias | varchar(255) | NO | | | |
| parent | int(11) | NO | MUL | 0 | |
| desc | text | YES | | NULL | |
| order | int(11) | NO | | 0 | |
+--------------+---------------------+------+-----+---------+----------------+
mysql> show columns from jjtable_tags;
+--------------+-------------+------+-----+---------------------+----------------+
| Field | Type | Null | Key | Default | Extra |
+--------------+-------------+------+-----+---------------------+----------------+
| tid | int(11) | NO | PRI | NULL | auto_increment |
| name | varchar(60) | NO | UNI | NULL | |
| talias | varchar(60) | NO | | | |
| ttypeid | int(11) | NO | | 1 | |
| pa_tid | int(11) | NO | | 0 | |
| delete_flag | tinyint(1) | NO | | 0 | |
| created | datetime | NO | | 0000-00-00 00:00:00 | |
| created_by | int(11) | NO | | 0 | |
| modified | datetime | NO | | 0000-00-00 00:00:00 | |
| modified_by | int(11) | NO | | 0 | |
| t_due | datetime | NO | | 0000-00-00 00:00:00 | |
+--------------+-------------+------+-----+---------------------+----------------+
mysql> show columns from jjtable_map;
+-------------+------------+------+-----+---------------------+----------------+
| Field | Type | Null | Key | Default | Extra |
+-------------+------------+------+-----+---------------------+----------------+
| id | int(11) | NO | PRI | NULL | auto_increment |
| picid | int(11) | NO | MUL | NULL | |
| tid | int(11) | NO | | NULL | |
| delete_flag | tinyint(1) | NO | | 0 | |
| created | datetime | NO | | 0000-00-00 00:00:00 | |
| created_by | int(11) | NO | | 0 | |
| modified | datetime | NO | | 0000-00-00 00:00:00 | |
| modified_by | int(11) | NO | | 0 | |
+-------------+------------+------+-----+---------------------+----------------+
Sorry for my poor explanation. I hope this edit help your better understanding.
EDIT END
Regards
Try this: Convert your left joins to a bunch of WHERE EXISTS sub-queries and drop the GROUP BY, because (that's my suspicion here) that's what you really wanted to express.
select
jj.id, jj.imgtitle, jj.alias
from
jjtable jj
-- you could drop this join, you don't do anything with jjtable_catg
inner join jjtable_catg jjc on jjc.cid = jj.catid
where
jj.published = 1
and jj.approved = 1
and jjc.cid in(4, 10)
and exists (
select 1
from jjtable_map m inner join jjtable_tags t on m.tid = t.tid
where m.picid = jj.id m.delete_flag = 0 and t.tid in (77) and t.delete_flag = 0
)
and exists (
select 1
from jjtable_map_per m inner join jjtable_tags t on m.tid = t.tid
where m.picid = jj.id m.delete_flag = 0 and t.tid in (28,36) and t.delete_flag = 0
)
and exists (
select 1
from jjtable_map_fea m inner join jjtable_tags t on m.tid = t.tid
where m.picid = jj.id m.delete_flag = 0 and t.tid in (87,90) and t.delete_flag = 0
)
and exists (
select 1
from jjtable_map_ag m inner join jjtable_tags t on m.tid = t.tid
where m.picid = jj.id m.delete_flag = 0 and t.tid in (98,99) and t.delete_flag = 0
)
and exists (
select 1
from jjtable_map_fa m inner join jjtable_tags t on m.tid = t.tid
where m.picid = jj.id m.delete_flag = 0 and t.tid in (104,107) and t.delete_flag = 0
)
order by
case when date(jj.date) > date_add(date(now()), interval -14 day) then jj.view end DESC,
jj.id DESC
Also create these composite indexes (if they're missing):
jjtable_catg: (cid)
jjtable_tags: (tid, delete_flag)
jjtable_map and all jjtable_map_*: (picid, delete_flag, tid)
One big improvement would be to use multiple conditions at joins instead of get a huge set and then put the condition at the end. I mean, if you put the condition at the same time you join your set will be smaller and your response time better:
select jj.id, jj.imgtitle, jj.alias
from jjtable jj
inner join jjtable_catg jjc on jj.catid = jjc.cid
left join jjtable_map jjtm_name on jj.id = jjtm_name.picid and jjtm_name.delete_flag = 0
...
This query already have a lot of tables. Remove any table that is not used. Is there any reason for this joins?
left join jjtable_map_im jjtm_im on jj.id = jjtm_im.picid
left join jjtable_tags jjts_im on jjtm_im.tid = jjts_im.tid where jj.published = 1
jjtm_im and jjts_im are not used at all.

Optimize query to GROUP BY uid with UNION

When GROUP BY uid and GROUP BY anonym_id are placed into queries the execution time problem appears > 2sec.
The question is - how to optimize/override this query with effect like GROUP BY statement?
( SELECT u.id as uid, u.name, u.avatar, u.avatar_date, u.driver,
u.vehicle, m.msg, m.removed, (m.date DIV 1000) AS date, m.from_id = 1 AS outbox,
CASE WHEN m.read_state IS NULL THEN 1 ELSE 0 END AS read_state, d.anonym_id
FROM dialog as d CROSS JOIN messages AS m CROSS JOIN users AS u
WHERE (m.id=d.mid AND ((d.uid1 = 1 and u.id = d.uid2) or (d.uid2 = 1 and u.id = d.uid1)))
GROUP BY uid
ORDER BY d.id DESC )
UNION
( SELECT u.id as uid, u.name, u.avatar, u.avatar_date, u.driver, u.vehicle,
m.msg, m.removed, (m.date DIV 1000) AS date, m.from_id = 1 AS outbox,
CASE WHEN m.read_state IS NULL THEN 1 ELSE 0 END AS read_state,
d.anonym_id
FROM dialog as d FORCE KEY(anonym_id) CROSS JOIN messages AS m ON d.mid=m.id
CROSS JOIN users AS u ON u.id=d.uid2
WHERE d.anonym_id=100001
GROUP BY d.uid2
ORDER BY d.id DESC )
LIMIT 0, 20
mysql> show index from users;
+-------+------------+-------------+--------------+--------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
| Table | Non_unique | Key_name | Seq_in_index | Column_name | Collation | Cardinality | Sub_part | Packed | Null | Index_type | Comment | Index_comment |
+-------+------------+-------------+--------------+--------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
| users | 0 | PRIMARY | 1 | id | A | 606396 | NULL | NULL | | BTREE | | |
| users | 1 | number | 1 | number | A | 606396 | NULL | NULL | | BTREE | | |
| users | 1 | name | 1 | name | A | 121279 | NULL | NULL | | BTREE | | |
| users | 1 | show_phone | 1 | show_phone | A | 10 | NULL | NULL | | BTREE | | |
| users | 1 | show_mail | 1 | show_mail | A | 14 | NULL | NULL | | BTREE | | |
| users | 1 | show_on_map | 1 | show_on_map | A | 18 | NULL | NULL | | BTREE | | |
| users | 1 | show_on_map | 2 | map_activity | A | 606396 | NULL | NULL | | BTREE | | |
| users | 1 | udid | 1 | udid | A | 606396 | NULL | NULL | YES | BTREE | | |
| users | 1 | blocked | 1 | blocked | A | 10 | NULL | NULL | | BTREE | | |
+-------+------------+-------------+--------------+--------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
mysql> show index from dialog;
+--------+------------+-----------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
| Table | Non_unique | Key_name | Seq_in_index | Column_name | Collation | Cardinality | Sub_part | Packed | Null | Index_type | Comment | Index_comment |
+--------+------------+-----------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
| dialog | 0 | PRIMARY | 1 | id | A | 2964484 | NULL | NULL | | BTREE | | |
| dialog | 1 | uid1 | 1 | uid1 | A | 211748 | NULL | NULL | | BTREE | | |
| dialog | 1 | uid1 | 2 | uid2 | A | 2964484 | NULL | NULL | | BTREE | | |
| dialog | 1 | uid2 | 1 | uid2 | A | 1482242 | NULL | NULL | | BTREE | | |
| dialog | 1 | anonym_id | 1 | anonym_id | A | 18 | NULL | NULL | | BTREE | | |
| dialog | 1 | idx_mid | 1 | mid | A | 200 | NULL | NULL | | BTREE | | |
+--------+------------+-----------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
mysql> show index from messages;
+----------+------------+-----------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
| Table | Non_unique | Key_name | Seq_in_index | Column_name | Collation | Cardinality | Sub_part | Packed | Null | Index_type | Comment | Index_comment |
+----------+------------+-----------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
| messages | 0 | PRIMARY | 1 | id | A | 23227116 | NULL | NULL | | BTREE | | |
| messages | 1 | user_id_2 | 1 | user_id | A | 188 | NULL | NULL | | BTREE | | |
| messages | 1 | user_id_2 | 2 | read_state | A | 188 | NULL | NULL | | BTREE | | |
| messages | 1 | user_id_2 | 3 | removed | A | 188 | NULL | NULL | | BTREE | | |
| messages | 1 | from_id | 1 | from_id | A | 188 | NULL | NULL | | BTREE | | |
| messages | 1 | from_id | 2 | to_number | A | 188 | NULL | NULL | | BTREE | | |
+----------+------------+-----------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
EXPLAIN EXTENDED
1 PRIMARY d index_merge uid1,uid2,idx_mid uid1,uid2 4,4 26 100.00 Using sort_union(uid1,uid2); Using where; Using temporary; Using filesort
1 PRIMARY u ALL PRIMARY 625866 100.00 Using where; Using join buffer
1 PRIMARY m eq_ref PRIMARY PRIMARY 4 numbers.d.mid 1 100.00
2 UNION d ref anonym_id anonym_id 4 const 1 100.00
2 UNION u eq_ref PRIMARY PRIMARY 4 numbers.d.uid2 1 100.00
2 UNION m eq_ref PRIMARY PRIMARY 4 numbers.d.mid 1 100.00
UNION RESULT <union1,2> ALL

Convert this subquery into a JOIN?

Does anyone know if it's possible to convert this subquery into a JOIN?
SELECT DISTINCT
lastname,
c.fullname,
(SELECT COUNT(lg.action) FROM tbl_log AS lg WHERE lg.userid = u.id AND lg.course = c.id) AS 'Total Course Hits Per Student'
FROM tbl_user AS u
JOIN tbl_user_enrolments AS ents ON ents.userid = u.id
JOIN tbl_enrol AS en ON ents.enrolid = en.id
JOIN tbl_course AS C ON c.id = en.courseid
JOIN tbl_context AS ctx ON c.id = ctx.instanceid
JOIN tbl_role_assignments AS ra ON ra.contextid = ctx.id AND ra.userid = u.id
LOG TABLE
+-------------+---------------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+-------------+---------------------+------+-----+---------+----------------+
| id | | NO | PRI | NULL | auto_increment |
| time | | NO | | NULL | |
| userid | | NO | | NULL | |
| course | | NO | | NULL | |
| action | | NO | | NULL | |
+-------------+---------------------+------+-----+---------+----------------+
USER Table
+--------------+---------------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+--------------+---------------------+------+-----+---------+----------------+
| id | | NO | PRI | NULL | auto_increment |
| username | | NO | | NULL | |
| userpassword | | NO | | NULL | |
| lastname | | NO | | NULL | |
| firstname | | NO | | NULL | |
+--------------+---------------------+------+-----+---------+----------------+
COURSE table
+--------------+---------------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+--------------+---------------------+------+-----+---------+----------------+
| id | | NO | PRI | NULL | auto_increment |
| category | | NO | | NULL | |
| fullname | | NO | | NULL | |
| shortname | | NO | | NULL | |
+--------------+---------------------+------+-----+---------+----------------+
I link the users together via the enrolment and context tables.
Cant you take a query on the log_table
SELECT
COUNT(tbl_log.action)
lastname,
c.fullname,
FROM tbl_log
JOIN tbl_user ON tbl_log.userid = tbl_user.id
JOIN tbl_course ON tbl_log.course = tbl_course.id
GROUP BY tbl_log.userid, tbl_log.course
I don't know if you need the other tables? This would provide a count, the student name and the course name if I am not mistaken. However, you only get the actual logs, so no occurences for students that haven't done anything.
Otherwise an OUTER JOIN may suffice on the log-table. I have no time to check now in SQL fiddle. Hope this already helps you a bit on your way.

How do I optimize a basic MySQL query with basic inner join?

SELECT au.*
FROM users au
INNER JOIN friends fa ON au.id = fa.to_user_id
WHERE fa.from_user_id = 369 AND fa.persona_id IN('1241')
GROUP BY au.id
ORDER BY id DESC
LIMIT 0, 9999999999;
Here is the explain
mysql> EXPLAIN SELECT au.* FROM users au INNER JOIN friends fa ON au.id = fa.to_user_id WHERE fa.from_user_id = 369 AND fa.persona_id IN('1241') GROUP BY au.id ORDER BY id DESC LIMIT 0, 9999999999;
+----+-------------+-------+-------------+------------------------------------------------------------+------------------------------------+---------+--------------------+------+---------------------------------------------------------------------------------------------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-------+-------------+------------------------------------------------------------+------------------------------------+---------+--------------------+------+---------------------------------------------------------------------------------------------------+
| 1 | SIMPLE | fa | index_merge | from_user_id,to_user_id,persona_id,from_user_id_persona_id | persona_id,from_user_id_persona_id | 4,8 | NULL | 49 | Using intersect(persona_id,from_user_id_persona_id); Using where; Using temporary; Using filesort |
| 1 | SIMPLE | au | eq_ref | PRIMARY | PRIMARY | 4 | kjdb.fa.to_user_id | 1 | |
+----+-------------+-------+-------------+------------------------------------------------------------+------------------------------------+---------+--------------------+------+---------------------------------------------------------------------------------------------------+
2 rows in set (0.00 sec)
I only have 300,000 rows in this table, but it's taking forever. (about .75 seconds to run it)
mysql> desc friends;
+-------------------+------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+-------------------+------------+------+-----+---------+----------------+
| id | int(11) | NO | PRI | NULL | auto_increment |
| from_user_id | int(11) | NO | MUL | NULL | |
| to_user_id | int(11) | NO | MUL | NULL | |
| persona_id | int(11) | NO | MUL | NULL | |
| action_by_user_id | int(11) | NO | MUL | NULL | |
| is_disabled | tinyint(1) | NO | | 0 | |
| created_at | datetime | YES | | NULL | |
| updated_at | datetime | YES | | NULL | |
+-------------------+------------+------+-----+---------+----------------+
8 rows in set (0.01 sec)
> desc users;
+---------------------+--------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+---------------------+--------------+------+-----+---------+----------------+
| id | int(11) | NO | PRI | NULL | auto_increment |
| username | varchar(255) | NO | UNI | NULL | |
| first_name | varchar(255) | NO | MUL | NULL | |
| last_name | varchar(255) | YES | MUL | NULL | |
| email | varchar(255) | YES | UNI | NULL | |
here are my indexes:
mysql> show indexes from friends;
+-------------------+------------+-------------------------+--------------+-------------------+-----------+-------------+----------+--------+------+------------+---------+
| Table | Non_unique | Key_name | Seq_in_index | Column_name | Collation | Cardinality | Sub_part | Packed | Null | Index_type | Comment |
+-------------------+------------+-------------------------+--------------+-------------------+-----------+-------------+----------+--------+------+------------+---------+
| friends | 0 | PRIMARY | 1 | id | A | 388926 | NULL | NULL | | BTREE | |
| friends | 0 | from_user_id | 1 | from_user_id | A | 17 | NULL | NULL | | BTREE | |
| friends | 0 | from_user_id | 2 | to_user_id | A | 388926 | NULL | NULL | | BTREE | |
| friends | 0 | from_user_id | 3 | persona_id | A | 388926 | NULL | NULL | | BTREE | |
| friends | 1 | to_user_id | 1 | to_user_id | A | 19446 | NULL | NULL | | BTREE | |
| friends | 1 | persona_id | 1 | persona_id | A | 32410 | NULL | NULL | | BTREE | |
| friends | 1 | action_by_user_id | 1 | action_by_user_id | A | 9972 | NULL | NULL | | BTREE | |
| friends | 1 | from_user_id_persona_id | 1 | from_user_id | A | 9486 | NULL | NULL | | BTREE | |
| friends | 1 | from_user_id_persona_id | 2 | persona_id | A | 35356 | NULL | NULL | | BTREE | |
+-------------------+------------+-------------------------+--------------+-------------------+-----------+-------------+----------+--------+------+------------+---------+
Add this index:
ALTER TABLE friends ADD INDEX ix_from_persona_to (from_user_id,persona_id,to_user_id);
and see if that helps.
If not, you may have to add a FORCE INDEX:
SELECT
au.*
FROM
users au
INNER JOIN
friends fa ON au.id = fa.to_user_id
FORCE INDEX
(ix_from_persona_to)
WHERE
fa.from_user_id = 369 AND
fa.persona_id IN ('1241')
GROUP BY
au.id
ORDER BY
id DESC
LIMIT
0, 9999999999;
the order by on a group by is causing a temp table + filesort which is killing your performance.
try subselects?
SELECT au.*
FROM users au
WHERE au.id IN (
SELECT DISTINCT fa.to_user_id
FRoM friends fa
WHERE fa.from_user_id = 369 AND fa.persona_id IN('1241')
)
ORDER BY id DESC
LIMIT 0, 9999999999;
then add indexes on friends.from_user_id , persona_id