MariaDB/PostreSQL - Query optimization - mysql

I need to produce some numbers, I have designed a query to get the result desired by my "customers".
This query is based on a table that contains one million records.
I usually use MariaDB for that, and I get a result in ~ 7s.
This execution time is quite suitable but I am looking to optimize again to improve my skills.
After some research, I came across a few posts saying "MySQL is fine, but not on tables> 1M of records, you have to switch on something else" PostgreSQL has been quoted several times.
So I installed PostgreSQL, and copied my tables, indexes and data.
I executed the same query, and I had a result in ~ 12s
I know less PostgreSQL, I think I did not use the specificities inherent to the language.
So for now I stay on MariaDB. Do you have an idea to improve the execution time?
Here my query :
select categorie.cat
,dhu_type.type
,COUNT(DISTINCT(
CASE WHEN dhu.date between '2013-01-01' and '2013-12-31'
THEN dhu.id
END )
) AS "2013"
,COUNT(DISTINCT(
CASE WHEN dhu.date between '2014-01-01' and '2014-12-31'
THEN dhu.id
END )
) AS "2014"
,COUNT(DISTINCT(
CASE WHEN dhu.date between '2015-01-01' and '2015-12-31'
THEN dhu.id
END )
) AS "2015"
,COUNT(DISTINCT(
CASE WHEN dhu.date between '2016-01-01' and '2016-12-31'
THEN dhu.id
END )
) AS "2016"
from dhu
inner join dhu_type on dhu.type_id = dhu_type.id
inner join patient on dhu.patient_id=patient.id
inner join fa on patient.id = fa.patient_id
inner join categorie on categorie.id = fa.cat_id
group by cat,dhu_type.type
I complete my question with a diagram
Here the CREATE TABLE :
/*!40101 SET #OLD_CHARACTER_SET_CLIENT=##CHARACTER_SET_CLIENT */;
/*!40101 SET NAMES utf8 */;
/*!50503 SET NAMES utf8mb4 */;
/*!40014 SET #OLD_FOREIGN_KEY_CHECKS=##FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */;
/*!40101 SET #OLD_SQL_MODE=##SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */;
CREATE TABLE IF NOT EXISTS `categorie` (
`id` tinyint(3) unsigned NOT NULL AUTO_INCREMENT,
`cat` varchar(50) NOT NULL DEFAULT 'neonat',
PRIMARY KEY (`id`,`cat`)
) ENGINE=InnoDB AUTO_INCREMENT=16 DEFAULT CHARSET=utf8;
CREATE TABLE IF NOT EXISTS `cp` (
`id` smallint(5) unsigned NOT NULL AUTO_INCREMENT,
`cp` varchar(5) NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `cp` (`cp`)
) ENGINE=InnoDB AUTO_INCREMENT=4096 DEFAULT CHARSET=utf8;
CREATE TABLE IF NOT EXISTS `dhu` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`patient_id` int(10) unsigned NOT NULL,
`date` date NOT NULL,
`type_id` tinyint(3) unsigned NOT NULL,
PRIMARY KEY (`id`),
KEY `FK_dhu_patient` (`patient_id`),
KEY `FK_dhu_dhu_type` (`type_id`),
CONSTRAINT `FK_dhu_dhu_type` FOREIGN KEY (`type_id`) REFERENCES `dhu_type` (`id`),
CONSTRAINT `FK_dhu_patient` FOREIGN KEY (`patient_id`) REFERENCES `patient` (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=953590 DEFAULT CHARSET=utf8;
CREATE TABLE IF NOT EXISTS `dhu_import` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`noip` bigint(10) unsigned zerofill NOT NULL,
`date` date NOT NULL,
`cp` varchar(5) NOT NULL,
`type` varchar(4) NOT NULL,
PRIMARY KEY (`id`),
KEY `noip` (`noip`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
CREATE TABLE IF NOT EXISTS `dhu_type` (
`id` tinyint(3) unsigned NOT NULL AUTO_INCREMENT,
`type` varchar(4) NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `type` (`type`)
) ENGINE=InnoDB AUTO_INCREMENT=8 DEFAULT CHARSET=utf8;
CREATE TABLE IF NOT EXISTS `dpt` (
`dpt` tinyint(3) unsigned DEFAULT NULL,
`abrev` char(3) DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
CREATE TABLE IF NOT EXISTS `fa` (
`patient_id` int(10) unsigned NOT NULL,
`cat_id` tinyint(3) unsigned NOT NULL,
PRIMARY KEY (`patient_id`,`cat_id`),
KEY `idx_cat_id_pat_id` (`cat_id`,`patient_id`),
CONSTRAINT `FK_fa_patient` FOREIGN KEY (`patient_id`) REFERENCES `patient` (`id`),
CONSTRAINT `FK_fa_categorie` FOREIGN KEY (`cat_id`) REFERENCES `categorie` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
CREATE TABLE IF NOT EXISTS `fa_import` (
`noip` bigint(10) unsigned zerofill NOT NULL,
`cat` varchar(50) NOT NULL,
PRIMARY KEY (`noip`,`cat`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 ROW_FORMAT=COMPACT;
CREATE TABLE IF NOT EXISTS `patient` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`noip` bigint(10) unsigned zerofill NOT NULL,
`cp_id` smallint(5) unsigned NOT NULL,
PRIMARY KEY (`id`),
KEY `FK_patient_cp` (`cp_id`),
CONSTRAINT `FK_patient_cp` FOREIGN KEY (`cp_id`) REFERENCES `cp` (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=262141 DEFAULT CHARSET=utf8;
/*!40101 SET SQL_MODE=IFNULL(#OLD_SQL_MODE, '') */;
/*!40014 SET FOREIGN_KEY_CHECKS=IF(#OLD_FOREIGN_KEY_CHECKS IS NULL, 1, #OLD_FOREIGN_KEY_CHECKS) */;
/*!40101 SET CHARACTER_SET_CLIENT=#OLD_CHARACTER_SET_CLIENT */;
Here the explain query :
Here a modification improving performance (select categorie.id instead of categorie.cat):
Here the best best query I found thanks #RickJames & #BillKarwin
select categorie.cat
,dhu_type.`type`
,t.`2013`
,t.`2014`
,t.`2015`
,t.`2016`
from ( select fa.cat_id as catid
,dhu.type_id typid
,COUNT(DISTINCT(
CASE WHEN dhu.date between '2013-01-01' and '2013-12-31'
THEN dhu.id
END )
) AS "2013"
,COUNT(DISTINCT(
CASE WHEN dhu.date between '2014-01-01' and '2014-12-31'
THEN dhu.id
END )
) AS "2014"
,COUNT(DISTINCT(
CASE WHEN dhu.date between '2015-01-01' and '2015-12-31'
THEN dhu.id
END )
) AS "2015"
,COUNT(DISTINCT(
CASE WHEN dhu.date between '2016-01-01' and '2016-12-31'
THEN dhu.id
END )
) AS "2016"
from dhu
inner join patient on dhu.patient_id=patient.id
inner join fa on patient.id = fa.patient_id
group by fa.cat_id, dhu.type_id ) t
inner join categorie on t.catid = categorie.id
inner join dhu_type on t.typid = dhu_type.id
order by categorie.cat,dhu_type.`type`

MySQL does just fine with billion-row tables.
Any database engine is at the mercy of the speed of the disk and how much (or little) RAM you have for caching.
The textbooks say to normalize everything, but I suggest that a 4-char type is not worth normalizing. Ditto for the 5-char cp.
Unless you really want output rows with all zeros, add this WHERE dhu.date between '2016-01-01' and '2016-12-31' before the GROUP BY.
Follow my advice here on many:many schema design (fa). This may speed up the query for MySQL. (I don't know if the same principles apply to Postgres.)

Related

Can't specify target table t4 for update in FROM clause

I am trying to update my table:
UPDATE offers t1
SET t1.deleted_at = NOW()
WHERE t1.id
NOT IN
(
SELECT f.id
FROM (
SELECT ean, MIN(net_price) as minprice
FROM offers group BY ean
)
as x inner join offers as f on f.ean = x.ean and f.net_price = x.minprice
);
can anybody help me with this issue?
I realized that I needed to solve the issue through left join, but did not understand how to apply it in my case. Thank you.
Here is create table:
CREATE TABLE `offers` (
`id` bigint unsigned NOT NULL AUTO_INCREMENT,
`name` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
`net_price` decimal(10,2) unsigned DEFAULT NULL,
`retail_price` decimal(10,2) unsigned DEFAULT NULL,
`supplier_id` bigint unsigned DEFAULT NULL,
`manufacturer_id` bigint unsigned DEFAULT NULL,
`stock` int DEFAULT NULL,
`ean` varchar(14) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`mnn_id` bigint unsigned DEFAULT NULL,
`source_id` bigint unsigned NOT NULL,
`nds` smallint unsigned DEFAULT NULL,
`to_export` tinyint(1) DEFAULT NULL,
`created_at` timestamp NULL DEFAULT NULL,
`updated_at` timestamp NULL DEFAULT NULL,
`deleted_at` timestamp NULL DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `offers_supplier_id_foreign` (`supplier_id`),
KEY `offers_manufacturer_id_foreign` (`manufacturer_id`),
KEY `offers_mnn_id_foreign` (`mnn_id`),
KEY `offers_source_id_foreign` (`source_id`),
CONSTRAINT `offers_manufacturer_id_foreign` FOREIGN KEY (`manufacturer_id`) REFERENCES `manufacturers` (`id`) ON DELETE CASCADE ON UPDATE CASCADE,
CONSTRAINT `offers_mnn_id_foreign` FOREIGN KEY (`mnn_id`) REFERENCES `mnns` (`id`) ON DELETE CASCADE ON UPDATE CASCADE,
CONSTRAINT `offers_source_id_foreign` FOREIGN KEY (`source_id`) REFERENCES `sources` (`id`) ON DELETE CASCADE ON UPDATE CASCADE,
CONSTRAINT `offers_supplier_id_foreign` FOREIGN KEY (`supplier_id`) REFERENCES `suppliers` (`id`) ON DELETE CASCADE ON UPDATE CASCADE
) ENGINE=InnoDB AUTO_INCREMENT=25762 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
The error you are getting is
You can't specify target table 't1' for update in FROM clause
This is a problem specific to MySQL. You cannot directly access the updated table in a subquery, you need a sub subquery :-)
Replace
inner join offers as f
by
inner join (select * from offers) as f
hence to get the query working.
You don't need the join by the way. You can just write
UPDATE offers
SET deleted_at = NOW()
WHERE (ean, net_price) NOT IN
(
SELECT ean, MIN(net_price)
FROM (select * from offers) o
GROUP BY ean
);
Another way to write this (update all rows for which exists a lower price for the same EAN):
UPDATE offers t1
SET t1.deleted_at = NOW()
WHERE EXISTS
(
SELECT NULL
FROM (SELECT * FROM offers) t2
WHERE t2.ean = t1.ean
AND t2.net_price < t1.net_price
);
Demo: https://dbfiddle.uk/?rdbms=mysql_8.0&fiddle=bfa6d74a72d40fa445ea5fa9f95803d1
By the way: You may want to add the condition deleted_at IS NULL to your update statement in order not to update rows that are already logically deleted, if such rows exists.

How to order by calculated column in mysql

It is very slow to order by calculated column. Like this one:
select
`users`.`id`,
`username`,
`about_me`,
(
select
count(*)
from
`interests`
inner join `users_interests` on `interests`.`id` = `users_interests`.`interest_id`
where
`users`.`id` = `users_interests`.`user_id`
and `interest_id` in (1, 2, 4) --this is ids of interests which authenticated user has chosen
) as `interests_count`
from
`users`
order by
`interests_count` desc
So in this query i am ordering users By interests which is best suiting authenticated user. And it is very slow in large data tables. Any ideas what will be alternative and much faster solution. Thanks in advance
EDIT
Users Table
CREATE TABLE `users` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
`roles_id` bigint(20) unsigned NOT NULL DEFAULT 2,
`username` varchar(100) COLLATE utf8mb4_unicode_ci NOT NULL,
`about_me` varchar(70) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`email` varchar(100) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`status` tinyint(1) NOT NULL DEFAULT 0,
`email_verified_at` timestamp NULL DEFAULT NULL,
`password` varchar(191) COLLATE utf8mb4_unicode_ci NOT NULL,
`created_at` timestamp NULL DEFAULT NULL,
`updated_at` timestamp NULL DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `users_username_unique` (`username`),
UNIQUE KEY `users_email_unique` (`email`),
KEY `users_roles_id_foreign` (`roles_id`),
CONSTRAINT `users_roles_id_foreign` FOREIGN KEY (`roles_id`) REFERENCES
`user_roles` (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=50102 DEFAULT CHARSET=utf8mb4
COLLATE=utf8mb4_unicode_ci
Interests Table
CREATE TABLE `interests` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
`name` varchar(191) COLLATE utf8mb4_unicode_ci NOT NULL,
`interest_category_id` bigint(20) unsigned NOT NULL,
`created_at` timestamp NULL DEFAULT NULL,
`updated_at` timestamp NULL DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `interests_interest_category_id_foreign` (`interest_category_id`),
CONSTRAINT `interests_interest_category_id_foreign` FOREIGN KEY
(`interest_category_id`) REFERENCES `interests_categories` (`id`) ON DELETE
CASCADE
) ENGINE=InnoDB AUTO_INCREMENT=61 DEFAULT CHARSET=utf8mb4
COLLATE=utf8mb4_unicode_ci
Pivot table between Users and Interests
CREATE TABLE `users_interests` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
`user_id` bigint(20) unsigned NOT NULL,
`interest_id` bigint(20) unsigned NOT NULL,
PRIMARY KEY (`id`),
KEY `users_interests_user_id_foreign` (`user_id`),
KEY `users_interests_interest_id_foreign` (`interest_id`),
CONSTRAINT `users_interests_interest_id_foreign` FOREIGN KEY (`interest_id`) REFERENCES `interests` (`id`) ON DELETE CASCADE,
CONSTRAINT `users_interests_user_id_foreign` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON DELETE CASCADE
) ENGINE=InnoDB AUTO_INCREMENT=251552 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
Without test data it's difficult to know if this would be faster than your current query, but you might find the use of a Common Table Expression (a.k.a. WITH clause) to be helpful here:
with cteCount AS (select ui.`user_id`,
count(*) as `interests_count`
from `interests` i
inner join `users_interests` ui
on i.`id` = ui.`interest_id`
where ui.`interest_id` in (1, 2, 4))
select u.`id`,
`username`,
`about_me`,
cc.`interests_count`
from `users` u
inner join cteCount cc
on cc.`user_id` = u.`id`
order by
cc.`interests_count` desc
The performance of this query will be affected by the presence or absence of indexes on the appropriate columns. Just glancing at it I'd say you should have the following indexes available:
interests
Index 1: id
users_interests
Index 1: interest_id
users
Index 1: id
Try to directly join and aggregate.
SELECT u.id,
u.username,
u.about_me,
count(ui.interest_id) interests_count
FROM users u
LEFT JOIN users_interests ui
ON ui.user_id = u.id
AND ui.interest_id IN (1, 2, 4)
GROUP BY u.id,
u.username,
u.about_me;
Additionally create an index supporting the aggregation.
CREATE INDEX users_id_username_about_me
ON users
(id,
username,
about_me);

Mysql update query with inner table join

I have problem with update query in mysql.
When I try this
update planned_expense p1
set deleted=1
where case_id=204
and deleted =0
and type='MONTHLY'
and planned_date>='2017-04-01'
and id > (select min(id) from planned_expense p2 where
p2.case_id = p1.case_id
and p2.planned_date = p1.planned_date
and p2.account = p1.account
and p2.type = p1.type and p2.deleted = 0)
I get
You can't specify target table 'p1' for update in FROM clause
And when I try
update planned_expense p1
set deleted=1
where case_id=204
and deleted =0
and type='MONTHLY'
and planned_date>='2017-04-01'
and id > (select min(id)
from (select * from planned_expense p2
where p2.case_id=p1.case_id and
p2.planned_date=p1.planned_date
and p2.account=p1.account and p2.type=p1.type and p2.deleted=0) p3)
I get
Unknown column 'p1.case_id' in 'where clause'
What should I write in order to update those records?
Tnank you.
This is my table
DROP TABLE IF EXISTS `bes-ers`.`planned_expense`;
CREATE TABLE `bes-ers`.`planned_expense` (
`ID` bigint(20) NOT NULL AUTO_INCREMENT,
`case_id` bigint(20) DEFAULT NULL,
`deleted` tinyint(1) DEFAULT '0',
`planned_date` datetime DEFAULT NULL,
`addition_mark` int(11) DEFAULT NULL,
`code` varchar(255) COLLATE utf8_unicode_ci DEFAULT NULL,
`planned_amount` decimal(22,4) DEFAULT NULL,
`account` varchar(255) COLLATE utf8_unicode_ci DEFAULT NULL,
`type` varchar(255) COLLATE utf8_unicode_ci DEFAULT NULL,
`search_field` varchar(4096) COLLATE utf8_unicode_ci DEFAULT NULL,
`description` varchar(4096) COLLATE utf8_unicode_ci DEFAULT NULL,
`created_by_id` bigint(20) DEFAULT NULL,
`locked` tinyint(4) DEFAULT '0',
PRIMARY KEY (`ID`),
KEY `FK_planned_expenses_1` (`case_id`),
KEY `FK_planned_expenses_created_by_id` (`created_by_id`),
KEY `planned_expense_planned_date` (`planned_date`),
KEY `planned_expense_type` (`type`),
KEY `planned_expense_deleted` (`deleted`),
CONSTRAINT `FK_planned_expenses_1` FOREIGN KEY (`case_id`) REFERENCES `bankruptcy_case` (`ID`) ON DELETE SET NULL ON UPDATE SET NULL,
CONSTRAINT `FK_planned_expenses_created_by_id` FOREIGN KEY (`created_by_id`) REFERENCES `user` (`ID`)
) ENGINE=InnoDB AUTO_INCREMENT=13172954 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
Mysql don't allow the update of the table if is involved in select but you can avoid this problem using a dinamica select table eg:
update planned_expense
set deleted=1
where case_id=204
and deleted =0
and type='MONTHLY'
and planned_date>='2017-04-01'
and id > ( select t.min_id from (select min(id) min_id
from planned_expense p2
INNER JOIN planned_expense p1 on p2.case_id=p1.case_id
and p2.planned_date=p1.planned_date
and p2.account=p1.account
and p2.type=p1.type and p2.deleted=0) t )
try adding a composite index as
create index idx_test on planned_expense (case_id, planned_date, account)
As explained by #scaisEdge, you have to use a join and to speed up you query,
try creating a composite key as per your where clause.
Something like:
KEY case_id_deleted_type_planned_date_id (case_id, deleted, type, planned_date, id)
Use Explain to check if your query is using proper indexes or not.
Thank you all, I did what I wanted to by creating temp table and than working with it.
Thanks for

MySQL Improve performance execution time

I am trying to improve performance of following query which took 93.2 sec to execute query below:
SELECT year(date), month(date), `country_name_name`,
CEIL(count(res.`user_xmpp_login`) /DAY(LAST_DAY(date))) as avgUser,
CEIL(count(res.user)/DAY(LAST_DAY(date))) as avgPurchase
FROM
( SELECT DATE(`user_registration_timestamp`) as date,
user_country,
NULL as user, `user_xmpp_login`
FROM users
WHERE `user_registration_timestamp` >= "2015-01-01 00:00:00"
AND `user_registration_timestamp` < "2016-01-01 00:00:00"
UNION ALL
SELECT DATE(`ts`) as date, user_country, user, NULL as `user_xmpp_login`
FROM purchase_log p
INNER JOIN users u ON u.`user_xmpp_login` = p.`user`
WHERE `ts` >= "2015-01-01 00:00:00"
AND `ts` < "2016-01-01 00:00:00"
AND result in ('ok', 'cancelled', 'pending')
) AS res
INNER JOIN countries c ON c.`country_id` = res.`user_country`
INNER JOIN country_names cn
ON (cn.`country_name_country` = c.`country_id`
AND cn.`country_name_language` = 'en')
GROUP BY 1,2,3
ORDER BY 4 DESC,5 DESC, 3 ASC;
Explain command shows:
And structure of each table is:
purchase table:
CREATE TABLE `purchase` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`user` varchar(255) COLLATE utf8_unicode_ci NOT NULL,
`ts` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
`result` varchar(32) COLLATE utf8_unicode_ci NOT NULL,
PRIMARY KEY (`id`),
KEY `iuser` (`user`),
) ENGINE=InnoDB AUTO_INCREMENT=12710221 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
users table:
CREATE TABLE `users` (
`user_id` int(11) NOT NULL AUTO_INCREMENT,
`user_country` int(11) DEFAULT NULL,
`user_xmpp_login` varchar(255) COLLATE utf8_unicode_ci DEFAULT NULL,
`user_registration_timestamp` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`user_id`),
UNIQUE KEY `user_xmpp_login_UNIQUE` (`user_xmpp_login`),
KEY `user_country_FK` (`user_country`),
KEY `user_registration_timestamp` (`user_registration_timestamp`),
CONSTRAINT `users_country_FK` FOREIGN KEY (`user_country`)
REFERENCES `countries` (`country_id`) ON UPDATE CASCADE
) ENGINE=InnoDB AUTO_INCREMENT=33504745 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
countries table
CREATE TABLE `countries` (
`country_id` int(11) NOT NULL AUTO_INCREMENT,
`country_code` varchar(2) COLLATE utf8_unicode_ci NOT NULL,
PRIMARY KEY (`country_id`),
) ENGINE=InnoDB AUTO_INCREMENT=508 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
country names
CREATE TABLE `country_names` (
`country_name_id` int(11) NOT NULL AUTO_INCREMENT,
`country_name_country` int(11) NOT NULL,
`country_name_language` char(2) COLLATE utf8_unicode_ci NOT NULL,
`country_name_name` varchar(255) COLLATE utf8_unicode_ci NOT NULL,
PRIMARY KEY (`country_name_id`),
UNIQUE KEY `country_name_country_language_UNIQUE`
(`country_name_country`,`country_name_language`),
KEY `country_name_language` (`country_name_language`),
CONSTRAINT `country_name_country` FOREIGN KEY (`country_name_country`)
REFERENCES `countries` (`country_id`) ON DELETE CASCADE ON UPDATE CASCADE
) ENGINE=InnoDB AUTO_INCREMENT=45793 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
Is there any recommendations?
If you time each subquery, I think you will find users is the slowest component.
The purchase_log subquery can probably be improved with this "covering" INDEX(result, ts, user).
Combine the two "country" tables!. Use CHAR(2) CHARACTER SET ascii for the PRIMARY KEY and the JOINs to other tables. It is only 2 bytes, unlike INT, which is 4 bytes and VARCHAR..., which is 3 bytes (in this case).
You mention ts, but I don't see where it is coming from. If it is in purchase_log, then that table needs INDEX(user, ts).
What percentage of the users involved 2015? If it is more than about 20%, the INDEX(user_registration_timestamp) won't help.
Consider: Get rid of PRIMARY KEY (country_name_id), and promote the UNIQUE key to PRIMARY.
The biggest problem seems to be in your users table. Remember, mysql can only use one index per table for most situations. On your users table, the user_xmpp_login_UNIQUE column has been used to join it to the purchase_log table. There fore, the user_registration_timestamp index is not being used on the comparison involving the timestamp column.
One suggestion is to create a composite index on the user_xmpp_login and user_registration_timestamp columns.

Need help to optimize MySQL query

I have 6 tables:
CREATE TABLE IF NOT EXISTS `sbpr_groups` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(255) DEFAULT NULL,
`active` tinyint(1) DEFAULT '0',
`dnd` tinyint(1) DEFAULT '0',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 AUTO_INCREMENT=32 ;
CREATE TABLE IF NOT EXISTS `sbpr_newsletter` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`created_date` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
`from` varchar(255) COLLATE utf8_unicode_ci NOT NULL,
`mail` varchar(255) COLLATE utf8_unicode_ci NOT NULL,
`subject` varchar(255) COLLATE utf8_unicode_ci NOT NULL,
`body` text COLLATE utf8_unicode_ci,
`attach1` varchar(255) COLLATE utf8_unicode_ci NOT NULL,
`attach2` varchar(255) COLLATE utf8_unicode_ci NOT NULL,
`attach3` varchar(255) COLLATE utf8_unicode_ci NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci AUTO_INCREMENT=14;
CREATE TABLE IF NOT EXISTS `sbpr_news_groups` (
`newsletter_id` int(11) NOT NULL,
`groups` int(11) NOT NULL,
KEY `fk_sbpr_news_groups_sbpr_newsletter1` (`newsletter_id`),
KEY `fk_sbpr_news_groups_sbpr_groups1` (`groups`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
CREATE TABLE IF NOT EXISTS `sbpr_news_recs` (
`newsletter_id` int(11) NOT NULL,
`recipients` int(11) NOT NULL,
KEY `fk_sbpr_news_recs_sbpr_newsletter1` (`newsletter_id`),
KEY `fk_sbpr_news_recs_sbpr_recipients1` (`recipients`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
CREATE TABLE IF NOT EXISTS `sbpr_recipients` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`mail` varchar(160) DEFAULT NULL,
`date_reg` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
`active` tinyint(1) DEFAULT '0',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 AUTO_INCREMENT=3008 ;
CREATE TABLE IF NOT EXISTS `sbpr_rec_groups` (
`rec_id` int(11) NOT NULL,
`group` int(11) NOT NULL,
KEY `fk_sbpr_rec_groups_sbpr_recipients` (`rec_id`),
KEY `fk_sbpr_rec_groups_sbpr_groups1` (`group`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin;
With this foreign keys:
ALTER TABLE `sbpr_news_groups`
ADD CONSTRAINT `fk_sbpr_news_groups_sbpr_groups1`
FOREIGN KEY (`groups`) REFERENCES `sbpr_groups` (`id`)
ON DELETE CASCADE ON UPDATE NO ACTION,
ADD CONSTRAINT `fk_sbpr_news_groups_sbpr_newsletter1`
FOREIGN KEY (`newsletter_id`) REFERENCES `sbpr_newsletter` (`id`)
ON DELETE CASCADE ON UPDATE NO ACTION;
ALTER TABLE `sbpr_news_recs`
ADD CONSTRAINT `fk_sbpr_news_recs_sbpr_newsletter1`
FOREIGN KEY (`newsletter_id`) REFERENCES `sbpr_newsletter` (`id`)
ON DELETE CASCADE ON UPDATE NO ACTION,
ADD CONSTRAINT `fk_sbpr_news_recs_sbpr_recipients1`
FOREIGN KEY (`recipients`) REFERENCES `sbpr_recipients` (`id`)
ON DELETE CASCADE ON UPDATE NO ACTION;
ALTER TABLE `sbpr_rec_groups`
ADD CONSTRAINT `fk_sbpr_rec_groups_sbpr_groups1`
FOREIGN KEY (`group`) REFERENCES `sbpr_groups` (`id`)
ON DELETE CASCADE ON UPDATE NO ACTION,
ADD CONSTRAINT `fk_sbpr_rec_groups_sbpr_recipients`
FOREIGN KEY (`rec_id`) REFERENCES `sbpr_recipients` (`id`)
ON DELETE CASCADE ON UPDATE NO ACTION;
Visual structure of tables:
I want to select all rows from sbpr_newsletter table, and add to each of these lines the number of rows from sbpr_recipients whose id prescribed in sbpr_news_recs or prescribed in sbpr_rec_groups depends on FKs.
Ex. I want to select count of all recipients of current newsletter wihch are in sbpr_news_recs or exists in group which are in sbpr_rec_groups plus count of active recipients.
I have working SQL:
SELECT d.id, d.subject , d.created_date,
(SELECT count(*) FROM sbpr_recipients r
LEFT JOIN sbpr_news_recs nr ON nr.recipients = r.id
LEFT JOIN sbpr_rec_groups g ON g.rec_id = r.id
LEFT JOIN sbpr_news_groups ng ON ng.groups = g.group
WHERE nr.newsletter_id = d.id OR ng.newsletter_id = d.id) AS repicients,
(SELECT count(*) FROM sbpr_recipients r
LEFT JOIN sbpr_news_recs nr ON nr.recipients = r.id
LEFT JOIN sbpr_rec_groups g ON g.rec_id = r.id
LEFT JOIN sbpr_news_groups ng ON ng.groups = g.group
WHERE (nr.newsletter_id = d.id OR ng.newsletter_id = d.id)
AND r.active = 1) AS active_repicients
FROM sbpr_newsletter d
ORDER BY d.id ASC, d.id
Explain of this sql:
Question:
How can I optimize my sql?
Just approach to optimize, two SELECT queries are transfered into JOIN clause -
SELECT d.id
, d.subject
, d.created_date
, count(if(nr_newsletter_id is not null or ng_newsletter_id is not null, 1, null)) repicients
, count(if((nr_newsletter_id is not null or ng_newsletter_id is not null) and t.active = 1, 1, null)) active_repicients
FROM
sbpr_newsletter d
LEFT JOIN (
SELECT nr.newsletter_id nr_newsletter_id
, ng.newsletter_id ng_newsletter_id
, r.active
FROM
sbpr_recipients r
LEFT JOIN sbpr_news_recs nr
ON nr.recipients = r.id
LEFT JOIN sbpr_rec_groups g
ON g.rec_id = r.id
LEFT JOIN sbpr_news_groups ng
ON ng.groups = g.group
) t
ON nr_newsletter_id = d.id OR ng_newsletter_id = d.id
GROUP BY
d.id;
I rewrited your query a little, it is not tested, but try it.
You COULD create a view and query that instead - trade off is storage but should vastly remove load from server...
The subquery for recipients / active_recipients runs twice, and each time returns 3311 records, so it would be worth defining as a view.
Otherwise, define indexes on the foreign keys you use in joins.