MySQL - GROUP BY slow down the page - php

GROUP BY clause in the query below slow down the page, please help to resolve this issue
SELECT
`a`.*,
CONCAT(a.`firstname`, " ", a.`lastname`) AS `cont_name`,
CONCAT(a.`position`, " / ", a.`company`) AS `comp_pos`,
CONCAT(f.`firstname`, " ", f.`lastname`) AS `created_by`
FROM
`contacts` AS `a`
LEFT JOIN `users` AS `f` ON f.id = a.user_id
LEFT JOIN `user_centres` AS `b` ON a.user_id = b.user_id
WHERE b.centre_id IN (23, 24, 25, 26, 20, 21, 22, 27, 28)
GROUP BY `a`.`id`
ORDER BY `a`.`created` desc
Here the join with user_centres table is for centre wise filtering of data. EXPLAIN gives the result as:
- 1 SIMPLE a index PRIMARY,user_id,area_id,industry_id,country PRIMARY 4 NULL 20145 Using temporary; Using filesort
Our requirement is as below
Listing of all contacts in admin login
Centre wise listing of contacts in manager/clerk login
Total records in contact table is > 20K.
There will be multiple entry for users in user_centres table, ie: a user is assigned to more than one centre.
While executing the query in server by excluding GROUP BY is nearly 300k data which makes the problem.
Db stucture
Table structure for table contacts
CREATE TABLE IF NOT EXISTS `contacts` (
`id` int(11) NOT NULL,
`user_id` int(11) DEFAULT NULL,
`imported` tinyint(4) NOT NULL DEFAULT '0',
`situation` char(10) DEFAULT NULL,
`firstname` varchar(150) DEFAULT NULL,
`lastname` varchar(150) DEFAULT NULL,
`position` varchar(150) DEFAULT NULL,
`dob` datetime DEFAULT NULL,
`office_contact` varchar(100) DEFAULT NULL,
`mobile_contact` varchar(100) DEFAULT NULL,
`email` varchar(255) NOT NULL,
`company` varchar(150) DEFAULT NULL,
`industry_id` int(11) DEFAULT NULL,
`address` varchar(255) DEFAULT NULL,
`city` varchar(150) DEFAULT NULL,
`country` int(11) DEFAULT NULL,
`isclient` tinyint(4) NOT NULL DEFAULT '0',
`classification` varchar(100) DEFAULT NULL,
`created` datetime NOT NULL,
`updated` datetime NOT NULL,
`unsubscribe` enum('Y','N') NOT NULL DEFAULT 'N'
) ENGINE=InnoDB AUTO_INCREMENT=25203 DEFAULT CHARSET=latin1;
Indexes for table contacts
ALTER TABLE `contacts`
ADD PRIMARY KEY (`id`), ADD KEY `user_id` (`user_id`),
ADD KEY `industry_id` (`industry_id`), ADD KEY `country` (`country`);
Constraints for table contacts
ALTER TABLE `contacts`
ADD CONSTRAINT `contacts_ibfk_4` FOREIGN KEY (`user_id`)
REFERENCES `users` (`id`) ON DELETE SET NULL ON UPDATE NO ACTION,
ADD CONSTRAINT `contacts_ibfk_6` FOREIGN KEY (`industry_id`)
REFERENCES `industries` (`id`) ON DELETE SET NULL ON UPDATE NO ACTION,
ADD CONSTRAINT `contacts_ibfk_7` FOREIGN KEY (`country`)
REFERENCES `country` (`id`) ON DELETE SET NULL ON UPDATE NO ACTION;
Table structure for table users
CREATE TABLE IF NOT EXISTS `users` (
`id` int(11) NOT NULL,
`role_id` int(11) NOT NULL,
`email` varchar(250) NOT NULL,
`password` varchar(45) NOT NULL,
`salt` varchar(45) DEFAULT NULL,
`status_id` int(11) DEFAULT NULL,
`status` tinyint(1) DEFAULT '1',
`firstname` varchar(255) NOT NULL,
`lastname` varchar(255) NOT NULL,
`created` datetime DEFAULT NULL,
`updated` datetime DEFAULT NULL
) ENGINE=InnoDB AUTO_INCREMENT=30 DEFAULT CHARSET=latin1;
Indexes for table users
ALTER TABLE `users`
ADD PRIMARY KEY (`id`), ADD UNIQUE KEY `email_UNIQUE` (`email`),
ADD KEY `type_id_idx` (`role_id`), ADD KEY `status_id_idx` (`status_id`);
Constraints for table users
ALTER TABLE `users`
ADD CONSTRAINT `role_id` FOREIGN KEY (`role_id`)
REFERENCES `users_roles` (`id`) ON DELETE NO ACTION ON UPDATE NO ACTION,
ADD CONSTRAINT `status_id` FOREIGN KEY (`status_id`)
REFERENCES `users_status` (`id`) ON DELETE NO ACTION ON UPDATE NO ACTION,
ADD CONSTRAINT `users_ibfk_1` FOREIGN KEY (`area`)
REFERENCES `area` (`id`) ON DELETE SET NULL ON UPDATE NO ACTION;
Table structure for table user_centres
CREATE TABLE IF NOT EXISTS `user_centres` (
`id` int(11) NOT NULL,
`user_id` int(11) NOT NULL,
`area_id` int(11) NOT NULL,
`centre_id` int(11) NOT NULL
) ENGINE=InnoDB AUTO_INCREMENT=72 DEFAULT CHARSET=utf8;
Indexes for table user_centres
ALTER TABLE `user_centres`
ADD PRIMARY KEY (`id`), ADD KEY `user_id` (`user_id`),
ADD KEY `centre_id` (`centre_id`), ADD KEY `area_id` (`area_id`);
Constraints for table user_centres
ALTER TABLE `user_centres`
ADD CONSTRAINT `user_centres_ibfk_1` FOREIGN KEY (`user_id`)
REFERENCES `users` (`id`) ON DELETE CASCADE ON UPDATE NO ACTION,
ADD CONSTRAINT `user_centres_ibfk_2` FOREIGN KEY (`centre_id`)
REFERENCES `centre` (`id`) ON DELETE CASCADE ON UPDATE NO ACTION;
Also please refer EXPLAIN screens - http://prntscr.com/6o5h8s

