Mysql sub query with MAX - mysql

I'm trying to speed up a query which currently takes 1.2 seconds to run. The query is:
select
process.exchange,
process.market,
process.volume,
process.bid,
process.ask,
(select MAX(last) FROM a where a.exchange = process.exchange
AND a.market = process.market
AND a.created_date > NOW() - INTERVAL 5 MINUTE LIMIT 1) as Ask1,
Ask2,
((Ask2 / (select MAX(last) FROM a where a.exchange = process.exchange
AND a.market = process.market
AND a.created_date > NOW() - INTERVAL 5 MINUTE LIMIT 1 ))
* 100) - 100 as percentage
FROM process
WHERE process.exchange IN('BLAH','BLAH2')
ORDER BY percentage ASC
Table structures:
CREATE TABLE `a` (
`id` int(10) UNSIGNED NOT NULL,
`exchange` varchar(15) NOT NULL,
`market` varchar(15) NOT NULL,
`volume` double UNSIGNED NOT NULL,
`bid` double NOT NULL,
`ask` double UNSIGNED NOT NULL,
`last` double NOT NULL,
`created_date` datetime NOT NULL
) ENGINE=MyISAM DEFAULT CHARSET=utf8;
ALTER TABLE `a`
ADD PRIMARY KEY (`id`),
ADD KEY `market` (`market`),
ADD KEY `exchange` (`exchange`),
ADD KEY `created_date` (`created_date`);
SET SQL_MODE = "NO_AUTO_VALUE_ON_ZERO";
SET time_zone = "+00:00";
CREATE TABLE `process` (
`id` int(11) NOT NULL,
`exchange` varchar(20) NOT NULL,
`market` varchar(10) NOT NULL,
`volume` double NOT NULL,
`bid` double NOT NULL,
`ask` double NOT NULL,
`Ask2` double NOT NULL,
`created_date` datetime NOT NULL
) ENGINE=MyISAM DEFAULT CHARSET=utf8;
ALTER TABLE `process`
ADD PRIMARY KEY (`id`),
ADD KEY `created_date` (`created_date`),
ADD KEY `exchange` (`exchange`),
ADD KEY `market` (`market`);
ALTER TABLE `process`
MODIFY `id` int(11) NOT NULL AUTO_INCREMENT;
I need to work out the percentage change between Ask1 and Ask2. Ask1 information is in another table called a that has lots of rows of the prices for each market and exchange.
This is how the data in table "a" looks like:
Exchange Market Volume Bid Ask Last Created Date
BLAH APL 3000 1.2 1.3 1.3 2017-07-26 16:31:00
BLAH APL 3000 1.4 1.5 1.45 2017-07-26 16:30:00
I need Ask1 to get information from here getting the highest value in the last 5 minutes. So the sub query I got is:
select MAX(last)
FROM a
where a.exchange = process.exchange
AND a.market = process.market
AND a.created_date > NOW() - INTERVAL 5 MINUTE
LIMIT 1
I need to select it and also do a calculation with it so at the moment I have to have the sub query there twice.
The MAX seems to slow down the sub query a lot.
This is what EXPLAIN says:
id select type table partitions type possible keys key key_len ref rows filtered Extra
1 PRIMARY process NULL ALL exchange NULL NULL NULL 272 99.63 Using where; Using temporary; Using filesort
2 DEPENDENT SUBQUERY a NULL ref market,exchange,created_date market 47 cms.process.market 1603 0.17 Using index condition; Using where
3 DEPENDENT SUBQUERY a NULL ref market,exchange,created_date market 47 cms.process.market 1603 0.17 Using index condition; Using where
The "a" table has about a million rows and "process" table has about 630 rows.
Is it possible to speed it up?
Thanks in advance for your help.

Related

How to improve query speed in mysql query

