MySQL Join 1 to 1 in N items table - mysql

I need some help on my shop database structure. To fetch items from category, there are already some joins, but i would like to add an image to an item, from other table. Main items table doesn't have an image id, so we just get one from other table by it's weight = 1.
So, there is a structure:
CREATE TABLE IF NOT EXISTS `categories` (
`category_id` INT(11) UNSIGNED NOT NULL AUTO_INCREMENT,
`name` VARCHAR(255)
CHARACTER SET utf8mb4 DEFAULT NULL
COMMENT 'Category title',
`parent_id` INT(11) NOT NULL DEFAULT '0'
COMMENT 'Category Parent ID',
`status` TINYINT(1) DEFAULT '0'
COMMENT 'Category active status',
`weight` INT(11) DEFAULT '0',
`slug` VARCHAR(255)
CHARACTER SET utf8mb4 DEFAULT NULL
COMMENT 'Category url alias',
PRIMARY KEY (`category_id`)
)
ENGINE = InnoDB
DEFAULT CHARSET = utf8mb4
COLLATE = utf8mb4_unicode_ci;
CREATE TABLE IF NOT EXISTS `items` (
`item_id` INT(11) UNSIGNED NOT NULL AUTO_INCREMENT,
`name` VARCHAR(255)
CHARACTER SET utf8mb4 NOT NULL DEFAULT ''
COMMENT 'Item name',
`description` TEXT CHARACTER SET utf8mb4 NOT NULL
COMMENT 'Item description',
`user_id` INT(11) UNSIGNED NOT NULL DEFAULT '0'
COMMENT 'User id',
`category_id` INT(11) UNSIGNED NOT NULL DEFAULT '0'
COMMENT 'Category id',
`price` DECIMAL(10, 2) NOT NULL DEFAULT '0.00'
COMMENT 'Item price',
`status` INT(1) UNSIGNED NOT NULL DEFAULT '1'
COMMENT 'Item status',
`deleted` INT(1) UNSIGNED NOT NULL DEFAULT '0'
COMMENT 'Delite status',
`blocked` INT(1) UNSIGNED NOT NULL DEFAULT '0'
COMMENT 'Block status',
PRIMARY KEY (`item_id`)
)
ENGINE = InnoDB
DEFAULT CHARSET = utf8mb4
COLLATE = utf8mb4_unicode_ci;
CREATE TABLE IF NOT EXISTS `items_images` (
`id` INT(11) UNSIGNED NOT NULL AUTO_INCREMENT,
`item_id` INT(10) UNSIGNED NOT NULL DEFAULT '0',
`file` VARCHAR(255)
CHARACTER SET utf8mb4 NOT NULL DEFAULT '',
`weight` INT(11) UNSIGNED NOT NULL DEFAULT '0',
`status` TINYINT(1) UNSIGNED NOT NULL DEFAULT '0',
`deleted` TINYINT(1) UNSIGNED NOT NULL DEFAULT '0',
`created` INT(11) UNSIGNED NOT NULL DEFAULT '0',
PRIMARY KEY (`id`)
)
ENGINE = InnoDB
DEFAULT CHARSET = utf8mb4
COLLATE = utf8mb4_unicode_ci
ROW_FORMAT = COMPACT;
As you can see from there, category structure also contain parent_id inside, so we can always get the category tree.
Using this query is working as well and really fast.
SELECT
`i`.*,
`c1`.`name` AS `category_name`,
`c1`.`slug` AS `category_slug`,
`c2`.`name` AS `subcategory_name`,
`c2`.`slug` AS `subcategory_slug`
FROM `items` AS `i`
LEFT JOIN `categories` AS `c2` ON `c2`.`category_id` = `i`.`category_id`
LEFT JOIN `categories` AS `c1` ON `c1`.`category_id` = `c2`.`parent_id`
WHERE `i`.`deleted` = 0 AND `i`.`blocked` = 0 AND `i`.`status` = 1
ORDER BY `i`.`created` DESC
LIMIT 40
But, if i join items_images like:
SELECT
`i`.*,
`ii`.`file` AS `image`,
`c1`.`name` AS `category_name`,
`c1`.`slug` AS `category_slug`,
`c2`.`name` AS `subcategory_name`,
`c2`.`slug` AS `subcategory_slug`
FROM `items` AS `i`
LEFT JOIN `items_images` AS `ii`
ON `i`.`item_id` = `ii`.`item_id` AND `ii`.`weight` = 1
AND `ii`.`status` = 1 AND `ii`.`deleted` = 0
LEFT JOIN `categories` AS `c2` ON `c2`.`category_id` = `i`.`category_id`
LEFT JOIN `categories` AS `c1` ON `c1`.`category_id` = `c2`.`parent_id`
WHERE `i`.`deleted` = 0 AND `i`.`blocked` = 0 AND `i`.`status` = 1
ORDER BY `i`.`created` DESC LIMIT 40
Sometimes it takes up to 1 minute on 14k items with 40k images.
Is there something i can improve?! Please note, there might be items without images too. That is not requirement.
Small addition. Even single images join on items makes the query run over a minute. here a sample:
SELECT `i`.*, `ii`.`file` AS `image` FROM `items` AS `i`
LEFT JOIN `items_images` AS `ii` ON `i`.`item_id` = `ii`.`item_id`
AND `ii`.`weight` = 1 AND `ii`.`status` = 1 AND `ii`.`deleted` = 0
WHERE `i`.`deleted` =0 AND `i`.`sold` =0 AND `i`.`blocked` =0 AND `i`.`status` = 1
ORDER BY `i`.`created` DESC