Index were not used because of the different ORDER BY a GROUP BY clauses.
http://dev.mysql.com/doc/refman/5.0/en/order-by-optimization.html

You are spending a lot of time checking user_centres, but not needing anything from it. Remove it from the query.
users can be made into a correlates subquery:
SELECT `a`.*,
CONCAT(a.`firstname`, " ", a.`lastname`) AS `cont_name`,
CONCAT(a.`position`, " / ", a.`company`) AS `comp_pos`,
( SELECT CONCAT(f.`firstname`, " ", f.`lastname`)
FROM `users` AS `f` ON f.id = a.user_id
) AS `created_by`
FROM `contacts` AS `a`
GROUP BY `a`.`id`
ORDER BY `a`.`created` desc
Do you really need all 20K rows?? The sheer bulk of the result is part of the sluggishness.

Thanks all, based on the feedback got from all of you I have tried the query below now and give me the speed improvement from 30 secs to 15 secs
SELECT `a`.`id`, `a`.`user_id`, `a`.`imported`, `a`.`created`,
`a`.`unsubscribe`, CONCAT(a.firstname, " ", a.lastname) AS `cont_name`,
CONCAT(a.position, " / ", a.company) AS `comp_pos`,
( SELECT COUNT(uc.id)
FROM `user_centres` AS `uc`
WHERE (uc.user_id = a.user_id)
AND (uc.centre_id IN (29))
GROUP BY `uc`.`user_id`
) AS `centre_cnt`,
( SELECT GROUP_CONCAT(DISTINCT g.group_name
ORDER BY g.group_name ASC SEPARATOR ", ")
FROM `groups` AS `g`
INNER JOIN `group_contacts` AS `gc` ON g.id = gc.group_id
WHERE (gc.contact_id = a.id)
GROUP BY `gc`.`contact_id`
) AS `group_name`,
( SELECT CONCAT(u.`firstname`, " ", u.`lastname`)
FROM `users` AS `u`
WHERE (u.id = a.user_id)
) AS `created_by`, `e`.`name` AS `industry_name`
FROM `contacts` AS `a`
LEFT JOIN `industries` AS `e` ON e.id = a.industry_id
WHERE (1)
HAVING (centre_cnt is NOT NULL)
ORDER BY `a`.`id` desc
Let me know Is there a way to improve the speed and make the page loading below 5 secs.
Please see the interface (noted the filtering and sorting fields) - http://prntscr.com/6q6q70

Related

Implicit MySQL Join on Update Statement - 0 rows affected