I'm trying to optimize my query speed as much as possible. A side problem is that I cannot see the exact query speed, because it is rounded to a whole second. The query does get the expected result and takes about 1 second. The final query should be extended even more and for this reason i am trying to improve it. How can this query be improved?
The database is constructed as an electricity utility company. The query should eventually calculate an invoice. I basically have 4 tables, APX price, powerdeals, powerload, eans_power.
APX price is an hourly price, powerload is a quarterly hour volume. First step is joining these two together for each quarter of an hour.
Second step is that I currently select the EAN that is indicated in the table eans_power.
Finally I will join the Powerdeals that currently consist only of a single line and indicates from which hour, until which hour and weekday from/until it should be applicable. It consist of an hourly volume and price. Currently it is only joined on the hours, but it will be extended to weekdays as well.
MYSQL Query:
SELECT l.DATE, l.PERIOD_FROM, a.PRICE, l.POWERLOAD,
SUM(a.PRICE*l.POWERLOAD), SUM(d.hourly_volume/4)
FROM timeseries.powerload l
INNER JOIN timeseries.apxprice a ON l.DATE = a.DATE
INNER JOIN contracts.eans_power c ON l.ean = c.ean
LEFT OUTER JOIN timeseries.powerdeals d ON d.period_from <= l.period_from
AND d.period_until >= l.period_until
WHERE l.PERIOD_FROM >= a.PERIOD_FROM
AND l.PERIOD_FROM < a.PERIOD_UNTIL
AND l.DATE >= '2018-01-01'
AND l.DATE <= '2018-12-31'
GROUP BY l.date
Explain:
1 SIMPLE c NULL system PRIMARY,ean NULL NULL NULL 1 100.00 Using temporary; Using filesort
1 SIMPLE l NULL ref EAN EAN 21 const 35481 11.11 Using index condition
1 SIMPLE d NULL ALL NULL NULL NULL NULL 1 100.00 Using where; Using join buffer (Block Nested Loop)
1 SIMPLE a NULL ref DATE DATE 4 timeseries.l.date 24 11.11 Using index condition
Create table queries:
apxprice
CREATE TABLE `apxprice` (
 `apx_id` int(11) NOT NULL AUTO_INCREMENT,
 `date` date DEFAULT NULL,
 `period_from` time DEFAULT NULL,
 `period_until` time DEFAULT NULL,
 `price` decimal(10,2) DEFAULT NULL,
 PRIMARY KEY (`apx_id`),
 KEY `DATE` (`date`,`period_from`,`period_until`)
) ENGINE=MyISAM AUTO_INCREMENT=29664 DEFAULT CHARSET=latin1
powerdeals
CREATE TABLE `powerdeals` (
 `deal_id` int(11) NOT NULL AUTO_INCREMENT,
 `date_deal` date NOT NULL,
 `start_date` date NOT NULL,
 `end_date` date NOT NULL,
 `weekday_from` int(11) NOT NULL,
 `weekday_until` int(11) NOT NULL,
 `period_from` time NOT NULL,
 `period_until` time NOT NULL,
 `hourly_volume` int(11) NOT NULL,
 `price` int(11) NOT NULL,
 `type_deal_id` int(11) NOT NULL,
 `contract_id` int(11) NOT NULL,
 PRIMARY KEY (`deal_id`)
) ENGINE=MyISAM AUTO_INCREMENT=2 DEFAULT CHARSET=latin1
powerload
CREATE TABLE `powerload` (
 `powerload_id` int(11) NOT NULL AUTO_INCREMENT,
 `ean` varchar(18) DEFAULT NULL,
 `date` date DEFAULT NULL,
 `period_from` time DEFAULT NULL,
 `period_until` time DEFAULT NULL,
 `powerload` int(11) DEFAULT NULL,
 PRIMARY KEY (`powerload_id`),
 KEY `EAN` (`ean`,`date`,`period_from`,`period_until`)
) ENGINE=MyISAM AUTO_INCREMENT=61039 DEFAULT CHARSET=latin1
eans_power
CREATE TABLE `eans_power` (
 `ean` char(19) NOT NULL,
 `contract_id` int(11) NOT NULL,
 `invoicing_id` int(11) NOT NULL,
 `street` varchar(255) NOT NULL,
 `number` int(11) NOT NULL,
 `affix` char(11) NOT NULL,
 `postal` char(6) NOT NULL,
 `city` varchar(255) NOT NULL,
 PRIMARY KEY (`ean`),
 KEY `ean` (`ean`,`contract_id`,`invoicing_id`)
) ENGINE=MyISAM DEFAULT CHARSET=latin1
Sample data tables
apx_prices
apx_id,date,period_from,period_until,price
1,2016-01-01,00:00:00,01:00:00,23.86
2,2016-01-01,01:00:00,02:00:00,22.39
powerdeals
deal_id,date_deal,start_date,end_date,weekday_from,weekday_until,period_from,period_until,hourly_volume,price,type_deal_id,contract_id
1,2019-05-15,2018-01-01,2018-12-31,1,5,08:00:00,20:00:00,1000,50,3,1
powerload
powerload_id,ean,date,period_from,period_until,powerload
1,871688520000xxxxxx,2018-01-01,00:00:00,00:15:00,9
2,871688520000xxxxxx,2018-01-01,00:15:00,00:30:00,11
eans_power
ean,contract_id,invoicing_id,street,number,affix,postal,city
871688520000xxxxxx,1,1,road,14,postal,city
Result, without sum() and group by:
DATE,PERIOD_FROM,PRICE,POWERLOAD,a.PRICE*l.POWERLOAD,d.hourly_volume/4,
2018-01-01,00:00:00,27.20,9,244.80,NULL
2018-01-01,00:15:00,27.20,11,299.20,NULL
Result, with sum() and group by:
DATE, PERIOD_FROM, PRICE, POWERLOAD, SUM(a.PRICE*l.POWERLOAD), SUM(d.hourly_volume/4)
2018-01-01,08:00:00,26.33,21,46193.84,12250.0000
2018-01-02, 08:00:00,47.95,43,90623.98,12250.0000
Preliminary optimizations:
Use InnoDB, not MyISAM.
Use CHAR only for constant-lenght strings
Use consistent datatypes (see ean, for example)
For an alternative to using time-to-the-second, check out the Handler counts .
Because range tests (such as l.PERIOD_FROM >= a.PERIOD_FROM AND l.PERIOD_FROM < a.PERIOD_UNTIL) are essentially impossible to optimize, I recommend you expand the table to have one entry per hour (or 1 per quarter hour, if necessary). Looking up a row via a key is much faster than doing a scan of "ALL" the table. 9K rows for an entire year is trivial.
When you get past these recommendations (and the Comments), I will have more tips on optimizing the indexes, especially InnoDB's PRIMARY KEY.