You have inconsistency in naming convention.
Index parent_id
ALTER TABLE categories ADD INDEX parent_id_ind (parent_id ASC) ;
Try to run your query again.
In databases you would use indexes to improve the speed of data retrieval. An index is typically created on columns used in JOIN, WHERE, and ORDER BY clauses.

I suggest you to improve your structure, you can take it or leave it,
Here my suggestion:
DROP TABLE IF EXISTS `category`;
CREATE TABLE `category` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`name` varchar(255) CHARACTER SET utf8mb4 DEFAULT NULL COMMENT 'Category title',
`category_id` int(11) NOT NULL DEFAULT '0' COMMENT 'Category Parent ID',
`status` enum('status1','status2') COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT 'Category active status',
`weight` int(11) DEFAULT '0',
`slug` varchar(255) CHARACTER SET utf8mb4 DEFAULT NULL COMMENT 'Category url alias',
PRIMARY KEY (`id`),
KEY `category_id_ind` (`category_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
DROP TABLE IF EXISTS `item`;
CREATE TABLE `item` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`name` varchar(255) CHARACTER SET utf8mb4 NOT NULL DEFAULT '' COMMENT 'Item name',
`description` text CHARACTER SET utf8mb4 NOT NULL COMMENT 'Item description',
`user_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT 'User id',
`category_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT 'Category id',
`price` decimal(10,2) NOT NULL DEFAULT '0.00' COMMENT 'Item price',
`status` enum('status1','status2') COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'status1' COMMENT 'Item status',
`is_deleted` tinyint(1) unsigned NOT NULL DEFAULT '0' COMMENT 'Delite status',
`is_blocked` tinyint(1) unsigned NOT NULL DEFAULT '0' COMMENT 'Block status',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
DROP TABLE IF EXISTS `item_images`;
CREATE TABLE `item_images` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`item_id` int(10) unsigned NOT NULL DEFAULT '0',
`file_path` varchar(255) CHARACTER SET utf8mb4 DEFAULT NULL ,
`weight` int(11) unsigned NOT NULL DEFAULT '0',
`status` tinyint(1) unsigned NOT NULL DEFAULT '0',
`is_deleted` tinyint(1) unsigned NOT NULL DEFAULT '0',
`creation_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci ROW_FORMAT=COMPACT;

Add composite indexes:
items: INDEX(deleted, blocked, status, created) -- created must be last
items_images: INDEX(item_id, weight, status, deleted)
categories: INDEX(category_id, deleted, blocked, status)
Except as noted, the order of columns is not important.
Remove LEFT unless you see a need for it.
Please provide EXPLAIN SELECT ....
The likely reason for the sluggishness is having to check lots of image rows for weight=1. On top of that, the lack of any useful index means a costly table scan.

Related

Unable to change query values in wordpress query.php

My website having woo commerce theme is very slow in loading specially product summary page takes almost 3 minutes in loading.Now i want to change product summary page main query but unable to find way so that i will be able to change/optimize my query.Query output value is given below.I change $query in Query.php but when i execute page then there is no change in query and it is showing same query.Please guide if any other change is necessary.
Previous and new values in query.php are given below.
Previous value
$query = $wpdb->prepare("SELECT post_id
FROM $wpdb->postmeta, $wpdb->posts
WHERE ID = post_id
AND post_type = %s
AND meta_key = '_wp_old_slug'
AND meta_value = %s",
$post_type, $wp_query->query_vars['name']);
New value(want to use this value instead of above)
$query = $wpdb->prepare("SELECT post_id
FROM $wpdb->postmeta, $wpdb->posts
WHERE ID = post_id" );
Query value used for page data
SELECT SQL_CALC_FOUND_ROWS wp_bkkf_posts.ID
FROM wp_bkkf_posts
INNER JOIN wp_bkkf_postmeta
ON ( wp_bkkf_posts.ID = wp_bkkf_postmeta.post_id )
WHERE 1=1
AND ( ( wp_bkkf_postmeta.meta_key = '_visibility'
AND wp_bkkf_postmeta.meta_value IN ('visible','catalog') ) )
AND wp_bkkf_posts.post_type = 'product'
AND (wp_bkkf_posts.post_status = 'publish'
OR wp_bkkf_posts.post_status = 'private')
GROUP BY wp_bkkf_posts.ID
ORDER BY wp_bkkf_posts.menu_order ASC, wp_bkkf_posts.post_title ASC
LIMIT 0, 12
Actually i don't want to use following values in query.
AND ( ( wp_bkkf_postmeta.meta_key = '_visibility'
AND wp_bkkf_postmeta.meta_value IN ('visible','catalog') ) )
AND wp_bkkf_posts.post_type = 'product'
AND (wp_bkkf_posts.post_status = 'publish'
OR wp_bkkf_posts.post_status = 'private')
Show create table output is given below
CREATE TABLE `wp_bkkf_postmeta` (
`meta_id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
`post_id` bigint(20) unsigned NOT NULL DEFAULT '0',
`meta_key` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`meta_value` longtext COLLATE utf8mb4_unicode_ci,
PRIMARY KEY (`meta_id`),
KEY `post_id` (`post_id`),
KEY `meta_key` (`meta_key`(191))
) ENGINE=MyISAM AUTO_INCREMENT=30883570 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
CREATE TABLE `wp_bkkf_posts` (
`ID` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
`post_author` bigint(20) unsigned NOT NULL DEFAULT '0',
`post_date` datetime NOT NULL DEFAULT '0000-00-00 00:00:00',
`post_date_gmt` datetime NOT NULL DEFAULT '0000-00-00 00:00:00',
`post_content` longtext COLLATE utf8mb4_unicode_ci NOT NULL,
`post_title` text COLLATE utf8mb4_unicode_ci NOT NULL,
`post_excerpt` text COLLATE utf8mb4_unicode_ci NOT NULL,
`post_status` varchar(20) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'publish',
`comment_status` varchar(20) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'open',
`ping_status` varchar(20) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'open',
`post_password` varchar(20) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '',
`post_name` varchar(200) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '',
`to_ping` text COLLATE utf8mb4_unicode_ci NOT NULL,
`pinged` text COLLATE utf8mb4_unicode_ci NOT NULL,
`post_modified` datetime NOT NULL DEFAULT '0000-00-00 00:00:00',
`post_modified_gmt` datetime NOT NULL DEFAULT '0000-00-00 00:00:00',
`post_content_filtered` longtext COLLATE utf8mb4_unicode_ci NOT NULL,
`post_parent` bigint(20) unsigned NOT NULL DEFAULT '0',
`guid` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '',
`menu_order` int(11) NOT NULL DEFAULT '0',
`post_type` varchar(20) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'post',
`post_mime_type` varchar(100) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '',
`comment_count` bigint(20) NOT NULL DEFAULT '0',
PRIMARY KEY (`ID`),
KEY `post_name` (`post_name`(191)),
KEY `type_status_date` (`post_type`,`post_status`,`post_date`,`ID`),
KEY `post_parent` (`post_parent`),
KEY `post_author` (`post_author`),
KEY `post_idx` (`post_type`,`post_status`)
) ENGINE=MyISAM AUTO_INCREMENT=1556181 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
If anyone have idea regarding $query value change then please guide,also please share other file names where change required..thanks.
SELECT MAX(CHAR_LENGTH(meta_key)) FROM wp_bkkf_postmeta;
If that comes out less than 191, then do these:
ALTER TABLE wp_bkkf_postmeta
MODIFY `meta_key` varchar(191) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
DROP KEY `post_id`,
ADD INDEX pm (post_id, meta_key);
Consider getting rid of SQL_CALC_FOUND_ROWS; it probably is a slowdown. However losing that will lose the "out of ..." clause on the page.
If you toss the entire WHERE then the JOIN and GROUP BY can go away, too:
SELECT SQL_CALC_FOUND_ROWS wp_bkkf_posts.ID
FROM wp_bkkf_posts
ORDER BY wp_bkkf_posts.menu_order ASC, wp_bkkf_posts.post_title ASC
LIMIT 0, 12
That will run faster. Still, it will need to scan the entire 1.5 million row table. Why you would want that particular ORDER BY?

MySQL query optimization improvement

Hello all I have a MySQL query that fetches data from more than on tables. Here is my query
SELECT
user_id as id,
user_name as name,
user_phone as phone,
user_email as email,
user_address1 as address1,
user_address2 as address2,
user_city as city,
user_state as state,
user_country as country,
user_available as available,
user_location as location,
user_latitude as latitude,
user_longitude as longitude,
user_description as description,
user_company as company,
user_gender as gender,
(SELECT MIN(service_price) FROM service WHERE service.user_id = a.user_id) as price,
(SELECT service_recomanded FROM service WHERE service.user_id = a.user_id ORDER BY service.service_price ASC LIMIT 1) as recomandad,
verified_email,
verified_facebook,
verified_phone,
verified_twitter,
(SELECT providerphoto_name FROM providerphoto WHERE providerphoto.user_id = a.user_id ORDER BY providerphoto_order ASC LIMIT 1 ) as photo,
(SELECT ROUND( AVG(review_rate),2) FROM review WHERE review.user_id = a.user_id ) AS rate,
(SELECT service_ICOC FROM service WHERE service.user_id = a.user_id ORDER BY service_price ASC LIMIT 1) as type
FROM
user a
WHERE a.user_type = 'provider'
AND a.user_active=1
AND a.user_deleted=0
It gets data from user table, service table, review table and providerphoto table. It works too but the execution time is very slow. I guess to make it a single query and avoid the inner five queries may run it fast. Any help?
Table structures.
--
-- Table structure for table `providerphoto`
--
CREATE TABLE IF NOT EXISTS `providerphoto` (
`providerphoto_id` int(11) NOT NULL AUTO_INCREMENT,
`user_id` int(11) NOT NULL,
`providerphoto_file` varchar(256) COLLATE utf8_unicode_ci NOT NULL DEFAULT '',
`providerphoto_name` varchar(256) COLLATE utf8_unicode_ci NOT NULL DEFAULT '',
`providerphoto_date` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
`providerphoto_order` int(11) NOT NULL DEFAULT '0',
PRIMARY KEY (`providerphoto_id`),
KEY `user_id` (`user_id`),
KEY `providerphoto` (`user_id`,`providerphoto_name`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci AUTO_INCREMENT=487 ;
-- --------------------------------------------------------
--
-- Table structure for table `review`
--
CREATE TABLE IF NOT EXISTS `review` (
`review_id` int(11) NOT NULL AUTO_INCREMENT,
`review_title` varchar(256) COLLATE utf8_unicode_ci NOT NULL,
`user_id` int(11) NOT NULL,
`review_date` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
`review_content` text COLLATE utf8_unicode_ci NOT NULL,
`review_user_id` int(11) NOT NULL,
`review_rate` int(10) NOT NULL DEFAULT '1',
`review_tip` int(11) NOT NULL DEFAULT '0',
`service_booked` int(11) NOT NULL DEFAULT '0',
PRIMARY KEY (`review_id`),
KEY `user_id` (`user_id`),
KEY `review_date` (`review_date`),
KEY `review_user_id` (`review_user_id`),
KEY `review_rate` (`review_rate`),
KEY `review_tip` (`review_tip`),
KEY `service_booked` (`service_booked`),
KEY `review` (`user_id`,`review_rate`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci AUTO_INCREMENT=97 ;
-- --------------------------------------------------------
--
-- Table structure for table `service`
--
CREATE TABLE IF NOT EXISTS `service` (
`service_id` int(11) NOT NULL AUTO_INCREMENT,
`service_name` varchar(45) COLLATE utf8_unicode_ci DEFAULT NULL,
`user_id` int(11) DEFAULT NULL,
`service_created_by` int(11) NOT NULL DEFAULT '0',
`service_ICOC` varchar(256) COLLATE utf8_unicode_ci NOT NULL DEFAULT '',
`service_price` int(11) NOT NULL,
`service_date_added` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
`service_date_expire` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00',
`service_time` varchar(256) COLLATE utf8_unicode_ci NOT NULL DEFAULT '0',
`service_rate` varchar(256) COLLATE utf8_unicode_ci NOT NULL DEFAULT '0',
`service_type` int(10) NOT NULL DEFAULT '1' COMMENT '1-in call, 2-out call, 3-in&out call',
`service_recomanded` int(2) NOT NULL DEFAULT '0',
`service_genre` varchar(100) COLLATE utf8_unicode_ci NOT NULL,
`service_message` text COLLATE utf8_unicode_ci NOT NULL,
PRIMARY KEY (`service_id`),
KEY `user_id` (`user_id`),
KEY `service_ICOC` (`service_ICOC`(255)),
KEY `service` (`user_id`,`service_price`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci AUTO_INCREMENT=854 ;
-- --------------------------------------------------------
--
-- Table structure for table `user`
--
CREATE TABLE IF NOT EXISTS `user` (
`user_id` int(11) NOT NULL AUTO_INCREMENT,
`user_name` varchar(256) COLLATE utf8_unicode_ci DEFAULT NULL,
`user_password` varchar(256) COLLATE utf8_unicode_ci NOT NULL,
`user_email` varchar(256) COLLATE utf8_unicode_ci DEFAULT NULL,
`user_phone` varchar(256) COLLATE utf8_unicode_ci DEFAULT NULL,
`user_address1` text COLLATE utf8_unicode_ci,
`user_address2` text COLLATE utf8_unicode_ci NOT NULL,
`user_city` varchar(256) COLLATE utf8_unicode_ci NOT NULL,
`user_state` varchar(256) COLLATE utf8_unicode_ci NOT NULL,
`user_country` varchar(256) COLLATE utf8_unicode_ci NOT NULL,
`user_company` varchar(256) COLLATE utf8_unicode_ci NOT NULL,
`user_birthday` date DEFAULT NULL,
`user_register_date` timestamp NULL DEFAULT CURRENT_TIMESTAMP,
`user_type` enum('provider','client') COLLATE utf8_unicode_ci DEFAULT NULL,
`user_description` text COLLATE utf8_unicode_ci NOT NULL,
`user_available` int(10) NOT NULL DEFAULT '1',
`verified_email` tinyint(1) NOT NULL DEFAULT '0',
`verified_facebook` tinyint(1) NOT NULL DEFAULT '0',
`verified_phone` tinyint(1) NOT NULL DEFAULT '0',
`verified_twitter` tinyint(1) NOT NULL DEFAULT '0',
`user_facebook_friends` int(11) NOT NULL DEFAULT '0',
`user_twitter_friends` int(11) NOT NULL DEFAULT '0',
`user_longitude` decimal(10,5) NOT NULL DEFAULT '0.00000',
`user_latitude` decimal(10,5) NOT NULL DEFAULT '0.00000',
`user_deleted` tinyint(4) NOT NULL DEFAULT '0',
`user_gender` int(11) NOT NULL DEFAULT '0',
`user_facebook_token` varchar(200) COLLATE utf8_unicode_ci NOT NULL,
`user_active` tinyint(4) NOT NULL DEFAULT '1',
`user_location` varchar(200) COLLATE utf8_unicode_ci NOT NULL,
`user_push_notification_token` varchar(100) COLLATE utf8_unicode_ci NOT NULL,
`user_timezone_diff` int(11) NOT NULL DEFAULT '0',
`balanced_uri` text COLLATE utf8_unicode_ci NOT NULL,
`user_reset_passwd_token` varchar(200) COLLATE utf8_unicode_ci NOT NULL,
`is_test_user` int(11) NOT NULL DEFAULT '0',
PRIMARY KEY (`user_id`),
KEY `deleted_idx` (`user_deleted`),
KEY `email_idx` (`user_email`(255))
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci AUTO_INCREMENT=426 ;
You can probably just add indexes to speed up the query. Try adding the following indexes:
service(user_id, service_price)
providerphoto(user_id, providerphoto_order)
review(user_id, review_rate)
Something like this. This will return all the values for the users as I did not use the ORDER and LIMIT in the query. This is just for the approach.
SELECT
a.user_id as id,
user_name as name,
user_phone as phone,
user_email as email,
user_address1 as address1,
user_address2 as address2,
user_city as city,
user_state as state,
user_country as country,
user_available as available,
user_location as location,
user_latitude as latitude,
user_longitude as longitude,
user_description as description,
user_company as company,
user_gender as gender,
MIN(s.service_price) as price,
s.service_recomanded as recomandad,
verified_email,
verified_facebook,
verified_phone,
verified_twitter,
pp.providerphoto_name as photo,
ROUND( AVG(r.review_rate),2) as rate,
s.service_ICOC as type
FROM
user a LEFT JOIN service s on s.user_id = a.user_id LEFT JOIN providerphoto pp on pp.user_id = a.user_id LEFT JOIN review r on r.user_id = a.user_id
WHERE a.user_type = 'provider'
AND a.user_active=1
AND a.user_deleted=0;
First thing I would do is index user.user_type, user.user_deleted and user.user_active to let the optimizer quickly start with a smaller set of user records to have to deal with.
Do you mean to allow service.user_id to be NULL? That seems like a mistake.
Also you may want to throw indexes on any columns where you're doing an ORDER BY.
The best way to find out what is slow in this query is to do a EXPLAIN QUERY and analyze what it tells you. We can help you with that as well.

How to optimize a MySQL query with EXPLAIN showing 'using temporary; using filesort'

I have a rather slow query that I'd like to optimize. EXPLAIN shows 'using temporary; using filesort'. I tried a few solutions and, doing without an ORDER BY, even managed to get rid of the 'using filesort'. But is there a way to avoid 'using temporary; using filesort' entirely, without sacrificing the ORDER BY?
This is my query:
SELECT `tags`.`name`,
`tags`.`tag_id`,
COUNT(*) AS `qty_products`
FROM `products_subsubcategories`
JOIN `products_tags` ON `products_subsubcategories`.`product_id` = `products_tags`.`product_id`
JOIN `products` ON `products_subsubcategories`.`product_id` = `products`.`product_id`
JOIN `tags` ON `products_tags`.`tag_id` = `tags`.`tag_id`
WHERE `products_subsubcategories`.`subsubcategory_id` = 55
AND `tags`.`type` = 'brand'
AND `products`.`dont_display` = 0
GROUP BY `tags`.`tag_id`
ORDER BY `tags`.`order`,
`tags`.`name`;
The subsubcategory 55 is dynamic user input.
This is the EXPLAIN result:
id select_type table type possible_keys key key_len ref rows filtered Extra
1 SIMPLE products_subsubcategories ref PRIMARY,subsubcategory_id subsubcategory_id 4 const 3982 100.00 Using temporary; Using filesort
1 SIMPLE tags ALL PRIMARY,type NULL NULL NULL 679 78.94 Using where; Using join buffer
1 SIMPLE products eq_ref PRIMARY,dont_display PRIMARY 4 mbb.products_subsubcategories.product_id 1 100.00 Using where
1 SIMPLE products_tags eq_ref PRIMARY,tag_id PRIMARY 8 mbb.products.product_id,mbb.tags.tag_id 1 100.00 Using where; Using index
(When I replace ORDER BY ... by ORDER BY NULL, the 'using filesort' disapperars. I could sort the result with PHP afterwards, although it's more convenient with MySQL, of course ...)
My tables look like this:
CREATE TABLE IF NOT EXISTS `products_subsubcategories` (
`position` smallint(5) unsigned NOT NULL DEFAULT '0',
`product_id` int(10) unsigned NOT NULL,
`subsubcategory_id` int(10) unsigned NOT NULL,
PRIMARY KEY (`product_id`,`subsubcategory_id`),
KEY `subsubcategory_id` (`subsubcategory_id`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
CREATE TABLE IF NOT EXISTS `products_tags` (
`product_id` int(10) unsigned NOT NULL,
`tag_id` int(10) unsigned NOT NULL,
PRIMARY KEY (`product_id`,`tag_id`),
KEY `tag_id` (`tag_id`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
CREATE TABLE IF NOT EXISTS `products` (
`article_number` varchar(255) COLLATE utf8_unicode_ci NOT NULL,
`date` date DEFAULT NULL,
`delivery_time` varchar(255) COLLATE utf8_unicode_ci NOT NULL,
`description` text COLLATE utf8_unicode_ci NOT NULL,
`dont_display` tinyint(1) unsigned NOT NULL DEFAULT '0',
`ean` varchar(255) COLLATE utf8_unicode_ci NOT NULL,
`image_error` tinyint(1) NOT NULL DEFAULT '0',
`image_is_downloaded` tinyint(1) NOT NULL DEFAULT '0',
`image_url` varchar(400) COLLATE utf8_unicode_ci NOT NULL,
`image_url_170_134` varchar(255) COLLATE utf8_unicode_ci NOT NULL,
`image_url_original_size` varchar(255) COLLATE utf8_unicode_ci NOT NULL,
`is_deleted` tinyint(1) unsigned NOT NULL DEFAULT '0',
`is_duplicate` tinyint(1) unsigned NOT NULL DEFAULT '0',
`is_not_associated_to_category` tinyint(1) unsigned NOT NULL DEFAULT '0',
`is_not_associated_to_subcategory` tinyint(1) unsigned NOT NULL DEFAULT '0',
`is_not_associated_to_subsubcategory` tinyint(1) unsigned NOT NULL DEFAULT '0',
`last_association` datetime DEFAULT NULL,
`last_completion_by_ean` datetime DEFAULT NULL,
`matching_age` varchar(255) COLLATE utf8_unicode_ci NOT NULL,
`matching_brand` varchar(255) COLLATE utf8_unicode_ci NOT NULL,
`matching_category` varchar(255) COLLATE utf8_unicode_ci NOT NULL,
`matching_color` varchar(255) COLLATE utf8_unicode_ci NOT NULL,
`matching_gender` varchar(255) COLLATE utf8_unicode_ci NOT NULL,
`matching_keywords` varchar(255) COLLATE utf8_unicode_ci NOT NULL,
`matching_main_category` varchar(255) COLLATE utf8_unicode_ci NOT NULL,
`matching_size` varchar(255) COLLATE utf8_unicode_ci NOT NULL,
`matching_subcategory` varchar(255) COLLATE utf8_unicode_ci NOT NULL,
`matching_subsubcategory` varchar(255) COLLATE utf8_unicode_ci NOT NULL,
`old_price` decimal(7,2) unsigned NOT NULL DEFAULT '0.00',
`price` decimal(7,2) unsigned NOT NULL DEFAULT '0.00',
`product_id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`product_list_id` int(10) unsigned NOT NULL DEFAULT '0',
`qty_overall_clicks` int(10) unsigned NOT NULL DEFAULT '0',
`shipping` decimal(7,2) unsigned NOT NULL DEFAULT '0.00',
`shop_url` varchar(400) COLLATE utf8_unicode_ci NOT NULL,
`title` varchar(255) COLLATE utf8_unicode_ci NOT NULL,
`vendor_id` int(10) unsigned NOT NULL DEFAULT '0',
PRIMARY KEY (`product_id`),
KEY `article_number` (`article_number`),
KEY `dont_display` (`dont_display`),
KEY `ean` (`ean`),
KEY `is_deleted` (`is_deleted`),
KEY `is_duplicate` (`is_duplicate`),
KEY `is_not_associated_to_category` (`is_not_associated_to_category`),
KEY `is_not_associated_to_subcategory` (`is_not_associated_to_subcategory`),
KEY `is_not_associated_to_subsubcategory` (`is_not_associated_to_subsubcategory`),
KEY `product_list_id` (`product_list_id`),
KEY `vendor_id` (`vendor_id`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci AUTO_INCREMENT=1084370;
CREATE TABLE IF NOT EXISTS `tags` (
`display_name` varchar(255) COLLATE utf8_unicode_ci NOT NULL,
`image_url` varchar(255) COLLATE utf8_unicode_ci NOT NULL,
`name` varchar(255) COLLATE utf8_unicode_ci NOT NULL,
`order` int(10) unsigned NOT NULL DEFAULT '0',
`tag_id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`type` varchar(255) COLLATE utf8_unicode_ci NOT NULL,
PRIMARY KEY (`tag_id`),
KEY `type` (`type`),
KEY `name` (`name`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci AUTO_INCREMENT=1084;
I would suggest attempting a query that doesn't use JOINs, simply for the fact that you don't use the JOINs for anything other than getting a count.
Try the following:
SELECT
t.name,
t.tag_id,
(
SELECT COUNT(*) FROM product_tags pt
INNER JOIN product_subcategories ps
ON ps.product_id = pt.product_id
INNER JOIN product p
ON p.product_id = pt.product_id
WHERE pt.tag_id = t.tag_id
AND p.dont_display = 0
AND ps.subsubcategory_id = 55
) AS qty_products
FROM tags t
WHERE
t.type = 'brand'
AND EXISTS (
SELECT * FROM product_tags pt
INNER JOIN product_subcategories ps
ON ps.product_id = pt.product_id
INNER JOIN product p
ON p.product_id = pt.product_id
WHERE pt.tag_id = t.tag_id
AND p.dont_display = 0
AND ps.subsubcategory_id = 55
)
ORDER BY t.order,t.name
In this way, you only query the tags table, getting the results back in order initially. Then for each record you check if any are in subsubcategory 55, and otherwise skip that tag.
This should improve your query greatly, unless there are an enormous number of tags (and even then it still might improve things.)
Another improvement you can make is the one Kickstart suggested in the comments: add a covering index to the tags table:
ALTER TABLE tags
ADD INDEX `type_order_name` (`type`,`order`,`name`)
If you aren't familiar with multipart keys, just know that internally they are effectively stored as a concatenation of each of the columns, in the order the columns are listed in the key definition.
Therefore as long as you provide the type in the WHERE clause, the tags will be stored ordered by order and name, just as this query wants them. This will result in very fast sorting (because they will already be sorted in the index).

Joins in MySQL are not working

I am a beginner in MySQL and trying to create a join query in MySQL.
My first SQL query is as below which displays the 2 columns votes and post
SELECT votes, post
FROM `wp_votes` where votes!=''
GROUP BY votes,post asc LIMIT 0 , 30
**Second table is where the posts are **
SELECT *
FROM `wp_posts`
LIMIT 0 , 30
What i wanna do is create a join so that it display all records from wp_posts table WHERE wp_post.ID=wp_votes.post, Also have to check if wp_votes.votes!=''
I tried the following but i am stuck on it
SELECT * FROM wp_posts join wp_votes ON wp_posts.ID =wp_votes.post
Table Structure below
CREATE TABLE IF NOT EXISTS `wp_posts` (
`ID` bigint(20) unsigned NOT NULL auto_increment,
`post_author` bigint(20) unsigned NOT NULL default '0',
`post_date` datetime NOT NULL default '0000-00-00 00:00:00',
`post_date_gmt` datetime NOT NULL default '0000-00-00 00:00:00',
`post_content` longtext NOT NULL,
`post_title` text NOT NULL,
`post_excerpt` text NOT NULL,
`post_status` varchar(20) NOT NULL default 'publish',
`comment_status` varchar(20) NOT NULL default 'open',
`ping_status` varchar(20) NOT NULL default 'open',
`post_password` varchar(20) NOT NULL default '',
`post_name` varchar(200) NOT NULL default '',
`to_ping` text NOT NULL,
`pinged` text NOT NULL,
`post_modified` datetime NOT NULL default '0000-00-00 00:00:00',
`post_modified_gmt` datetime NOT NULL default '0000-00-00 00:00:00',
`post_content_filtered` longtext NOT NULL,
`post_parent` bigint(20) unsigned NOT NULL default '0',
`guid` varchar(255) NOT NULL default '',
`menu_order` int(11) NOT NULL default '0',
`post_type` varchar(20) NOT NULL default 'post',
`post_mime_type` varchar(100) NOT NULL default '',
`comment_count` bigint(20) NOT NULL default '0',
PRIMARY KEY (`ID`),
KEY `post_name` (`post_name`),
KEY `type_status_date` (`post_type`,`post_status`,`post_date`,`ID`),
KEY `post_parent` (`post_parent`),
KEY `post_author` (`post_author`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8 AUTO_INCREMENT=4570 ;
--
-- Table structure for table `wp_votes`
--
CREATE TABLE IF NOT EXISTS `wp_votes` (
`ID` int(11) NOT NULL auto_increment,
`post` int(11) NOT NULL,
`votes` text NOT NULL,
`guests` text NOT NULL,
`usersinks` text NOT NULL,
`guestsinks` text NOT NULL,
PRIMARY KEY (`ID`)
) ENGINE=MyISAM DEFAULT CHARSET=latin1 AUTO_INCREMENT=1052 ;
SELECT a.votes, a.post,
b.*
FROM wp_votes a
INNER JOIN wp_Post b
ON a.post = b.ID
WHERE a.votes <> ''
To further gain more knowledge about joins, kindly visit the link below:
Visual Representation of SQL Joins

Join two tables only if a value is null or a specific number

I have three tables in a database:
Product table - +100000 entries
Attribute table (list of possible attributes of a product)
Product attribtue table (which contains the value of the attribute of a product)
I am looking for 8 random products and one of their attributes (attribute_id = 2), but if a product hasn't this attribute it should appear at the return of the query. I have been trying some sql queries without any succesful result because my return only shows the products that have the attribute and hide the others.
My three tables are like this:
CREATE TABLE `product` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`sku` varchar(20) COLLATE utf8_unicode_ci NOT NULL DEFAULT '',
`name` varchar(90) COLLATE utf8_unicode_ci NOT NULL DEFAULT '',
`provider_id` int(11) unsigned DEFAULT NULL,
`url` varchar(255) COLLATE utf8_unicode_ci DEFAULT NULL,
`active` int(1) unsigned NOT NULL DEFAULT '0',
PRIMARY KEY (`id`),
UNIQUE KEY `sku_UNIQUE` (`sku`)
) ENGINE=InnoDB AUTO_INCREMENT=123965 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
CREATE TABLE `attribute` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`name` varchar(50) NOT NULL DEFAULT '',
`data_type` varchar(50) DEFAULT '',
PRIMARY KEY (`id`),
) ENGINE=InnoDB AUTO_INCREMENT=30 DEFAULT CHARSET=latin1;
CREATE TABLE `product_attribute` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`product_id` int(11) unsigned NOT NULL,
`attribute_id` int(11) unsigned NOT NULL DEFAULT '6',
`value` longtext NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `product_id` (`product_id`,`attribute_id`)
) ENGINE=InnoDB AUTO_INCREMENT=869437 DEFAULT CHARSET=latin1;
And this is one of the queries I tried, I thought it was correct but it have the same problem as the others...
SELECT product.id, product.sku, product.name,provider.name as provider_name,
product_attribute.value as author
FROM (`product`)
LEFT JOIN `provider` ON `product`.`provider_id` = `provider`.`id`
LEFT JOIN `product_attribute` ON `product`.`id` = `product_attribute`.`product_id`
WHERE `product`.`active` = '1' AND `product`.`url` IS NOT NULL
AND (`product_attribute`.`attribute_id` = 8 OR `product_attribute`.`attribute_id` IS NULL)
AND `product`.`provider_id` = '7' ORDER BY RAND() LIMIT 8
I was trying with left, inner and right join and nothing works.
You should put the condition for the left-joined table in the join, not the where clause
...
from product
left join provider ON product.provider_id = provider.id
left join product_attribute on product.id = product_attribute.product_id
and product_attribute.attribute_id = 8
where `product`.`active` = '1'
and `product`.`url` IS NOT NULL
and `product`.`provider_id` = '7'
...