I'm trying to get this MySQL code to work, but it's saying 0 rows affected.
UPDATE assessments, assessment_types
SET assessments.assessment_type_id = assessment_types.id
WHERE (assessment_types.description = "Skills Assessment" AND assessments.id = 2);
Basically I have assessment_types with id and description column, and I just have the id in the assessments.assessment_type_id
I need to update the id.
I searched and couldn't find quite what I need for this.
Thanks!
Table Data:
assessment_types
id description
1 Knowledge Assessment
2 Skill Assessment
3 Personal Information
4 Natural Skills
Table Structure:
--
-- Table structure for table `assessments`
--
CREATE TABLE IF NOT EXISTS `assessments` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(255) COLLATE utf8_bin NOT NULL,
`acronym` varchar(255) COLLATE utf8_bin NOT NULL,
`assessment_type_id` int(11) NOT NULL,
`language_id` int(11) NOT NULL,
`date_created` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
`date_updated` date NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `id` (`id`),
KEY `assessment_type_id` (`assessment_type_id`),
KEY `language_id` (`language_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin AUTO_INCREMENT=2385 ;
--
-- Table structure for table `assessment_types`
--
CREATE TABLE IF NOT EXISTS `assessment_types` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`description` varchar(255) CHARACTER SET latin1 NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `id` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin AUTO_INCREMENT=7 ;
You can try doing an explicit join of the two tables in your UPDATE statement:
UPDATE assessments a
INNER JOIN assessment_types at
ON a.assessment_type_id = at.id
SET a.assessment_type_id = at.id
WHERE (at.description = "Skills Assessment" AND a.id = 2);

JOIN Query In MYSQL and PHP

I am going to generate specific information from all the tables in MYSQL. I have 5 tables in my database. I.e.:
advertiser
CREATE TABLE `advertiser` (
`Adv_id` int(11) NOT NULL AUTO_INCREMENT,
`Name` char(20) NOT NULL,
`F_Name` char(20) NOT NULL,
`Address` varchar(40) NOT NULL,
`CNIC` int(13) NOT NULL,
`Contact` int(11) NOT NULL,
`Monthly_fee` varchar(10) NOT NULL,
`Region` varchar(10) NOT NULL,
`Reg_date` varchar(10) NOT NULL,
PRIMARY KEY (`Adv_id`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=latin1;
company_information
CREATE TABLE `company_information` (
`Company_id` int(11) NOT NULL AUTO_INCREMENT,
`Company_Name` varchar(20) NOT NULL,
`Company_Contact` int(11) NOT NULL,
`Company_Address` varchar(30) NOT NULL,
PRIMARY KEY (`Company_id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
advertisement
CREATE TABLE `advertisement` (
`Ads_id` int(11) NOT NULL AUTO_INCREMENT,
`Adv_id_id` int(11) NOT NULL,
`Company_id` int(11) NOT NULL,
`Ads_Title` varchar(20) NOT NULL,
`Ads_Description` varchar(40) NOT NULL,
`Ads_Image` varchar(50) NOT NULL,
PRIMARY KEY (`Ads_id`),
KEY `Adv_id` (`Adv_id_id`),
KEY `Company_id` (`Company_id`),
CONSTRAINT `Adv_id` FOREIGN KEY (`Adv_id_id`) REFERENCES `advertiser` (`Adv_id`) ON DELETE CASCADE ON UPDATE CASCADE,
CONSTRAINT `advertisement_ibfk_1` FOREIGN KEY (`Company_id`) REFERENCES `company_information` (`Company_id`) ON DELETE CASCADE ON UPDATE CASCADE
) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=latin1;
ads_type
CREATE TABLE `ads_type` (
`Ads_type_id` int(11) NOT NULL AUTO_INCREMENT,
`Advertisement_id` int(11) NOT NULL,
`Full_Channel_Ads` varchar(30) NOT NULL,
`Logo_Ads` varchar(30) NOT NULL,
`Com_Break_Ads` varchar(30) NOT NULL,
PRIMARY KEY (`Ads_type_id`),
KEY `Advertisement_id` (`Advertisement_id`),
CONSTRAINT `Advertisement_id` FOREIGN KEY (`Advertisement_id`) REFERENCES `advertisement` (`Ads_id`) ON DELETE CASCADE ON UPDATE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
ads_date
CREATE TABLE `ads_date` (
`Ads_date_id` int(11) NOT NULL AUTO_INCREMENT,
`Ads_id` int(11) NOT NULL,
`Starting_Date` varchar(30) NOT NULL,
`Expiry_Date` varchar(30) NOT NULL,
PRIMARY KEY (`Ads_date_id`),
KEY `Ads_id` (`Ads_id`),
CONSTRAINT `Ads_id` FOREIGN KEY (`Ads_id`) REFERENCES `advertisement` (`Ads_id`) ON DELETE CASCADE ON UPDATE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
I want to retrive
Name, CNIN, and Contact from table Advertiser
Company_Name from company_information
Ads_Title from Advertisement
Starting_Date and Expiry_Date From ads_date.
This Query work well for the above rows :
SELECT *
FROM ads_date a
JOIN advertisement ci ON ci.Ads_id = a.Ads_id
JOIN company_information ar ON ar.Company_id = ci.Company_id
JOIN advertiser ad ON ad.Adv_id = ci.Advertiser_id
WHERE Expiry_Date >= CURDATE()
Problem:
I also want to retrive Advertisement_type from ads_type.
how can I do this with a JOIN query? Can any one explain?
You'll need another join:
SELECT *
FROM ads_date a
JOIN advertisement ci ON ci.Ads_id = a.Ads_id
JOIN company_information ar ON ar.Company_id = ci.Company_id
JOIN advertiser ad ON ad.Adv_id = ci.Advertiser_id
JOIN ads_type at ON at.Advertisement_id = ci.Ads_id -- Here
WHERE Expiry_Date >= CURDATE()
Please try the below query:
SELECT *
FROM ads_date a
JOIN advertisement ci ON ci.Ads_id = a.Ads_id
JOIN company_information ar ON ar.Company_id = ci.Company_id
JOIN advertiser ad ON ad.Adv_id = ci.Advertiser_id
JOIN ads_type at ON at.Advertisement_id =ci.Ads_id
WHERE Expiry_Date >= CURDATE()
Add another join as
JOIN ads_type at ON at.Advertisement_id = ci.Ads_id
Then use group_by at.Advertisement_id to avoid repetition.

Select all possible children with parent in mysql

I have a users table.
I have a certifications table.
Each user can have multiple certifications. Certifications has a user_id foreign key.
How may I select a user as well as all of their certifications in one query?
How may I select users that have certifications?
users table:
CREATE TABLE `users` (
`id` int(11) unsigned NOT NULL auto_increment,
`username` varchar(24) default NULL,
`displayname` varchar(24) default NULL,
`email` varchar(64) default NULL,
`password` text,
`signup_date` int(11) default NULL,
`signup_ip` varchar(15) default NULL,
`hash` text,
`verified` tinyint(1) default '0',
`last_login` int(11) default NULL,
`logins` int(11) default NULL,
`status` text,
`recovery_hash` text,
`recovery_initiated` int(11) default NULL,
`last_updated` int(11) default NULL,
`signup_method` text,
`signup_question` int(11) default NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8;
certifications table:
CREATE TABLE `certifications` (
`id` int(11) unsigned NOT NULL auto_increment,
`user_id` int(11) unsigned NOT NULL,
`number` varchar(128) default NULL,
`board` varchar(128) default NULL,
`company` varchar(128) default NULL,
`website` varchar(128) default NULL,
PRIMARY KEY (`id`),
KEY `user_id` (`user_id`),
CONSTRAINT `certifications_ibfk_1` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON DELETE CASCADE ON UPDATE CASCADE
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8;
Thank you for reading.
How may I select a user as well as all of their certifications in one query?
SELECT *
FROM certifivations C
LEFT JOIN users U
ON C.user_id = U.id
WHERE U.id=USERID
How may I select users that have certifications?
SELECT *
FROM certifivations C
JOIN users U
ON C.user_id = U.id
GROUP BY U.id
Get all the certifications of user with id of 1 by using a join.
SELECT b.id as certification_id
FROM users AS a
LEFT JOIN certifications AS b
ON a.id = b.user_id
WHERE a.id = 1;
Get all the users that have certifications using an inner join. All users without certs will drop out.
SELECT a.id as users_with_certifications
FROM users as a
JOIN certifications AS b
ON a.id = b.user_id;

Optimizing sql statements to reduce MySQL server load

$sql1 = "SELECT questions FROM last_check_date WHERE user_id=? ORDER BY questions DESC LIMIT 1";
$sql2 = "SELECT id FROM questions WHERE add_dt>?";
What do statements above do?
When I execute sql1, it gets last check date for user.
Then I'm executing second query, to fetch all id's where add date>last check date (from sql1) and return affected rows count.
What I want to do is to merge this 2 statements into 1, and optimize query count. Following problem may occur:
There is no row for user in $sql1: must select all rows in sql2 and return affected rows count.
I can't figure out how it must look like.. Thx in advance
UPDATE
SHOW CREATE TABLE last_check_date; result is
CREATE TABLE `last_check_date` (
`id` int(11) unsigned NOT NULL,
`user_id` bigint(20) unsigned NOT NULL,
`questions` datetime DEFAULT NULL,
`users` datetime DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8
And SHOW CREATE TABLE questions;
CREATE TABLE `questions` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`author_id` bigint(20) unsigned DEFAULT NULL,
`question` text NOT NULL,
`var_a` text NOT NULL,
`var_b` text NOT NULL,
`var_c` text NOT NULL,
`var_d` text NOT NULL,
`var_e` text NOT NULL,
`subject` int(11) unsigned DEFAULT NULL,
`chapter` int(11) unsigned DEFAULT NULL,
`section` int(11) unsigned DEFAULT NULL,
`paragraph` int(11) unsigned DEFAULT NULL,
`rank` tinyint(2) NOT NULL,
`add_dt` datetime NOT NULL,
`answer` varchar(1) NOT NULL,
PRIMARY KEY (`id`),
KEY `fk_chapters-id` (`chapter`),
KEY `fk_paragraphs-id` (`paragraph`),
KEY `fk_subjects-id` (`subject`),
KEY `fk_sections-id` (`section`),
KEY `fk_author-id` (`author_id`),
CONSTRAINT `fk_author-id` FOREIGN KEY (`author_id`) REFERENCES `users` (`id`) ON DELETE SET NULL ON UPDATE CASCADE,
CONSTRAINT `fk_chapters-id` FOREIGN KEY (`chapter`) REFERENCES `chapters` (`id`) ON DELETE SET NULL ON UPDATE CASCADE,
CONSTRAINT `fk_paragraphs-id` FOREIGN KEY (`paragraph`) REFERENCES `paragraphs` (`id`) ON DELETE SET NULL ON UPDATE CASCADE,
CONSTRAINT `fk_sections-id` FOREIGN KEY (`section`) REFERENCES `sections` (`id`) ON DELETE SET NULL ON UPDATE CASCADE,
CONSTRAINT `fk_subjects-id` FOREIGN KEY (`subject`) REFERENCES `subjects` (`id`) ON DELETE SET NULL ON UPDATE CASCADE
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8
$sql = "
SELECT q.id
FROM questions q
LEFT JOIN (
SELECT questions
FROM last_check_date
WHERE user_id=?
ORDER BY questions
DESC LIMIT 1
) l ON q.add_dt > l.questions"
$rs = mysql_query($sql);
$rowcount = mysql_num_rows($rs);
I don't know yet the proper syntax for PDO/MYSQLI, please adapt to your prefered driver.
See below. I have assumed that if there are no records in last_check_date that you still want to show questions (in that case all of them).
select q.id
from questions q
left outer join (
select max(questions) as questions
from last_check_date
where user_id = ?
) lcd on q.add_date > lcd.questions
where user_id = ?
order by questions desc

MySQL JOIN query more tables and return more result in one select

I would question, sorry my bad english :(
I have multiple tables
CREATE TABLE `user` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`username` varchar(50) COLLATE utf8_slovak_ci NOT NULL,
`password` varchar(200) COLLATE utf8_slovak_ci NOT NULL,
`status` tinyint(1) NOT NULL,
PRIMARY KEY (`id`,`username`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8 COLLATE=utf8_slovak_ci;
CREATE TABLE `user_acl` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`id_user` int(11) unsigned NOT NULL,
`group` smallint(5) unsigned NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8 COLLATE=utf8_slovak_ci;
CREATE TABLE `user_profil` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`email` varchar(100) COLLATE utf8_slovak_ci NOT NULL,
`fullname` varchar(100) COLLATE utf8_slovak_ci NOT NULL,
`profil` text COLLATE utf8_slovak_ci NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8 COLLATE=utf8_slovak_ci;
Query:
SELECT user.*, prf.*, acl.*
FROM (SELECT * FROM user LIMIT 1) AS user
LEFT JOIN user_acl AS acl ON (acl.id_user = user.id)
INNER JOIN user_profil AS prf ON (user.id = prf.id)
I have table user, user_acl, user_profil
table user and user_profil are indexed under id what is the common key
Table user_acl have id_usercommon key with table user (id) in the table but user_acl There are more rows for the table user and I need all rows from a table user_acl in one query.
You can get MySQL to combine values from multiple rows into one row with GROUP_CONCAT:
SELECT user.*, prf.*, GROUP_CONCAT(acl.id), GROUP_CONCAT(acl.group)
FROM user
LEFT JOIN user_acl AS acl ON (acl.id_user = user.id)
INNER JOIN user_profil AS prf ON (user.id = prf.id)
GROUP BY user.id;
You can't. You have to separate query, if you need more than one acl row to one user row.
[EDIT]: But if you need to to it with one query then you should use somekindof bitwise operations. http://codingrecipes.com/how-to-write-a-permission-system-using-bits-and-bitwise-operations-in-php

Categories