MySQL query - taking ORDER BY off making query 100 times faster

I have found very long query in my system. The MySQL Slow Log says the following:
# Time: 2018-07-08T18:47:02.273314Z
# User#Host: server[server] # localhost [] Id: 1467
# Query_time: 97.251247 Lock_time: 0.000210 Rows_sent: 50 Rows_examined: 41646378
SET timestamp=1531075622;
SELECT n1.full_name AS sender_full_name, s1.email AS sender_email,
e.subject, e.body, e.attach, e.date, e.id, r.status,
n2.full_name AS receiver_full_name, s2.email AS receiver_email,
r.basket,
FROM email_routing r
JOIN email e ON e.id = r.message_id
JOIN people_emails s1 ON s1.id = r.sender_email_id
JOIN people n1 ON n1.id = s1.people_id
JOIN people_emails s2 ON s2.id = r.receiver_email_id
JOIN people n2 ON n2.id = s2.people_id
WHERE r.sender_email_id = 21897 ORDER BY e.date desc LIMIT 0, 50;
The EXPLAIN query shows no full table scan and the query using indexes:
id select_type table partitions type possible_keys key key_len ref rows filtered Extra
1 SIMPLE s1 NULL const PRIMARY PRIMARY 4 const 1 100.00 Using temporary; Using filesort
1 SIMPLE n1 NULL const PRIMARY,ppl PRIMARY 4 const 1 100.00 NULL
1 SIMPLE n2 NULL index PRIMARY,ppl ppl 771 NULL 1 100.00 Using index
1 SIMPLE s2 NULL index PRIMARY s2 771 NULL 3178 10.00 Using where; Using index; Using join buffer (Block Nested Loop)
1 SIMPLE r NULL ref bk1,bk2,msgid bk1 4 server.s2.id 440 6.60 Using where; Using index
1 SIMPLE e NULL eq_ref PRIMARY PRIMARY 4 server.r.message_id 1 100.00 NULL
Here is my SHOW CREATE TABLE queries for the used tables:
CREATE TABLE `email_routing` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`message_id` int(11) NOT NULL,
`sender_email_id` int(11) NOT NULL,
`receiver_email_id` int(11) NOT NULL,
`basket` int(11) NOT NULL,
`status` int(11) NOT NULL,
`popup` int(11) NOT NULL DEFAULT '0',
`tm` int(11) NOT NULL DEFAULT '0',
KEY `id` (`id`),
KEY `bk1` (`receiver_email_id`,`status`,`sender_email_id`,`message_id`,`basket`),
KEY `bk2` (`sender_email_id`,`tm`),
KEY `msgid` (`message_id`)
) ENGINE=InnoDB AUTO_INCREMENT=1055796 DEFAULT CHARSET=utf8
-
CREATE TABLE `email` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`subject` text NOT NULL,
`body` text NOT NULL,
`date` datetime NOT NULL,
`attach` text NOT NULL,
`attach_dir` varchar(255) CHARACTER SET cp1251 DEFAULT NULL,
`attach_subject` varchar(255) DEFAULT NULL,
`attach_content` longtext,
`sphinx_synced` datetime DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `Index_2` (`attach_dir`),
KEY `dt` (`date`)
) ENGINE=InnoDB AUTO_INCREMENT=898001 DEFAULT CHARSET=utf8
-
CREATE TABLE `people_emails` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`nick` varchar(255) NOT NULL,
`email` varchar(255) NOT NULL,
`key_name` varchar(255) NOT NULL,
`people_id` int(11) NOT NULL,
`status` int(11) NOT NULL DEFAULT '0',
`activity` int(11) NOT NULL,
`internal_user_id` int(11) NOT NULL,
PRIMARY KEY (`id`),
KEY `s2` (`email`,`people_id`)
) ENGINE=InnoDB AUTO_INCREMENT=22146 DEFAULT CHARSET=utf8
-
CREATE TABLE `people` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`fname` varchar(255) CHARACTER SET cp1251 NOT NULL,
`lname` varchar(255) CHARACTER SET cp1251 NOT NULL,
`patronymic` varchar(255) CHARACTER SET cp1251 NOT NULL,
`gender` tinyint(1) NOT NULL,
`full_name` varchar(255) NOT NULL DEFAULT ' ',
`category` int(11) NOT NULL,
`people_type_id` int(255) DEFAULT NULL,
`tags` varchar(255) CHARACTER SET cp1251 NOT NULL,
`job` varchar(255) CHARACTER SET cp1251 NOT NULL,
`post` varchar(255) CHARACTER SET cp1251 NOT NULL,
`profession` varchar(255) CHARACTER SET cp1251 DEFAULT NULL,
`zip` varchar(16) CHARACTER SET cp1251 NOT NULL,
`country` int(11) DEFAULT NULL,
`region` varchar(10) NOT NULL,
`city` varchar(255) CHARACTER SET cp1251 NOT NULL,
`address` varchar(255) CHARACTER SET cp1251 NOT NULL,
`address_date` date DEFAULT NULL,
`last_update_ts` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
KEY `ppl` (`id`,`full_name`)
) ENGINE=InnoDB AUTO_INCREMENT=415040 DEFAULT CHARSET=utf8
Here is the SHOW TABLE STATUS output for those 4 tables:
Name Engine Version Row_format Rows Avg_row_length Data_length Max_data_length Index_length Data_free Auto_increment
email InnoDB 10 Dynamic 753748 12079 9104785408 0 61112320 4194304 898167
email_routing InnoDB 10 Dynamic 900152 61 55132160 0 69419008 6291456 1056033
people InnoDB 10 Dynamic 9538 386 3686400 0 2785280 4194304 415040
people_emails InnoDB 10 Dynamic 3178 752 2392064 0 98304 4194304 22146
MySQL Version 5.7.22 Ubuntu 16.04
However I have noticed one thing - if I take ORDER BY out of the query, but leaving the LIMIT, then query runs almost instantly taking not more than 0.2 seconds. So I have started to think to run query with no ORDER BY and do sorting by PHP means like that but eventually that seems to complicated as using the LIMIT with no ORDER BY I get wronge range to sort.
Is there anything else I could do to speed up or optimize that query?
AS AN ALTERNATIVE I could do sorting and paging by my PHP code. I add addtional columnt into the SELECT ..., UNIX_TIMESTAMP(e.date) as ts and then do:
<?php
...
$main_query = $server->query($query);
$emails_list = $main_query->fetch_all(MYSQLI_ASSOC);
function cmp($a, $b) {
return strcmp($a['ts'], $b['ts']);
}
$emails_sorted = usort($emails_list, "cmp");
for ($i=$start;$i<$lenght;$i++)
{
$singe_email = $emails_sorted[$i]
// Format the output
}
But when I do that I get
Fatal error: Allowed memory size of 134217728 bytes exhausted
at line of $emails_sorted = usort($emails_list, "cmp");
Warning, I'm not very familiar with MySQL, in fact I'm mostly projecting MSSQL experience on top of things I (mostly) read about MySQL.
1) Potential workaround: is it safe to assume that email.id and email.date are always in the same order? From a functional point of view this seems logical as emails get added to the table over time and thus have an ever increasing auto-number... But maybe the initial load of the data was in a different/random order? Anyway, if it is, what happens if you ORDER BY e.id instead of ORDER BY e.date ?
2) Does adding a composite index on email (id, date) (in that order!) help?
3) If all of that does not help, splitting the query into 2 parts might help out the optimizer. (You may need to fix the syntax for MySQL)
-- Locate what we want first
CREATE TEMPORARY TABLE results (
SELECT e.id
r.basket
FROM email_routing r
JOIN email e ON e.id = r.message_id
WHERE r.sender_email_id = 21897
ORDER BY e.date desc LIMIT 0, 50 );
-- Again, having an index on email (id, date) seems like a good idea to me
-- (As a test you may want to add an index on results (id) here, shouldn't take long and
-- in MSSQl it would help build a better query plan, can't tell with MySQL)
-- return actual results
SELECT n1.full_name AS sender_full_name,
s1.email AS sender_email,
e.subject, e.body, e.attach, e.date, e.id, r.status,
n2.full_name AS receiver_full_name,
s2.email AS receiver_email,
r.basket,
FROM results r
JOIN email e ON e.id = r.message_id
JOIN people_emails s1 ON s1.id = r.sender_email_id
JOIN people n1 ON n1.id = s1.people_id
JOIN people_emails s2 ON s2.id = r.receiver_email_id
JOIN people n2 ON n2.id = s2.people_id
ORDER BY e.date desc
If your data comes back that quickly, how about wrapping it... but how many rows are actually GOING to be returned WITHOUT the LIMIT. Maybe you would still get better performance AFTER such as...
select PQ.*
from ( YourQueryWithoutOrderByAndLimt ) PQ
order by PQ.date desc
LIMIT 0, 50;
I suspect this is a case where the MySQL Join Optimizer overestimates the benefits of Block Nested Loop (BNL) join. You can try to turn off BNL by doing:
set optimizer_switch='block_nested_loop=off';
Hopefully this will provide a better join order. You could also try:
set optimizer_prune_level = 0;
to force the join optimizer to explore all possible join orders.
Another option is to use STRAIGHT_JOIN to force a particular join order. In this case, it seems the order as specified in the query text would be good. Hence, to force this particular join order you could write
SELECT STRAIGHT_JOIN ...
Note that whatever you do, you can not expect the query to be as fast as without ORDER BY. As long as you need to find the latest emails from a particular sender, and there is no information about sender in the email table, it is not possible to use an index to avoid sorting without going through all emails from all senders. Things would be different if you had information about date in the email_routing table. Then an index on that table could have been used to avoid sorting.
MySQL cannot use index for order by in your query because
The query joins many tables, and the columns in the ORDER BY are not
all from the first nonconstant table that is used to retrieve rows.
(This is the first table in the EXPLAIN output that does not have a
const join type.)
MySQL Order By Optimization

Slow subquery: group by a groupwise maximum

I have two tables:
CREATE TABLE share_prices (
price_id int(10) unsigned NOT NULL AUTO_INCREMENT,
price_date date NOT NULL,
company_id int(10) NOT NULL,
high decimal(20,2) DEFAULT NULL,
low decimal(20,2) DEFAULT NULL,
close decimal(20,2) DEFAULT NULL,
PRIMARY KEY (price_id),
UNIQUE KEY price_date (price_date,company_id),
KEY company_id (company_id),
KEY price_date_2 (price_date)
) ENGINE=InnoDB AUTO_INCREMENT=368586 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
And
CREATE TABLE rating_lookup (
rating_id int(11) NOT NULL,
start_date date DEFAULT NULL,
start_price decimal(10,2) DEFAULT NULL,
broker_id int(11) DEFAULT NULL,
company_id int(11) DEFAULT NULL,
end_date date DEFAULT NULL,
PRIMARY KEY (rating_id),
KEY idx_rating_lookup_company_id (company_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
This is the current query:
SELECT broker_id, count(rating_id)
FROM (
SELECT rating_lookup.*,
share_prices.company_id as correct_company,
share_prices.price_date,
max(high) as peak_gain,
( ( ( max(high) - rating_lookup.start_price ) / rating_lookup.start_price ) * 100 ) as percent_gain
FROM rating_lookup, share_prices
WHERE share_prices.price_date > rating_lookup.start_date
AND share_prices.price_date < ifnull(end_date, curdate())
AND share_prices.company_id = rating_lookup.company_id
GROUP BY rating_id
HAVING percent_gain > 5
) correct
GROUP BY broker_id
Currently this query takes 10.969 sec.
The isolated subquery takes 0.391 sec (duration) / 10.438 sec (fetch)
Query objective:
Get the total amount of correct ratings per broker_id.
A correct rating is defined as a rating that has a reached + 5% since its start_price.
I am looking to drastically decrease the query time, even if restructuring the database is the only way.
Appendix
Explain of above query:
+---+---------+---------------+-------+--------------------------------------+------------+---+----------------------------------------+---------+---------------------------------+
| 1 | PRIMARY | <derived2> | ALL | | | | | 3894800 | Using temporary; Using filesort |
| 2 | DERIVED | rating_lookup | index | PRIMARY,idx_rating_lookup_company_id | PRIMARY | 4 | | 18200 | Using where |
| 2 | DERIVED | share_prices | ref | price_date,company_id,price_date_2 | company_id | 4 | brokermetrics.rating_lookup.company_id | 214 | Using where |
+---+---------+---------------+-------+--------------------------------------+------------+---+----------------------------------------+---------+---------------------------------+
share_prices ~ 375,000 rows
rating_lookup ~ 18,000 rows with around 46 unique brokers
I assume that share prices are inserted once per day after the market closes (or a couple of times per day if you cover multiple markets).
If you don't manage to tune the query sufficiently you can pre-calculate the result. Run the query after every time you finish loading a batch of new stock prices. Insert the result in a new table. Reading the pre-calculated data should be fast enough.
PRIMARY KEY (price_id), -- useless
UNIQUE KEY price_date (price_date,company_id), -- could/should be PK
KEY company_id (company_id),
KEY price_date_2 (price_date) -- redundant
-->
PRIMARY KEY(price_date, company_id),
KEY company_id (company_id)
decimal(20,2) consumes 9 bytes, no existing stock is likely to exceed 6 digits to the left of the decimal point, and does not handle low-priced stocks that need more than two decimals. Consider DECIMAL(8,2) (4 bytes) or (10,4) (5 bytes). FLOAT (4 bytes) avoids most of the issues, but is limited to 7 significant digits.
Smaller --> more cacheable --> less I/O --> faster.
Don't SELECT stuff you don't need. All you need is
SELECT rating_id, broker_id
and move the expression to the HAVING:
HAVING ((( max(high)... *100) > 5
Please use the JOIN..ON syntax:
FROM rating_lookup, share_prices
WHERE share_prices.company_id = rating_lookup.company_id
AND ...
-->
FROM rating_lookup AS r
JOIN share_prices AS p
ON p.company_id = r.company_id
WHERE ...
Extending Klas' answer, below is a schema of a "summary" table that could be populated with pre-calculated record per-broker, per-company, per-day basis.
Disclaimer: haven't tested on real data but should work.
CREATE TABLE `price_summary` (
`price_id` int(10) NOT NULL,
`broker_id` int(10) NOT NULL DEFAULT '0',
`company_id` int(10) NOT NULL DEFAULT '0',
`start_date` int(10) NOT NULL DEFAULT '0',
`end_date` int(10) NOT NULL DEFAULT '0',
`peak_gain` int(10) NOT NULL DEFAULT '0',
`max_price` int(10) NOT NULL DEFAULT '0',
`percentage_gain` decimal(10,0) NOT NULL DEFAULT '0',
`updated_on` int(10) NOT NULL DEFAULT '0'
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
--
-- Indexes for dumped tables
--
--
-- Indexes for table `price_summary`
--
ALTER TABLE `price_summary`
ADD PRIMARY KEY (`price_id`),
ADD UNIQUE KEY `broker_company_date` (`broker_id`,`company_id`,`start_date`) USING BTREE,
ADD KEY `broker_id` (`broker_id`),
ADD KEY `company_id` (`company_id`),
ADD KEY `start_date` (`start_date`),
ADD KEY `end_date` (`end_date`),
ADD KEY `peak_gain` (`peak_gain`),
ADD KEY `max_price` (`max_price`),
ADD KEY `percentage_gain` (`percentage_gain`);
ALTER TABLE `price_summary`
MODIFY `price_id` int(10) NOT NULL AUTO_INCREMENT;
And a sample query to retrieve desired records.
SELECT
broker_id,
count(company_id) as company_count
FROM
price_summary
WHERE
start_date > {input_timestamp}
AND
end_date < {input_timestamp/now()}
AND
percentage_gain > {input_percentage}
GROUP BY
broker_id

Sorting result of mysql join by avg of third table?

I have three tables.
One table contains submissions which has about 75,000 rows
One table contains submission ratings and only has < 10 rows
One table contains submission => competition mappings and for my test data also has about 75,000 rows.
What I want to do is
Get the top 50 submissions in a round of a competition.
Top is classified as highest average rating, followed by highest amount of votes
Here is the query I am using which works, but the problem is that it takes over 45 seconds to complete! I profiled the query (results at bottom) and the bottlenecks are copying the data to a tmp table and then sorting it so how can I speed this up?
SELECT `submission_submissions`.*
FROM `submission_submissions`
JOIN `competition_submissions`
ON `competition_submissions`.`submission_id` = `submission_submissions`.`id`
LEFT JOIN `submission_ratings`
ON `submission_submissions`.`id` = `submission_ratings`.`submission_id`
WHERE `top_round` = 1
AND `competition_id` = '2'
AND `submission_submissions`.`date_deleted` IS NULL
GROUP BY submission_submissions.id
ORDER BY AVG(submission_ratings.`stars`) DESC,
COUNT(submission_ratings.`id`) DESC
LIMIT 50
submission_submissions
CREATE TABLE `submission_submissions` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`account_id` int(11) NOT NULL,
`title` varchar(255) NOT NULL,
`description` varchar(255) DEFAULT NULL,
`genre` int(11) NOT NULL,
`goals` text,
`submission` text NOT NULL,
`date_created` datetime DEFAULT NULL,
`date_modified` datetime DEFAULT NULL,
`date_deleted` datetime DEFAULT NULL,
`cover_image` varchar(255) DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `genre` (`genre`),
KEY `account_id` (`account_id`),
KEY `date_created` (`date_created`)
) ENGINE=InnoDB AUTO_INCREMENT=115037 DEFAULT CHARSET=latin1;
submission_ratings
CREATE TABLE `submission_ratings` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`account_id` int(11) NOT NULL,
`submission_id` int(11) NOT NULL,
`stars` tinyint(1) NOT NULL,
`date_created` datetime DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `submission_id` (`submission_id`),
KEY `account_id` (`account_id`),
KEY `stars` (`stars`)
) ENGINE=InnoDB AUTO_INCREMENT=7 DEFAULT CHARSET=latin1;
competition_submissions
CREATE TABLE `competition_submissions` (
`competition_id` int(11) NOT NULL,
`submission_id` int(11) NOT NULL,
`top_round` int(11) DEFAULT '1',
PRIMARY KEY (`submission_id`),
KEY `competition_id` (`competition_id`),
KEY `top_round` (`top_round`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
SHOW PROFILE Result (ordered by duration)
state duration (summed) in sec percentage
Copying to tmp table 33.15621 68.46924
Sorting result 11.83148 24.43260
removing tmp table 3.06054 6.32017
Sending data 0.37560 0.77563
... insignificant amounts removed ...
Total 48.42497 100.00000
EXPLAIN
id select_type table type possible_keys key key_len ref rows Extra
1 SIMPLE competition_submissions index_merge PRIMARY,competition_id,top_round competition_id,top_round 4,5 18596 Using intersect(competition_id,top_round); Using where; Using index; Using temporary; Using filesort
1 SIMPLE submission_submissions eq_ref PRIMARY PRIMARY 4 inkstakes.competition_submissions.submission_id 1 Using where
1 SIMPLE submission_ratings ALL submission_id 5 Using where; Using join buffer (flat, BNL join)
Assuming that in reality you won't be interested in unrated submissions, and that a given submission only has a single competition_submissions entry for a given match and top_round, I suggest:
SELECT s.*
FROM (SELECT `submission_id`,
AVG(`stars`) AvgStars,
COUNT(`id`) CountId
FROM `submission_ratings`
GROUP BY `submission_id`
ORDER BY AVG(`stars`) DESC, COUNT(`id`) DESC
LIMIT 50) r
JOIN `submission_submissions` s
ON r.`submission_id` = s.`id` AND
s.`date_deleted` IS NULL
JOIN `competition_submissions` c
ON c.`submission_id` = s.`id` AND
c.`top_round` = 1 AND
c.`competition_id` = '2'
ORDER BY r.AvgStars DESC,
r.CountId DESC
(If there is more than one competition_submissions entry per submission for a given match and top_round, then you can add the GROUP BY clause back in to the main query.)
If you do want to see unrated submissions, you can union the results of this query to a LEFT JOIN ... WHERE NULL query.
There is a simple trick that works on MySql and helps to avoid copying/sorting huge temp tables in queries like this (with LIMIT X).
Just avoid SELECT *, this copies all columns to the temporary table, then this huge table is sorted, and in the end, the query takes only 50 records from this huge table ( 50 / 70000 = 0,07 % ).
Select only columns that are really necessary to perform sort and limit, and then join missing columns only for selected 50 records by id.
select ss.*
from submission_submissions ss
join (
SELECT `submission_submissions`.id,
AVG(submission_ratings.`stars`) stars,
COUNT(submission_ratings.`id`) cnt
FROM `submission_submissions`
JOIN `competition_submissions`
ON `competition_submissions`.`submission_id` = `submission_submissions`.`id`
LEFT JOIN `submission_ratings`
ON `submission_submissions`.`id` = `submission_ratings`.`submission_id`
WHERE `top_round` = 1
AND `competition_id` = '2'
AND `submission_submissions`.`date_deleted` IS NULL
GROUP BY submission_submissions.id
ORDER BY AVG(submission_ratings.`stars`) DESC,
COUNT(submission_ratings.`id`) DESC
LIMIT 50
) xx
ON ss.id = xx.id
ORDER BY xx.stars DESC,
xx.cnt DESC;

Optimize mysql query (use or create index)

I have a SQL request (mySQL 5.1.51) that makes PHP timeout.
I would like to optimize it but I can't find what is missing.
The request is :
SELECT s_i.incident,
s.hostname,
a.application,
s_ie.problem_status,
s_i.open_time,
s_i.close_time,
s_ie.open_group,
s_ie.primary_assignment,
s_ie.closed_by_group,
s_ie.contact_first_name,
s_ie.contact_last_name,
s_ie.description,
s_ie.resolution,
s_ie.famille_1,
s_ie.famille_2,
s_ie.famille_3,
YEARWEEK(s_i.open_time) AS 'semaine_ouverture',
DATE_FORMAT(s_i.open_time, '%Y-%m') AS 'mois_ouverture',
YEARWEEK(s_i.close_time) AS 'semaine_cloture',
DATE_FORMAT(s_i.close_time, '%Y-%m') AS 'mois_cloture',
p.nom,
s.exploite_par,
t.environnement,
a.tdb
FROM t_link_serveur_eac t USE KEY(nna)
INNER JOIN serveur s ON s.id = t.id_serveur
INNER JOIN plateau p ON p.id = t.id_plateau
INNER JOIN applications a ON a.nna = t.nna
INNER JOIN scope_i s_i USE KEY (id_serveur) ON s_i.id_serveur = t.id_serveur
INNER JOIN scope_i_extended s_ie USE KEY (id_scope_i) ON s_ie.id_scope_i = s_i.id
WHERE s_ie.problem_status = 'Closed'
AND s_ie.contact_first_name = 'AUTOMATE'
AND s_ie.contact_last_name LIKE '%BEM%'
AND p.id = 4
AND open_time >= CURDATE() - INTERVAL 52 WEEK AND open_time <= CURDATE()
AND AND s_i.close_time < CURDATE() - INTERVAL DAYOFMONTH(CURDATE()) - 1 DAY
ORDER BY mois_cloture
When I ask mySQL to explain it, I have a line type 'ALL' for the union of the table s_ie.
I tried to create/modify all possibles index but all my tried didn't make any difference :
id select_type table type possible_keys key key_len ref rows Extra
1 SIMPLE p const PRIMARY PRIMARY 4 const 1 Using temporary; Using filesort
1 SIMPLE a ALL PRIMARY NULL NULL NULL 957
1 SIMPLE t ref nna nna 26 inspire.a.nna 10 Using where
1 SIMPLE s eq_ref PRIMARY PRIMARY 4 inspire.t.id_serveur 1
1 SIMPLE s_i ref id_serveur id_serveur 4 inspire.t.id_serveur 135 Using where
1 SIMPLE s_ie eq_ref id_scope_i id_scope_i 4 inspire.s_i.id 1 Using where
s_ie has 712.000 lines and s_i 740.000 so I think that the problem comes from this junction
Here is the structure of the table s_ie
CREATE TABLE IF NOT EXISTS `scope_i_extended` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`id_scope_i` int(11) NOT NULL,
`problem_status` varchar(16) NOT NULL,
`open_group` varchar(32) NOT NULL,
`primary_assignment` varchar(32) NOT NULL,
`closed_by_group` varchar(32) NOT NULL,
`contact_first_name` varchar(32) NOT NULL,
`contact_last_name` varchar(32) NOT NULL,
`description` text NOT NULL,
`resolution` text NOT NULL,
`famille_1` text NOT NULL,
`famille_2` text NOT NULL,
`famille_3` text NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `id_scope_i` (`id_scope_i`),
UNIQUE KEY `problem_status` (`id_scope_i`, `problem_status`, `contact_first_name`, `contact_last_name`),
KEY `contact_last_name` (`contact_last_name`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8;
And the structure of s_i
CREATE TABLE IF NOT EXISTS `scope_i` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`incident` varchar(20) NOT NULL,
`statut` varchar(20) NOT NULL,
`id_serveur` int(11) NOT NULL,
`open_time` datetime NOT NULL,
`close_time` datetime DEFAULT NULL,
`affectation` varchar(50) NOT NULL,
`titre` varchar(200) NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `incident` (`incident`),
KEY `serveur` (`id_serveur`),
KEY `serveur_open_time` (`id_serveur`,`open_time`),
KEY `id_serveur` (`id_serveur`,`close_time`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8 AUTO_INCREMENT=738862 ;
May you help/save me?
Regards,
Olivier
Sorry for stating the obvious but I'd suggest using:
"YEARWEEK(open_time) <= '201246' AND YEARWEEK(open_time) >= '201146'"
instead of
"YEARWEEK(open_time) IN (...)"
IN always slows things dramatically.
In your condition, you have date columns wrapped within a Mysql Function.
ex: YEARWEEK(open_time) and DATE_FORMAT(s_i.close_time, '%Y-%m-%d')
You should avoid this since Mysql seems to execute the function for each row of the table.
Can you try to replace
DATE_FORMAT(s_i.close_time, '%Y-%m-%d') < DATE_FORMAT(NOW(), '%Y-%m-01')
by
s_i.close_time < CURDATE() - INTERVAL DAYOFMONTH(CURDATE()) - 1 DAY
and
YEARWEEK(open_time) IN ('201246', '201245'....)
by this: (below is condition to get all records has "open_time" within a year. I am not sure if it's your case)
open_time >= CURDATE() - INTERVAL 1 YEAR AND open_time <= CURDATE()